[debian-edu-commits] debian-edu/ 01/10: New upstream version 1.2.1

Dominik George natureshadow-guest at moszumanska.debian.org
Mon Jan 15 22:04:35 UTC 2018


This is an automated email from the git hooks/post-receive script.

natureshadow-guest pushed a commit to branch master
in repository sdaps.

commit 9079231af7213624b0177b2645352edb1aee4518
Author: Dominik George <nik at naturalnet.de>
Date:   Mon Jan 15 21:26:16 2018 +0100

    New upstream version 1.2.1
---
 COPYING                                     |   11 +
 MANIFEST.in                                 |   20 +
 NEWS                                        |  299 +++
 PKG-INFO                                    |   14 +
 README                                      |  110 +
 TROUBLESHOOTING                             |   60 +
 bin/sdaps                                   |   23 +
 custom-scripts/README                       |   48 +
 custom-scripts/sdaps-identify.py            |   86 +
 custom-scripts/sdaps-overlay.py             |  129 ++
 custom-scripts/sdaps-recognize.py           |   93 +
 examples/example.tex                        |  157 ++
 examples/example.tif                        |  Bin 0 -> 669246 bytes
 po/POTFILES.in                              |   45 +
 po/POTFILES.skip                            |    2 +
 po/ar.po                                    | 1097 ++++++++++
 po/de.po                                    | 1293 ++++++++++++
 po/es.po                                    | 1284 ++++++++++++
 po/fi.po                                    | 1111 ++++++++++
 po/fr.po                                    | 1155 ++++++++++
 po/nl.po                                    | 1089 ++++++++++
 po/pt.po                                    | 1258 +++++++++++
 po/pt_BR.po                                 | 1258 +++++++++++
 po/sdaps.pot                                | 1089 ++++++++++
 po/sv.po                                    | 1089 ++++++++++
 sdaps.py                                    |   23 +
 sdaps/__init__.py                           |   72 +
 sdaps/add/__init__.py                       |  117 ++
 sdaps/annotate/__init__.py                  |   61 +
 sdaps/annotate/buddies.py                   |  259 +++
 sdaps/boxgallery/__init__.py                |  130 ++
 sdaps/boxgallery/buddies.py                 |  138 ++
 sdaps/calculate.py                          |  204 ++
 sdaps/clifilter.py                          |   52 +
 sdaps/cmdline/__init__.py                   |   21 +
 sdaps/cmdline/add.py                        |  140 ++
 sdaps/cmdline/annotate.py                   |   41 +
 sdaps/cmdline/boxgallery.py                 |   55 +
 sdaps/cmdline/convert.py                    |   66 +
 sdaps/cmdline/cover.py                      |   42 +
 sdaps/cmdline/csvdata.py                    |  121 ++
 sdaps/cmdline/gui.py                        |   45 +
 sdaps/cmdline/ids.py                        |   83 +
 sdaps/cmdline/info.py                       |   75 +
 sdaps/cmdline/recognize.py                  |   59 +
 sdaps/cmdline/reorder.py                    |   42 +
 sdaps/cmdline/report.py                     |   80 +
 sdaps/cmdline/reporttex.py                  |   63 +
 sdaps/cmdline/setup.py                      |  102 +
 sdaps/cmdline/setuptex.py                   |   49 +
 sdaps/cmdline/stamp.py                      |   58 +
 sdaps/convert/__init__.py                   |   42 +
 sdaps/cover/__init__.py                     |   48 +
 sdaps/csvdata/__init__.py                   |   52 +
 sdaps/csvdata/buddies.py                    |  259 +++
 sdaps/defs.py                               |  201 ++
 sdaps/gui/__init__.py                       |  471 +++++
 sdaps/gui/buddies.py                        |  297 +++
 sdaps/gui/main_window.ui                    |  457 ++++
 sdaps/gui/sheet_widget.py                   |  446 ++++
 sdaps/gui/widget_buddies.py                 |  313 +++
 sdaps/image/__init__.py                     |   55 +
 sdaps/image/image.c                         | 1351 ++++++++++++
 sdaps/image/image.h                         |   79 +
 sdaps/image/surface.c                       |  373 ++++
 sdaps/image/surface.h                       |   76 +
 sdaps/image/transform.c                     |  556 +++++
 sdaps/image/transform.h                     |   36 +
 sdaps/image/wrap_image.c                    |  490 +++++
 sdaps/log.py                                |  208 ++
 sdaps/matrix.py                             |  103 +
 sdaps/model/__init__.py                     |   70 +
 sdaps/model/buddy.py                        |   93 +
 sdaps/model/data.py                         |   84 +
 sdaps/model/questionnaire.py                |  400 ++++
 sdaps/model/sheet.py                        |  118 ++
 sdaps/model/survey.py                       |  410 ++++
 sdaps/paths.py                              |  101 +
 sdaps/recognize/__init__.py                 |   39 +
 sdaps/recognize/blank.py                    |   74 +
 sdaps/recognize/buddies.py                  |  911 ++++++++
 sdaps/recognize/classic.py                  |  163 ++
 sdaps/recognize/code128.py                  |  149 ++
 sdaps/recognize/qrcode.py                   |  103 +
 sdaps/reorder/__init__.py                   |   90 +
 sdaps/report/__init__.py                    |  123 ++
 sdaps/report/answers.py                     |  333 +++
 sdaps/report/buddies.py                     |  288 +++
 sdaps/report/flowables.py                   |  245 +++
 sdaps/reporttex/__init__.py                 |  172 ++
 sdaps/reporttex/buddies.py                  |  237 +++
 sdaps/script.py                             |  113 +
 sdaps/setup/__init__.py                     |    0
 sdaps/setup/additionalparser.py             |   34 +
 sdaps/setup/buddies.py                      |  229 ++
 sdaps/setupodt/__init__.py                  |  144 ++
 sdaps/setupodt/boxesparser.py               |  264 +++
 sdaps/setupodt/metaparser.py                |   86 +
 sdaps/setupodt/qobjectsparser.py            |  102 +
 sdaps/setuptex/__init__.py                  |  175 ++
 sdaps/setuptex/sdapsfileparser.py           |  148 ++
 sdaps/stamp/__init__.py                     |   93 +
 sdaps/stamp/generic.py                      |  548 +++++
 sdaps/stamp/latex.py                        |   57 +
 sdaps/surface.py                            |   94 +
 sdaps/template.py                           |  179 ++
 sdaps/utils/__init__.py                     |    0
 sdaps/utils/barcode.py                      |  132 ++
 sdaps/utils/create-latexmap.py              |   57 +
 sdaps/utils/exceptions.py                   |   23 +
 sdaps/utils/image.py                        |  115 +
 sdaps/utils/latex.py                        |  125 ++
 sdaps/utils/latexmap.py                     |  436 ++++
 sdaps/utils/mimetype.py                     |   34 +
 sdaps/utils/opencv.py                       |  252 +++
 sdaps/utils/paper.py                        |   70 +
 sdaps/utils/ugettext.py                     |   36 +
 setup.cfg                                   |   10 +
 setup.py                                    |  205 ++
 test/data/info_files/test-odt               |   24 +
 test/data/info_files/test-odt-lo35          |   24 +
 test/data/info_files/test-tex-ids           |   23 +
 test/data/info_files/test-tex-ids.2         |   23 +
 test/data/info_files/test-tex-no-ids        |   24 +
 test/data/info_files/test-tex-no-ids.2      |   24 +
 test/data/info_files/test-tex-no-ids.3      |   24 +
 test/data/odt-3/debug.internetquestions     |    2 +
 test/data/odt-3/debug.odt                   |  Bin 0 -> 30269 bytes
 test/data/odt-3/debug.pdf                   |  Bin 0 -> 238866 bytes
 test/data/odt-3/questionnaire_ids           |    5 +
 test/data/odt-5/debug.internetquestions     |    2 +
 test/data/odt-5/debug.odt                   |  Bin 0 -> 23563 bytes
 test/data/odt-5/debug.pdf                   |  Bin 0 -> 239252 bytes
 test/data/odt-5/questionnaire_ids           |    5 +
 test/data/odt/debug.internetquestions       |    2 +
 test/data/odt/debug.odt                     |  Bin 0 -> 30269 bytes
 test/data/odt/debug.pdf                     |  Bin 0 -> 232600 bytes
 test/data/odt/debug.tif                     |  Bin 0 -> 2910536 bytes
 test/data/tex/code128_test_ids              |    4 +
 test/data/tex/questionnaire_classic.tex     |   67 +
 test/data/tex/questionnaire_with_ids.tex    |   67 +
 test/data/tex/questionnaire_without_ids.tex |  147 ++
 test/data/tex/test_with_ids.tif             |  Bin 0 -> 224426 bytes
 test/data/tex/test_without_ids.tif          |  Bin 0 -> 669246 bytes
 test/data/tex/test_without_ids_2.tif        |  Bin 0 -> 1060996 bytes
 test/run-test-locally.sh                    |    3 +
 test/run-test.sh                            |  204 ++
 tex/code128.tex                             |  374 ++++
 tex/po/POTFILES.in                          |    1 +
 tex/po/de.po                                |   88 +
 tex/po/es.po                                |   88 +
 tex/po/fi.po                                |   87 +
 tex/po/fr.po                                |   88 +
 tex/po/nl.po                                |   85 +
 tex/po/pt.po                                |   88 +
 tex/po/pt_BR.po                             |   88 +
 tex/po/sdaps-tex.pot                        |   82 +
 tex/po/sv.po                                |   83 +
 tex/qrcode.sty                              | 3023 +++++++++++++++++++++++++++
 tex/sdaps.cls                               | 1266 +++++++++++
 tex/sdapsreport.cls                         |  324 +++
 tex/tex_translations.in                     |   42 +
 162 files changed, 35376 insertions(+)

diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..9cf937f
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,11 @@
+This program is free software. Different licenses apply to different parts
+of the program.
+
+Most of the files are distributed under the conditions of the GPLv3 or any
+later versions (see COPYING.GPLv3)
+
+Files in the "tex" directory are distributed under the conditions of the
+LPPLv1.3c or any later version.
+
+All files should have a corresponding copyright header, if one is missing
+please write the authors to clarify the license.
\ No newline at end of file
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..181b932
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,20 @@
+include MANIFEST.in
+include NEWS
+include README
+include TROUBLESHOOTING
+include COPYING
+include sdaps.py
+recursive-include sdaps *.py *.ui *.c *.h
+recursive-include po *.pot POTFILES.skip POTFILES.in *.po
+recursive-include tex/po *.pot POTFILES.skip POTFILES.in *.po
+include test/run-test.sh
+include test/run-test-locally.sh
+recursive-include test/data *.internetquestions *.odt *.pdf *.tif
+recursive-include test/data *.tex questionnaire_ids
+recursive-include test/data/info_files *
+include custom-scripts/README
+recursive-include custom-scripts *.py
+include examples/example.tex
+include examples/example.tif
+include test/data/tex/code128_test_ids
+include tex/tex_translations.in
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..8702036
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,299 @@
+Overview of Changes in SDAPS 1.2.1
+==================================
+
+Important changes:
+ * Fix regression in questionnaire ID handling
+
+Overview of Changes in SDAPS 1.2.0
+==================================
+
+Important changes:
+ * Allow using LO 5.2, requires filling the background of checkboxes
+ * Use python2 instead of python (#91)
+ * Fix non-square checkboxes (#92)
+ * Update TeX code and example for newer LaTeX versions
+ * Fix encoding issues (#98)
+ * Fix a recursion in the GUI
+
+New and updated languages:
+ * French
+
+Overview of Changes in SDAPS 1.1.11
+===================================
+
+Mostly a bugfix release again. The most important fix (preventing dataloss) is
+that the data is now written atomically. This means that aborting an SDAPS run
+should not result in a corrupted database anymore.
+
+Important changes:
+ * model: Write survey file atomically (#79)
+ * image: compatibility with older glib versions
+ * csv: Import code fixes (#79)
+ * latex: Work around manual enumeration breaking automatic numbering
+ * setup: Create nested directories instead of only one leve
+ * convert: Support landscape questionnaires (#88)
+
+New and updated languages:
+ * Arabic
+
+Overview of Changes in SDAPS 1.1.10
+===================================
+
+Again, mostly a bugfix release. One thing to note is that if you are using
+small fields (i.e. choiceitemtext) is that single characters/digits can
+sometimes be detected as only dirty. So if you want to use fields with only
+a single character, then it is likely a good idea to fine tune the minimum
+size of writing and other aspects of freeform field recognition.
+
+Important changes:
+ * latex: Fix search paths (#63)
+ * latex report: Fix formatting issues
+ * gui: Fix broken display in some corner cases (#67)
+ * gui: Ensure dialogs are on top of the main window (#66)
+ * csv: Options to export recognition quality
+ * Allow filtering based on string replacements for freeform fields (#69)
+ * latex: Fix encoding issues for some special characters (#70)
+ * latex: Allow smaller choiceitemtext elements (#68)
+
+New and updated languages:
+ * French
+ * German
+ * Portuguese (Brazil)
+ * Dutch
+
+Overview of Changes in SDAPS 1.1.9
+==================================
+
+Mostly a bugfix release.
+
+Important changes:
+ * csv: PNG export for complete questions
+ * Fix various encoding issues (#59, #61)
+ * dynamic kfill size for barcode detection
+ * Fix LaTeX search path regression (see #11)
+
+New and updated languages:
+ * Spanish
+
+Overview of Changes in SDAPS 1.1.8
+==================================
+
+Important changes:
+ * recognize: prevent division by zero error
+ * recognize: try barcode detection both with and without kfill
+ * recognize: ignore data from previous run
+ * csv: Add support to specify delimiter
+ * csv, ids: allow output to any file including stdout
+ * gui: properly escape all strings
+
+New and updated languages:
+ * German
+
+Overview of Changes in SDAPS 1.1.7
+==================================
+
+The most important change in this release is that the
+import of image data has been simplified.
+
+SDAPS can now do an image format conversion automatically
+as part of the "add" command, removing the necessity of
+using "convert" or some other external method to preprocess
+the images. As before, this feature requires OpenCV.
+
+Another change is that SDAPS now imports PDF files
+directly. If a PDF file contains a full page image
+(i.e. a scanned document) then this image is used
+directly to prevent image quality loss due to resampling.
+This feature requires poppler to be installed.
+
+Overall these changes make it a lot easier to work with
+different scanners. It is now only neccessary to
+pass the "--convert" option to the "add" command to
+add files that are not already in the expected format.
+
+Important changes:
+ * stamp: Fix re-stamping all IDs
+ * add: Implement conversion feature (--convert option)
+ * convert: Add support for reading PDF files
+
+New and updated languages:
+ * German
+ * Portuguese (Brazil)
+
+
+Overview of Changes in SDAPS 1.1.6
+==================================
+
+This release adds support to use QR code instead of Code-128. The
+main advantage is that QR-Code contains redundancy so that recognition
+should be more reliable even with bad scans.
+Another important change is that it is now possible to select different
+modes for checkbox detection without modifying the source code. This
+should simplify the usage of SDAPS in certain cases.
+
+Feedback for optimizing the different modes is of course welcome. The
+thresholds have not been tested extensively.
+
+Important changes:
+ * Support for QR-Code based IDs has been added ("qr" style)
+ * csv export: Allow export of freeform textboxes as images
+ * Updated example and testcase for newer multicol versions
+ * tex: Fix writing sdaps file for all macros.
+ * Allow selection of different checkbox detection modes.
+
+New and updated languages:
+ * Portoguese (copy of Portoguese (Brazil))
+ * German
+
+Overview of Changes in SDAPS 1.1.5
+==================================
+
+Important changes:
+ * report: Fix import of PIL (Florian Rinke)
+ * odt: Fix annotation on setup failure
+ * gui: Fix memory leak
+ * latex: Small improvements to class usability
+ * translations: Fix LaTeX dictionary names.
+
+New and updated languages:
+ * Finnish
+ * German
+
+Overview of Changes in SDAPS 1.1.4
+==================================
+
+This is mostly a bugfix and translations release, as there was still some
+fallout from the refactoring done in the last release. Thanks to everyone
+who submitted patches to fix these!
+
+Important changes:
+ * dependency, build, and import fixes (#44, #46, and more)
+ * fix layout changes in LaTeX and example (introduced in 1.1.2)
+ * report: fix non A4 paper sizes (issue #41)
+
+New and updated languages:
+ * Portuguese (Brazil)
+ * Spanish
+ * German
+
+Overview of Changes in SDAPS 1.1.3
+==================================
+
+With this release SDAPS has been restructured internally. There are two
+reasons for doing this. The first is to improve the API which
+simplifies the usage of it in custom scripts. Another point is that
+the old code was incompatible with the import handling of python 3. So
+doing this change is also a prerequisite for a future port to python 3.x.
+
+Other changes include:
+ * GUI: Fix an offset error with new GTK+ versions
+ * GUI: Improved keyboard navigation (issue #30)
+ * GUI: Improved mouse handling and overlay drawing
+ * GUI: Show the questionnaire ID on the right side
+ * GUI: Sort images by page number
+ * LaTeX: Improved unicode support
+ * LaTeX: Fixed precision issues in report generation
+ * LaTeX: Fixed some whitespace issues in the LaTeX class
+ * ODT: When stamping a single document, keep forms intact
+ * reorder: Fix reordering of simplex documents
+ * recognize: Slight changes in the OMR heuristics.
+ * Fixed issues in the upgrade routine
+
+
+New and updated translations:
+ * German
+ * Spanish
+
+Overview of Changes in SDAPS 1.1.2
+==================================
+
+This release brings a lot of small improvements, but also some new features.
+The main new feature is the addition of a "convert" module, which can be used
+to convert non-monochrome scans into monochrome images for later processing.
+This module is also able to apply 3D-transformations as they are neccessary
+when the source image was done using a camera.
+This new module requires OpenCV. Note that using a feed scanner is still
+prefered to this method.
+
+Other changes include:
+ * LaTeX: Fix compilation of large documents (by suppressing position output)
+ * LaTeX: Fix multicolumn items and cline at the start of choicequestions
+ * ODT: Custom styling in answers and question is now possible.
+ * Various improvements and fixes in the corner mark detection code
+ * New "custom" style which can be used when customizing the behaviour of SDAPS
+ * A PDF with annotations will now be created if there was an error during setup
+ * An issue in the base dir search code that affected OSX has been fixed
+
+New and updated translations:
+ * Arabic
+
+Overview of Changes in SDAPS 1.1.1
+==================================
+
+Important changes:
+ * Fix the "min coverage" heuristic
+ * Export text as UTF-8 in CSV files (issue #23)
+ * report: Ignore empty sheets
+ * Add "verified" and "recognized" flags for sheets. Recognition will
+   not be done by default if either flag is set.
+ * GUI: Pressing "Enter" now sets the "verified" flag
+ * LaTeX class: Paint inner area of boxes white. This is required to allow
+   background coloring.
+
+And a couple more small bugfixes and additions.
+
+New and updated translations:
+ * German
+ * Dutch
+
+Overview of Changes in SDAPS 1.1.0
+==================================
+
+This release brings a lot of new goodies. As a development release it may
+still be a bit rough in a few places, but everyone is invited to play with it
+and report any issues :-)
+
+Important changes:
+ * Support for duplex scanning of simplex questionnaires (issue #1)
+ * Freeform fields can be manually replaced with text (issue #14)
+ * Mark questions can now have an arbitrary checkbox count (issue #7)
+ * Correctly pick new questionnaire IDs during stamp (issue #22)
+ * Report paper size is now locale dependend (issue #9)
+ * LaTeX: classes are now translatable using PO files
+ * GUI: Widget based view of the questionnaire
+ * LaTex report: Allow the generated LaTeX to be stored
+
+New and updated translations:
+ * German
+
+Overview of Changes in SDAPS 1.0.3
+==================================
+
+Bugfix so that the SDAPS class works with older PGF versions.
+
+Overview of Changes in SDAPS 1.0.2
+==================================
+
+Bugfix so that the commands work fine without a TTY.
+
+Overview of Changes in SDAPS 1.0.1
+==================================
+
+Only depend on distutils and pkg_resources if doing a local run.
+
+Overview of Changes in SDAPS 1.0.0
+==================================
+
+This is the first release of SDAPS. It is not fully compatible to older
+versions. Anyone with existing projects should *not* upgrade.
+
+Important changes:
+ * LaTeX: Improved spacing
+ * LaTeX: Fix position extraction code
+ * LaTeX/core: Support for circular/elliptical checkboxes
+ * LaTeX: now supports multicolumn layouts
+ * LaTeX: new command to draw a filled checkbox
+
+New and updated translations:
+ * German
+ * Arabic
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..38ead36
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,14 @@
+Metadata-Version: 1.0
+Name: sdaps
+Version: 1.2.1
+Summary: Scripts for data acquisition with paper-based surveys
+Home-page: http://sdaps.sipsolutions.net
+Author: Benjamin Berg, Christoph Simon
+Author-email: benjamin at sipsolutions.net, post at christoph-simon.eu
+License: GPL-3
+Description: 
+        SDAPS is a tool to carry out paper based surveys. You can create machine
+        readable questionnaires using LibreOffice and LaTeX. It also provides
+        the tools to later analyse the scanned data, and create a report.
+        
+Platform: UNKNOWN
diff --git a/README b/README
new file mode 100644
index 0000000..69d4682
--- /dev/null
+++ b/README
@@ -0,0 +1,110 @@
+# SDAPS
+
+This Program can be used to carry out paper based surveys.
+
+The questionnaire is designed using either OpenOffice/LibreOffice and then
+exported to PDF. SDAPS then uses both the ODT document and the PDF file
+to figure out what questions are asked, and where checkboxes and freeform
+fields are placed.
+Another great way to use SDAPS is together with its LaTeX class. It allows
+you to create questionnaires very easily and is tightly integrated into the
+SDAPS main program.
+
+After this, the program can create an arbitrary number of (unique)
+questionnaires that can be printed and handed out. After being filled out, you
+just scan them in, let sdaps run over them, and let it create a report with
+the results.
+
+
+## Requirements
+
+Depending on what part of SDAPS you use, different dependencies are
+required.
+
+general (including recognize):
+ * Python 2.7
+ * distutils and distutils-extra
+ * python-cairo (including development files)
+ * libtiff (including development files)
+ * pkg-config
+ * python-zbar for "code128" and "qr" styles
+ * python development files
+
+graphical user interface (gui):
+ * GTK+ and introspection data
+ * python-gi
+
+reportlab based reports (report):
+ * reportlab
+ * Python Imaging Library (PIL)
+
+ODT based questionnaires (setup/stamp):
+ * reportlab (for barcode rendering)
+ * pdftk or pyPdf (pdftk is much faster if you need questionnaire ids)
+ * python-pdftools
+
+LaTeX based questionnaires (setup_tex/stamp):
+ * pdflatex and packages:
+   * PGF/TikZ
+   * translator (part of beamer)
+   * and more
+
+LaTeX based reports:
+ * pdflatex and packages:
+   * PGF/TikZ
+   * translator (part of beamer)
+ * siunitx
+
+Import of other image formats (convert, add --convert):
+ * python-opencv
+ * Poppler and introspection data
+ * python-gi
+
+Debug output (annotate):
+ * Poppler and introspection data
+ * python-gi
+
+## Installation
+
+You can install sdaps using "./setup.py install". The C extension will
+be compiled automatically, but of course you have to have all the
+dependencies installed for this to work.
+
+## Standalone execution
+
+As an alternative to installing sdaps it is also supported to run it without
+installation. To do this run "./setup.py build" to build the binary modules
+and translation. After this execute sdaps using the provided "sdaps.py"
+script in the toplevel directory.
+
+## Using SDAPS
+
+Please run sdaps with "--help" after installing it for a list of commands.
+Also check the website http://sdaps.org for some examples.
+
+## Quality of the recognition
+
+The quality of the recognition in SDAPS is quite good in my experience.
+There is a certain amount of wrong detections, that mostly arise from people
+not checking or filling out the boxes correctly. For example:
+ * The cross is not inside the checkbox, but next to it
+ * People cross the same box multiple times
+ * People use very thick pens
+ * Filling out is not done properly
+
+As you can see, most of the errors arise from the possibility to correct
+wrong marks by filling out checkboxes. SDAPS tries to be smart about this
+by using different heuristics to detect the case, but it is not foolproof.
+
+Suggestions on how to decrease the error rate are of course welcome.
+
+### Matrix Errors
+
+It can happen that SDAPS is not able to calculate the transformation matrix
+which transforms the pixel space of the image into the mm coordinate system
+used internally. If this happens the affected pages cannot be further
+analysed.
+It is usually possible to manually correct them using the GUI, but that can
+be quite tedious.
+
+See also TROUBLESHOOTING for some more information.
diff --git a/TROUBLESHOOTING b/TROUBLESHOOTING
new file mode 100644
index 0000000..8920abe
--- /dev/null
+++ b/TROUBLESHOOTING
@@ -0,0 +1,60 @@
+What to do when things go wrong
+===============================
+
+In general, you can try to adjust the magic values in defs.py to improve
+recognition and work around errors that you get. If you need to change
+something, pĺease drop us a line.
+
+Matrix could not be recognized
+------------------------------
+
+This means SDAPS could not find all four edge markers.
+
+Please have a look at the following:
+ * Check that the scan is OK.
+   * Does it have black/white lines? This can happen in the scanner is not
+     clean.
+   * Check that the resolution is set to 300dpi
+   * Check there is no black line at one side
+ * Try modifying the magic recognition parameters in defs.py
+   image_line_width and image_line_coverage. However, it is unlikely
+   that this is neccessary.
+
+Recognition quality is bad
+--------------------------
+
+If you want to analyse the heuristics you can run the tool "boxgallery".
+  $ sdaps project_dir boxgallery
+This creates a PDF for every heuristic, showing the returned value. Boxes
+with a bold label are considered checked by SDAPS.
+You can adjust the criteria by changing the checkbox_metrics dictionary in
+defs.py. Each of the entries is a list that maps the heuristic to checkbox
+and quality measures.
+
+Should you find better values, please share them!
+
+NOTE: If you want to remove the errors arising from the possibility to
+correct mistakes, you can choose a different checkbox detection mode
+that disallows corrections. Please have a look at the "checkmode" parameter.
+
+Text boxes are recognized even though they are empty
+----------------------------------------------------
+
+This can happen because of dirt on the page or because the scanner is not
+very accurate and the outline is detected as writing.
+
+If dirt is detected, try to adjust the textbox_scan_* values in defs.py.
+
+If the outline is detected than probably something worst is going on. You
+can adjust the textbox_*_padding values, or adjusting the image_line_*
+values might also help (as these are used to find the corners of the
+textbox).
+
+If the above does not help
+==========================
+
+Mail the authors :-)
+It may be neccessary to have all the data to help. So if you can try to
+always provide examples where things fail.
+
+NOTE: It may be that this document is not always up to date.
\ No newline at end of file
diff --git a/bin/sdaps b/bin/sdaps
new file mode 100755
index 0000000..0f05247
--- /dev/null
+++ b/bin/sdaps
@@ -0,0 +1,23 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright (C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright (C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import sys
+
+import sdaps
+sys.exit(sdaps.main())
diff --git a/custom-scripts/README b/custom-scripts/README
new file mode 100644
index 0000000..6d861bc
--- /dev/null
+++ b/custom-scripts/README
@@ -0,0 +1,48 @@
+These are examples of what can be done by using the SDAPS modules
+directly. This may be useful for specialized use cases, where one
+wants a custom behaviour.
+
+Note that these scripts do use internal APIs that may change in
+the future. That said, these it may also make sense to simplify
+them or add the functionallity that they provide into the main
+program or a separate scripting interface.
+I guess this is a political problem about whether the internal
+APIs should be consumable by end users or not.
+
+
+There are currently two scripts that work on image data. Both of
+them will not modify the SDAPS project that they are exected
+on, but instead only output information about the scanned data.
+
+sdaps-identify.py
+-----------------
+
+Arguments: SDAPS project and one or more tif files.
+
+Returns:
+ For each set of images basic information is printed. This
+ contains the IDs, information about rotation and the
+ transformation matrix.
+
+This returns the basic data about each image. It does not
+contain data from checkboxes, and SDAPS should usually output
+the correct information even if it is for a different survey.
+
+
+sdaps-recognize.py
+------------------
+
+Arguments: SDAPS project and one or more tif files.
+
+Returns:
+ A flat representation of all the values of checkboxes for each
+ questionnaire.
+ For each checkbox you get the following values:
+  GlobalID, SurveyID, QuestionnaireID, Checkbox Identifier, Checkbox State, Recognition Quality
+
+This script runs the recognition and dumps much of the internal
+data out to stdout. Note that raw image data (from freeform text
+fields) is currently ignored.
+Code could easily be added which dumps these out to PNG files, or even
+base64 encoded strings if required.
+
diff --git a/custom-scripts/sdaps-identify.py b/custom-scripts/sdaps-identify.py
new file mode 100755
index 0000000..0ea7dcc
--- /dev/null
+++ b/custom-scripts/sdaps-identify.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python2
+# -*- coding: utf8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright (C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright (C) 2012, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import sys
+import os
+
+# Use the following and local_run=True below to run without installing SDAPS
+#sys.path.append(os.path.join(os.path.dirname(sys.argv[0]), '..'))
+
+import sdaps
+#sdaps.init(local_run=True)
+sdaps.init()
+
+from sdaps import model
+from sdaps import image
+
+# We need a survey that has the correct definitions (paper size, duplex mode)
+# Assume the first argument is a survey
+survey = model.survey.Survey.load(sys.argv[1])
+
+# We need the recognize buddies, as they are able to identify the data
+from sdaps.recognize import buddies
+
+# A sheet object to attach the images to
+sheet = model.sheet.Sheet()
+survey.add_sheet(sheet)
+
+images = []
+
+for file in sys.argv[2:]:
+    num_pages = image.get_tiff_page_count(file)
+    for page in xrange(num_pages):
+        images.append((file, page))
+
+if len(images) == 0:
+    # No images, simply exit again.
+    sys.exit(1)
+
+
+def add_image(survey, tiff, page):
+    img = model.sheet.Image()
+    survey.sheet.add_image(img)
+    # SDAPS assumes a relative path from the survey directory
+    img.filename = os.path.relpath(os.path.abspath(tiff), survey.survey_dir)
+    img.orig_name = tiff
+    img.tiff_page = page
+
+while images:
+    # Simply drop the list of images again.
+    sheet.images = []
+
+    add_image(survey, *images.pop(0))
+
+    if survey.defs.duplex:
+        add_image(survey, *images.pop(0))
+
+    sheet.recognize.recognize()
+
+    for img in sheet.images:
+        print img.orig_name, img.tiff_page
+        print '\tPage:', img.page_number
+        print '\tRotated:', img.rotated
+        print '\tMatrix (px to mm):', img.raw_matrix
+        print '\tSurvey-ID:', sheet.survey_id
+        print '\tGlobal-ID:', sheet.global_id
+        print '\tQuestionnaire-ID:', sheet.questionnaire_id
+        print
+
+# And, we simply quit, ie. we don't save the survey
+
diff --git a/custom-scripts/sdaps-overlay.py b/custom-scripts/sdaps-overlay.py
new file mode 100755
index 0000000..dcd3c72
--- /dev/null
+++ b/custom-scripts/sdaps-overlay.py
@@ -0,0 +1,129 @@
+#!/usr/bin/env python2
+# -*- coding: utf8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright (C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright (C) 2015, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import sys
+import os
+import cairo
+import math
+
+# Use the following and local_run=True below to run without installing SDAPS
+#sys.path.append(os.path.join(os.path.dirname(sys.argv[0]), '..'))
+
+import sdaps
+#sdaps.init(local_run=True)
+sdaps.init()
+
+from sdaps import model
+from sdaps import image
+from sdaps import matrix
+
+survey = model.survey.Survey.load(sys.argv[1])
+counter = 1
+
+
+def ellipse(cr, x, y, width, height):
+    cr.save()
+
+    cr.translate(x + width / 2.0, y + height / 2.0)
+
+    line_width = cr.get_line_width()
+
+    cr.scale(width / 2.0, height / 2.0)
+    cr.arc(0, 0, 1.0, 0, 2*math.pi)
+    cr.close_path()
+
+    # Restore old matrix (without removing the current path)
+    cr.restore()
+
+
+
+def generate_pdf():
+
+    global counter
+    global survey
+
+    questionnaire = survey.questionnaire
+    sheet = survey.sheet
+
+    # Use the questionnaire ID, if not there, give up?
+    if sheet.questionnaire_id is not None:
+        pdf_name = 'overlay-%s.pdf' % sheet.questionnaire_id
+    else:
+        pdf_name = 'overlay-%04i.pdf' % counter
+        counter += 1
+
+    surface = cairo.PDFSurface(pdf_name, survey.defs.paper_width * 72 / 25.4, survey.defs.paper_height * 72 / 25.4)
+    cr = cairo.Context(surface)
+
+    # Transform to mm space
+    cr.scale(72 / 25.4, 72 / 25.4)
+
+    for p in xrange(questionnaire.page_count):
+        # 1 based page numbers
+        p += 1
+
+        # Get the current image
+        img = sheet.get_page_image(p)
+        if img is None:
+            print("no image")
+            cr.show_page()
+            continue
+
+        # Render the image on the background, we take the easy way and paint it
+        # correctly. i.e. so that the markings on top are at the expected
+        # positions. It would likely be faster to only scale the image instead.
+        # (And not much harder, just needs the correct transformation for the
+        # other markings)
+        cr.save()
+        cr.transform(img.matrix.px_to_mm())
+        cr.set_source_rgb(0, 0, 0)
+        cr.mask_surface(img.surface.load_uncached(), 0, 0)
+        cr.restore()
+
+        # Now render all checked checkboxes, for now inline here.
+        for qobj in questionnaire.qobjects:
+            for box in qobj.boxes:
+                if box.data.state:
+                    if box.page_number != p:
+                        continue
+
+                    if not isinstance(box, model.questionnaire.Checkbox):
+                        continue
+
+                    # 1pt line width
+                    cr.set_line_width(1 / 25.4)
+
+                    x, y, width, height = box.data.x, box.data.y, box.data.width, box.data.height
+                    x -= width / math.sqrt(2) / 2
+                    y -= height / math.sqrt(2) / 2
+                    width += width / math.sqrt(2)
+                    height += height / math.sqrt(2)
+
+                    ellipse(cr, x, y, width, height)
+                    cr.set_source_rgb(1, 0, 0)
+                    cr.stroke()
+
+        cr.show_page()
+
+    del cr
+    surface.flush()
+    del surface
+
+survey.iterate_progressbar(generate_pdf)
+
diff --git a/custom-scripts/sdaps-recognize.py b/custom-scripts/sdaps-recognize.py
new file mode 100755
index 0000000..ccf9539
--- /dev/null
+++ b/custom-scripts/sdaps-recognize.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python2
+# -*- coding: utf8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright (C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright (C) 2012, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import sys
+import os
+
+# Use the following and local_run=True below to run without installing SDAPS
+#sys.path.append(os.path.join(os.path.dirname(sys.argv[0]), '..'))
+
+import sdaps
+#sdaps.init(local_run=True)
+sdaps.init()
+
+from sdaps import model
+from sdaps import image
+
+# Assume the first argument is a survey
+survey = model.survey.Survey.load(sys.argv[1])
+
+# We need the recognize buddies, as they are able to identify the data
+from sdaps.recognize import buddies
+
+# A sheet object to attach the images to
+sheet = model.sheet.Sheet()
+survey.add_sheet(sheet)
+
+images = []
+
+for file in sys.argv[2:]:
+    num_pages = image.get_tiff_page_count(file)
+    for page in xrange(num_pages):
+        images.append((file, page))
+
+if len(images) == 0:
+    # No images, simply exit again.
+    sys.exit(1)
+
+
+def add_image(survey, tiff, page):
+    img = model.sheet.Image()
+    survey.sheet.add_image(img)
+    # SDAPS assumes a relative path from the survey directory
+    img.filename = os.path.relpath(os.path.abspath(tiff), survey.survey_dir)
+    img.orig_name = tiff
+    img.tiff_page = page
+
+while images:
+    # Simply drop the list of images again.
+    sheet.images = []
+
+    add_image(survey, *images.pop(0))
+
+    if survey.defs.duplex:
+        add_image(survey, *images.pop(0))
+    else:
+        add_image(survey, 'DUMMY', -1)
+
+    # Run the recognition algorithm over the given images
+    survey.questionnaire.recognize.recognize()
+
+    for qobject in survey.questionnaire.qobjects:
+        if isinstance(qobject, model.questionnaire.Question):
+            # Only print data if an image for the page has been loaded
+            if survey.sheet.get_page_image(qobject.page_number) is None:
+                continue
+            for box in qobject.boxes:
+                print "%s,%s,%s,%s,%i,%f" % (
+                    survey.sheet.global_id,
+                    survey.sheet.survey_id,
+                    survey.sheet.questionnaire_id,
+                    '_'.join([str(num) for num in box.id]),
+                    int(box.data.state),
+                    float(box.data.quality))
+    print
+
+# And, we simply quit, ie. we don't save the survey
+
diff --git a/examples/example.tex b/examples/example.tex
new file mode 100644
index 0000000..a3be02b
--- /dev/null
+++ b/examples/example.tex
@@ -0,0 +1,157 @@
+\documentclass[
+  % Babel language, also used to load translations
+  english,
+  % Use A4 paper size, you can change this to eg. letterpaper if you need
+  % the letter format. The normal methods to modify the paper size should
+  % be picked up by SDAPS automatically.
+  % a4paper, % setting this might break the example scan unfortunately
+  % letterpaper
+  %
+  % If you need it, you can add a custom barcode at the center
+  %globalid=SDAPS,
+  %
+  % And the following adds a per sheet barcode at the bottom left
+  %print_questionnaire_id,
+  %
+  % You can choose between twoside and oneside. twoside is the default, and
+  % requires the document to be printed and scanned in duplex mode.
+  %oneside,
+  %
+  % With SDAPS 1.1.6 and newer you can choose the mode used when recognizing
+  % checkboxes. valid modes are "checkcorrect" (default), "check" and
+  % "fill".
+  %checkmode=checkcorrect,
+  %
+  % The following options make sense so that we can get a better feel for the
+  % final look.
+  pagemark,
+  stamp]{sdaps}
+\usepackage[utf8]{inputenc}
+% For demonstration purposes
+\usepackage{multicol}
+
+\author{The Author}
+\title{The Title}
+
+\begin{document}
+  % Everything you do should be done inside the questionnaire environment.
+
+  % If you don't like the default text at the beginning of each questionnaire
+  % you can remove it with the optional [noinfo] parameter for the environment 
+  \begin{questionnaire}
+    % There is a predefined "info" style to hilight some text.
+    \begin{info}
+      Some information here. Nothing special, just adds a line above/below.
+    \end{info}
+
+    % Use \addinfo to add metadata (which is printed on the report later on)
+    \addinfo{Date}{10.03.2013}
+
+    % You can structure the document using sections. You should not use
+    % subsections yourself, as these are used to typeset question text.
+    \section{Range Questions}
+
+    % Lets ask some questions.
+    % \singlemark creates a single range (1-5) question.
+    \singlemark{How often do you use SDAPS?}{never}{daily}
+
+    % Now we would like to ask multiple range questions that are similar. We
+    % can use a markgroup environment to typeset many range questions under
+    % one heading.
+    \begin{markgroup}{What do you think about the following aspects of \LaTeX?}
+      \markline{equation syntax}{bad}{good}
+      \markline{rendered equations}{ugly}{beautiful}
+      \markline{ease of use}{hard}{easy}
+    \end{markgroup}
+
+    \section{Choice Questions}
+    We can also give users a question with predefined choices. Such a list
+    of choices is typesetted using a tabularx environment with equally
+    sized columns. Items can span multiple columns.
+
+    \begin{choicequestion}[3]{Which of the following Open Source
+                              Optical Mark Recognition software
+                              packages have you heard about?}
+      \choiceitem{SDAPS}
+      \choicemulticolitem{2}{Auto Multiple Choice}
+      \choiceitem{QueXF}
+
+      % Insert a text field. The freeform box automatically scales horizontally
+      % The first parameter is the height of the box. The second parameter
+      % is the amount of columns it should span.
+      \choiceitemtext{1.2cm}{2}{Other:}
+    \end{choicequestion}
+
+    % And a more compact way of doing it; similar to markgroup
+    \begin{choicegroup}{Which software do you prefere for the following tasks?}
+      % We have to add the possible choices at the start.
+      \groupaddchoice{\LaTeX}
+      \groupaddchoice{LibreOffice}
+      \groupaddchoice{Microsoft Word}
+      \groupaddchoice{other}
+
+      % After that it is possible to add each question.
+      \choiceline{writing letters}
+      \choiceline{creating tables}
+      \choiceline{typesetting equations}
+    \end{choicegroup}
+
+    \section{Freeform text fields}
+
+    SDAPS will extract freeform textfields such as below as images and put
+    these into reports. SDAPS knows whether there is writing in the box and
+    how large it is.
+
+    % This is a textbox which is at least 2cm high. It will automatically scale
+    % to fill the page.
+    \textbox{2cm}{Do you have any comments?}
+
+    % Force a new page here
+    \newpage
+    \section{Tricks and Features}
+    SDAPS can also use circular checkboxes if you prefere. Or you can use the
+    {\ttfamily multicol} package to create multi-column layouts as is done below.
+
+    % Set checkbox style to be circular
+    \def\checkboxstyle{ellipse}
+
+    \begin{multicols}{2}
+      \singlemark{This is a range question}{lower bound}{upper bound}%
+      % Note that we need the % at the end of the last line to prevent
+      % LaTeX from inserting too much whitespace.
+      \label{somelabel}
+
+      As you can see, this is a multi-column layout. The {\ttfamily markgroup} and
+      {\ttfamily choicegroup} environments may be a bit tight in this mode.
+
+      Lets put some more questions here, just because we can.
+
+      \begin{choicequestion}[1]{A choice question!}
+        \choiceitem{first choice}
+        \choiceitem{second choice}
+        \choiceitem{third choice}
+        \choiceitemtext{1.2cm}{1}{other:}
+      \end{choicequestion}
+
+      \singlemark{Another range question}{lower bound}{upper bound}
+      This text is closer to the question compared to question~\ref{somelabel}
+      because it is not starting a new paragraph.
+
+
+      \textbox{3cm}{And a freeform text field}
+    \end{multicols}
+
+    That's it for the multi-column part; it was fun while it lasted!
+
+    There are some more special commands. You can draw \checkedbox{} crossed
+    checkboxes, \filledbox{} filled or \correctedbox{} filled and crossed ones. Finally there is
+    also the plain \checkbox*{} checkbox using {\ttfamily \textbackslash{}checkbox*}.
+
+    \textbox*{2cm}{And textboxes with a fixed height. This one is exactly 2\,cm high.}
+
+    % Reset checkbox style again.
+    \def\checkboxstyle{box}
+
+  \end{questionnaire}
+\end{document}
+
diff --git a/examples/example.tif b/examples/example.tif
new file mode 100644
index 0000000..8660d2f
Binary files /dev/null and b/examples/example.tif differ
diff --git a/po/POTFILES.in b/po/POTFILES.in
new file mode 100644
index 0000000..b271103
--- /dev/null
+++ b/po/POTFILES.in
@@ -0,0 +1,45 @@
+sdaps/__init__.py
+sdaps/script.py
+sdaps/add/__init__.py
+sdaps/annotate/__init__.py
+sdaps/boxgallery/__init__.py
+sdaps/log.py
+sdaps/cmdline/add.py
+sdaps/cmdline/annotate.py
+sdaps/cmdline/boxgallery.py
+sdaps/cmdline/convert.py
+sdaps/cmdline/cover.py
+sdaps/cmdline/csvdata.py
+sdaps/cmdline/gui.py
+sdaps/cmdline/ids.py
+sdaps/cmdline/info.py
+sdaps/cmdline/recognize.py
+sdaps/cmdline/reorder.py
+sdaps/cmdline/report.py
+sdaps/cmdline/reporttex.py
+sdaps/cmdline/setup.py
+sdaps/cmdline/setuptex.py
+sdaps/cmdline/stamp.py
+sdaps/convert/__init__.py
+sdaps/cover/__init__.py
+sdaps/csvdata/__init__.py
+sdaps/gui/__init__.py
+[type: gettext/glade] sdaps/gui/main_window.ui
+sdaps/gui/widget_buddies.py
+sdaps/image/__init__.py
+sdaps/utils/opencv.py
+sdaps/model/survey.py
+sdaps/recognize/buddies.py
+sdaps/recognize/__init__.py
+sdaps/reorder/__init__.py
+sdaps/report/answers.py
+sdaps/report/__init__.py
+sdaps/setup/__init__.py
+sdaps/setup/buddies.py
+sdaps/setupodt/__init__.py
+sdaps/setupodt/boxesparser.py
+sdaps/stamp/__init__.py
+sdaps/stamp/generic.py
+sdaps/stamp/latex.py
+sdaps/setuptex/__init__.py
+sdaps/reporttex/__init__.py
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
new file mode 100644
index 0000000..dc4c79d
--- /dev/null
+++ b/po/POTFILES.skip
@@ -0,0 +1,2 @@
+build
+tex
diff --git a/po/ar.po b/po/ar.po
new file mode 100644
index 0000000..f9238df
--- /dev/null
+++ b/po/ar.po
@@ -0,0 +1,1097 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-03-13 19:45+0100\n"
+"PO-Revision-Date: 2015-07-01 10:51+0200\n"
+"Last-Translator: Hatim Alahmadi <mandrivagnome at gmail.com>\n"
+"Language-Team: Arabic <https://hosted.weblate.org/projects/sdaps/master/ar/>"
+"\n"
+"Language: ar\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
+"&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n"
+"X-Generator: Weblate 2.4-dev\n"
+
+#: ../sdaps/script.py:41
+msgid "SDAPS -- Paper based survey tool."
+msgstr "SDAPS -- اداة ورقة الاستفتاءات الاساسية."
+
+#: ../sdaps/script.py:45
+msgid "project directory|The SDAPS project."
+msgstr "مسار المشروع|مشروع سدابس."
+
+#: ../sdaps/script.py:46
+msgid "command list|Commands:"
+msgstr "قائمة الاوامر|الاوامر:"
+
+#: ../sdaps/add/__init__.py:55
+#, python-format
+msgid ""
+"Invalid input file %s. You need to specify a (multipage) monochrome TIFF as "
+"input."
+msgstr ""
+
+#: ../sdaps/add/__init__.py:67
+#, python-format
+msgid ""
+"Not adding %s because it has a wrong page count (needs to be a mulitple of "
+"%i)."
+msgstr ""
+
+#: ../sdaps/boxgallery/__init__.py:108
+#, python-format
+msgid "Rendering boxgallery for metric \"%s\"."
+msgstr ""
+
+#: ../sdaps/log.py:37
+msgid "Warning: "
+msgstr "تحذير: "
+
+#: ../sdaps/log.py:41
+msgid "Error: "
+msgstr "خطأ: "
+
+#: ../sdaps/cmdline/add.py:31
+msgid "Add scanned questionnaires to the survey."
+msgstr "إضافة الاسئلة الممسوحة للاستفتاء."
+
+#: ../sdaps/cmdline/add.py:32
+msgid ""
+"This command is used to add scanned images to the survey.\n"
+"    The image data needs to be a (multipage) 300dpi monochrome TIFF file. "
+"You\n"
+"    may choose not to copy the data into the project directory. In that "
+"case\n"
+"    the data will be referenced using a relative path."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:38
+msgid "Convert given files and add the result."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:43 ../sdaps/cmdline/convert.py:39
+msgid ""
+"Do a 3D-transformation after finding the corner marks. If the\n"
+"        corner marks are not found then the image will be added as-is."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:49
+msgid ""
+"Force adding the images even if the page count is wrong (only use if you "
+"know what you are doing)."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:53
+msgid "Copy the files into the directory (default)."
+msgstr "نسخ الملفات إلى المسار (الافتراضي)."
+
+#: ../sdaps/cmdline/add.py:58
+msgid "Do not copy the files into the directory."
+msgstr "لا تنسخ الملفات إلى المسار."
+
+#: ../sdaps/cmdline/add.py:62
+msgid ""
+"Images contain a duplex scan of a simplex questionnaire (default: simplex "
+"scan)."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:68 ../sdaps/cmdline/convert.py:49
+msgid "A number of TIFF image files."
+msgstr "عدد ملفات صور TIFF ."
+
+#: ../sdaps/cmdline/add.py:94
+msgid "The --no-copy option is not compatible with --convert!"
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:103
+msgid "Converting input files into a single temporary file."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:115
+msgid ""
+"The page count of the created temporary file does not work with this survey."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:120
+msgid "Running the conversion failed."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:125
+#, python-format
+msgid "Processing %s"
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:129
+msgid "Done"
+msgstr "تم"
+
+#: ../sdaps/cmdline/annotate.py:28
+msgid "Annotate the questionnaire and show the recognized positions."
+msgstr ""
+
+#: ../sdaps/cmdline/annotate.py:29
+msgid ""
+"This command is mainly a debug utility. It creates an\n"
+"    annotated version of the questionnaire, with the information that SDAPS\n"
+"    knows about it overlayed on top."
+msgstr ""
+
+#: ../sdaps/cmdline/boxgallery.py:31
+msgid "Create PDFs with boxes sorted by the detection heuristics."
+msgstr ""
+
+#: ../sdaps/cmdline/boxgallery.py:32
+msgid ""
+"SDAPS uses multiple heuristics to detect determine the\n"
+"    state of checkboxes. There is a list for each heuristic giving the "
+"expected\n"
+"    state and the quality of the value (see defs.py). Using this command a "
+"PDF\n"
+"    will be created for each of the heuristics so that one can adjust the\n"
+"    values."
+msgstr ""
+
+#: ../sdaps/cmdline/boxgallery.py:40
+msgid ""
+"Reruns part of the recognition process and retrieves debug images from this "
+"step."
+msgstr ""
+
+#: ../sdaps/cmdline/convert.py:30
+msgid "Convert a set of images to the correct image format."
+msgstr ""
+
+#: ../sdaps/cmdline/convert.py:31
+msgid ""
+"This command can be used if you scanned files in a different\n"
+"        mode than the expected monochrome TIFF file. All the given files "
+"will\n"
+"        be loaded, converted to monochrome and stored into a multipage 1bpp\n"
+"        TIFF file. Optionally you can select that a 3D transformation "
+"should\n"
+"        be performed, using this it may be possible to work with photos of\n"
+"        questionnaires instead of scans."
+msgstr ""
+
+#: ../sdaps/cmdline/convert.py:45
+msgid "The location of the output file. Must be given."
+msgstr ""
+
+#: ../sdaps/cmdline/convert.py:58
+msgid "No output filename specified!"
+msgstr ""
+
+#: ../sdaps/cmdline/cover.py:27
+msgid "Create a cover for the questionnaires."
+msgstr "انشاء غلاف للاسئلة."
+
+#: ../sdaps/cmdline/cover.py:28
+msgid ""
+"This command creates a cover page for questionnaires. All\n"
+"    the metadata of the survey will be printed on the page."
+msgstr ""
+
+#: ../sdaps/cmdline/cover.py:31
+#, python-format
+msgid "Filename to store the data to (default: cover_%%i.pdf)"
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:34
+msgid "Import or export data to/from CSV files."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:35
+msgid ""
+"Import or export data to/from a CSV file. The first line\n"
+"    is a header which defines questionnaire_id and global_id, and a column\n"
+"    for each checkbox and textfield. Note that the import is currently very\n"
+"    limited, as you need to specifiy the questionnaire ID to select the "
+"sheet\n"
+"    which should be updated."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:44
+msgid "Export data to CSV file."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:46
+#, python-format
+msgid "Filename to store the data to (default: data_%%i.csv)"
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:48
+msgid "The delimiter used in the CSV file (default ',')"
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:52 ../sdaps/cmdline/report.py:31
+#: ../sdaps/cmdline/reporttex.py:52
+msgid "Filter to only export a partial dataset."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:54
+msgid "Export images of freeform fields."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:60
+#, fuzzy
+msgid "Export an image for each question that includes all boxes."
+msgstr "تصدير واستيراد معرفات الاسئلة."
+
+#: ../sdaps/cmdline/csvdata.py:66
+msgid "Export the recognition quality for each checkbox."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:74
+msgid "Import data to from a CSV file."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:76
+msgid "The file to import."
+msgstr ""
+
+#: ../sdaps/cmdline/gui.py:28
+msgid "Launch a gui. You can view and alter the (recognized) answers with it."
+msgstr ""
+
+#: ../sdaps/cmdline/gui.py:29
+msgid ""
+"This command launches a graphical user interface that can\n"
+"    be used to correct answer. You need to run \"recognize\" before using "
+"it.\n"
+"    "
+msgstr ""
+
+#: ../sdaps/cmdline/gui.py:34
+msgid "Filter to only show a partial dataset."
+msgstr ""
+
+#: ../sdaps/cmdline/ids.py:29
+msgid "Export and import questionnaire IDs."
+msgstr "تصدير واستيراد معرفات الاسئلة."
+
+#: ../sdaps/cmdline/ids.py:30
+msgid ""
+"This command can be used to import and export questionnaire\n"
+"    IDs. It only makes sense in projects where such an ID is printed on the\n"
+"    questionnaire. Note that you can also add IDs by using the stamp "
+"command,\n"
+"    which will give you the PDF at the same time."
+msgstr ""
+
+#: ../sdaps/cmdline/ids.py:35
+#, python-format
+msgid "Filename to store the data to (default: ids_%%i)"
+msgstr ""
+
+#: ../sdaps/cmdline/ids.py:38
+msgid "Add IDs to the internal list from the specified file."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:28
+msgid "Display and modify metadata of project."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:29
+msgid ""
+"This command lets you modify the metadata of the SDAPS\n"
+"    project. You can modify, add and remove arbitrary keys that will be "
+"printed\n"
+"    on the report. The only key that always exist is \"title\".\n"
+"    If no key is given then a list of defined keys is printed."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:36
+msgid "The key to display or modify."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:40
+msgid "Set the given key to this value."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:44
+msgid "Delete the key and value pair."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:68
+msgid "Existing fields:\n"
+msgstr ""
+
+#: ../sdaps/cmdline/recognize.py:28
+msgid "Run the optical mark recognition."
+msgstr ""
+
+#: ../sdaps/cmdline/recognize.py:29
+msgid ""
+"Iterates over all images and runs the optical mark\n"
+"    recognition. It will reevaluate sheets even if \"recognize\" has "
+"already\n"
+"    run or manual changes were made."
+msgstr ""
+
+#: ../sdaps/cmdline/recognize.py:34
+msgid ""
+"Only identify the page properties, but don't recognize the checkbox states."
+msgstr ""
+
+#: ../sdaps/cmdline/recognize.py:39
+msgid ""
+"Rerun the recognition for all pages. The default is to skip all pages that "
+"were recognized or verified already."
+msgstr ""
+
+#: ../sdaps/cmdline/reorder.py:26
+#, fuzzy
+msgid "Reorder pages according to questionnaire ID."
+msgstr "تصدير واستيراد معرفات الاسئلة."
+
+#: ../sdaps/cmdline/reorder.py:27
+msgid ""
+"This command reorders all pages according to the already\n"
+"    recognized questionnaire ID. To use it add all the files to the "
+"project,\n"
+"    then run a partial recognition using \"recognize --identify\". After "
+"this\n"
+"    you have to run this command to reorder the data for the real "
+"recognition.\n"
+"    "
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:26
+msgid "Create a PDF report."
+msgstr "انشاء تقرير اكروبات."
+
+#: ../sdaps/cmdline/report.py:27
+msgid ""
+"This command creates a PDF report using reportlab that\n"
+"    contains statistics and if selected the freeform fields."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:33
+msgid "Create a filtered report for every checkbox."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:36
+msgid "Short format (without freeform text fields)."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:41
+msgid "Detailed output. (default)"
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:47 ../sdaps/cmdline/reporttex.py:30
+msgid ""
+"Do not include original images in the report. This is useful if there are "
+"privacy concerns."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:52 ../sdaps/cmdline/reporttex.py:35
+msgid "Do not use substitutions instead of images."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:58 ../sdaps/cmdline/reporttex.py:46
+msgid "The paper size used for the output (default: locale dependent)"
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:61 ../sdaps/cmdline/reporttex.py:49
+#, python-format
+msgid "Filename to store the data to (default: report_%%i.pdf)"
+msgstr ""
+
+#: ../sdaps/cmdline/reporttex.py:26
+msgid "Create a PDF report using LaTeX."
+msgstr "انشاء تقرير أكروبات باستخدام لتيك."
+
+#: ../sdaps/cmdline/reporttex.py:27
+msgid ""
+"This command creates a PDF report using LaTeX that\n"
+"    contains statistics and freeform fields."
+msgstr ""
+
+#: ../sdaps/cmdline/reporttex.py:41
+msgid "Save the generated TeX files instead of the final PDF."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:28
+msgid "Create a new survey using an ODT document."
+msgstr "انشاء استفتاء جديد باستخدام مستند odt ."
+
+#: ../sdaps/cmdline/setup.py:29
+msgid ""
+"Create a new surevey from an ODT document. The PDF export\n"
+"    of the file needs to be specified at the same time. SDAPS will import\n"
+"    metadata (properties) and the title from the ODT document."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:34
+msgid "The ODT Document"
+msgstr "مستند odt"
+
+#: ../sdaps/cmdline/setup.py:36
+msgid "PDF export of the ODT document"
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:39 ../sdaps/cmdline/setuptex.py:39
+msgid "Additional questions that are not part of the questionnaire."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:48
+msgid "Enable printing of the survey ID (default)."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:52
+msgid "Disable printing of the survey ID."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:57
+msgid "Enable printing of the questionnaire ID."
+msgstr "تفعيل طباعة معرفات الاسئلة."
+
+#: ../sdaps/cmdline/setup.py:61
+msgid "Disable printing of the questionnaire ID (default)."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:65
+msgid ""
+"Set an additional global ID for tracking. This can can only be used in the "
+"\"code128\" style."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:70
+msgid ""
+"The stamping style to use. Should be either \"classic\" or \"code128\". Use "
+"\"code128\" for more features."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:76
+msgid "The mode to use when detecting checkboxes."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:82
+msgid ""
+"Use duplex mode (ie. only print the IDs on the back side). Requires a "
+"mulitple of two pages."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:87
+msgid ""
+"Use simplex mode. IDs are printed on each page. You need a simplex scan "
+"currently!"
+msgstr ""
+
+#: ../sdaps/cmdline/setuptex.py:27
+msgid "Create a new survey using a LaTeX document."
+msgstr "انشاء استفتاء جديد باستخدام مستند لتيك."
+
+#: ../sdaps/cmdline/setuptex.py:28
+msgid ""
+"Create a new survey from a LaTeX document. You need to\n"
+"    be using the SDAPS class. All the metadata and options for the project\n"
+"    can be set inside the LaTeX document."
+msgstr ""
+
+#: ../sdaps/cmdline/setuptex.py:33
+msgid "The LaTeX Document"
+msgstr "مستند لتيك"
+
+#: ../sdaps/cmdline/setuptex.py:35
+msgid ""
+"Additional files that are required by the LaTeX document and need to be "
+"copied into the project directory."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:26
+msgid "Add marks for automatic processing."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:27
+msgid ""
+"This command creates the printable document. Depending on\n"
+"    the projects setting you are required to specifiy a source for "
+"questionnaire\n"
+"    IDs."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:33
+msgid ""
+"If using questionnaire IDs, create N questionnaires with randomized IDs."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:36
+msgid ""
+"If using questionnaire IDs, create questionnaires from the IDs read from the "
+"specified file."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:39
+msgid "If using questionnaire IDs, create questionnaires for all stored IDs."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:42
+#, python-format
+msgid "Filename to store the data to (default: stamp_%%i.pdf)"
+msgstr ""
+
+#: ../sdaps/convert/__init__.py:35
+#, python-format
+msgid "Could not apply 3D-transformation to image '%s', page %i!"
+msgstr ""
+
+#: ../sdaps/cover/__init__.py:40
+msgid "sdaps questionnaire"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:57
+msgid ""
+"The survey does not have any images! Please add images (and run recognize) "
+"before using the GUI."
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:202
+msgid "Page|Invalid"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:205
+#, python-format
+msgid "Page %i"
+msgid_plural "Page %i"
+msgstr[0] "صفحة %i"
+msgstr[1] "صفحة %i"
+msgstr[2] "صفحة %i"
+msgstr[3] "صفحة %i"
+msgstr[4] "صفحة %i"
+msgstr[5] "صفحة %i"
+
+#: ../sdaps/gui/__init__.py:247
+msgid "Copyright © 2007-2014 The SDAPS Authors"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:249
+msgid "Scripts for data acquisition with paper based surveys"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:250
+msgid "http://sdaps.org"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:251
+msgid "translator-credits"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:296
+#, python-format
+msgid " of %i"
+msgstr " من %i"
+
+#: ../sdaps/gui/__init__.py:297
+#, python-format
+msgid "Recognition Quality: %.2f"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:313
+msgid ""
+"You have reached the first page of the survey. Would you like to go to the "
+"last page?"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:315
+msgid "Go to last page"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:337
+msgid ""
+"You have reached the last page of the survey. Would you like to go to the "
+"first page?"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:339
+msgid "Go to first page"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:447
+msgid "Close without saving"
+msgstr "إغلاق بدون حفظ"
+
+#: ../sdaps/gui/__init__.py:451
+msgid ""
+"<b>Save the project before closing?</b>\n"
+"\n"
+"If you do not save you may loose data."
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:1
+msgid "Forward"
+msgstr "التالي"
+
+#: ../sdaps/gui/main_window.ui.h:2
+msgid "Previous"
+msgstr "السابق"
+
+#: ../sdaps/gui/main_window.ui.h:3
+msgid "Zoom In"
+msgstr "تكبير"
+
+#: ../sdaps/gui/main_window.ui.h:4
+msgid "Zoom Out"
+msgstr "تصغير"
+
+#: ../sdaps/gui/main_window.ui.h:5
+msgid "SDAPS"
+msgstr "سدابس"
+
+#: ../sdaps/gui/main_window.ui.h:6
+msgid "_File"
+msgstr "_ملف"
+
+#: ../sdaps/gui/main_window.ui.h:7
+msgid "_View"
+msgstr "_عرض"
+
+#: ../sdaps/gui/main_window.ui.h:8
+msgid "_Help"
+msgstr "_مساعدة"
+
+#: ../sdaps/gui/main_window.ui.h:9
+msgid "Page Rotated"
+msgstr "دورت الصفحة"
+
+#: ../sdaps/gui/main_window.ui.h:10
+msgid "Sort by Quality"
+msgstr "فرز حسب الجودة"
+
+#: ../sdaps/gui/main_window.ui.h:11
+msgid "label"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:56
+msgid "<b>Global Properties</b>"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:60
+msgid "Sheet valid"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:61
+msgid "Verified"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:62
+msgid "Empty"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:74 ../sdaps/gui/widget_buddies.py:98
+msgid "<b>Questionnaire ID: </b>"
+msgstr "<b>معرف السؤال: </b>"
+
+#: ../sdaps/image/__init__.py:47
+msgid ""
+"It appears you have not build the C extension. Please run \"./setup.py build"
+"\" in the toplevel directory."
+msgstr ""
+
+#: ../sdaps/utils/opencv.py:31
+msgid "Cannot convert PDF files as poppler is not installed or usable!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:253
+#, python-format
+msgid "%i sheet"
+msgid_plural "%i sheets"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/model/survey.py:264
+#, python-format
+msgid "%f seconds per sheet"
+msgstr ""
+
+#: ../sdaps/model/survey.py:290
+msgid ""
+"A questionnaire that is printed in duplex needs an even amount of pages!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:294
+msgid ""
+"The 'classic' style only supports a maximum of six pages! Use the 'code128' "
+"style if you require more pages."
+msgstr ""
+
+#: ../sdaps/model/survey.py:307
+msgid "IDs need to be integers in \"classic\" style!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:313
+#, python-format
+msgid "Invalid character %s in questionnaire ID \"%s\" in \"code128\" style!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:317
+msgid ""
+"SDAPS cannot draw a questionnaire ID with the \"custom\" style. Do this "
+"yourself somehow!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:337
+#, python-format
+msgid "Running upgrade routines for file format version %i"
+msgstr ""
+
+#. in simplex mode every page will have a matrix; it might be a None
+#. matrix though
+#: ../sdaps/recognize/buddies.py:72
+#, python-format
+msgid "%s, %i: Matrix not recognized."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:81
+#, python-format
+msgid "%s, %i: Rotation not found."
+msgstr ""
+
+#. Copy the rotation over (if required) and print warning if the rotation is unknown
+#: ../sdaps/recognize/buddies.py:85
+#, python-format
+msgid "Neither %s, %i or %s, %i has a known rotation!"
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:97
+#, python-format
+msgid "%s, %i: Matrix not recognized (again)."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:111
+#, python-format
+msgid "%s, %i: Could not get page number."
+msgstr ""
+
+#. Whoa, that should not happen.
+#: ../sdaps/recognize/buddies.py:131
+#, python-format
+msgid "Neither %s, %i or %s, %i has a known page number!"
+msgstr ""
+
+#. We don't touch the ignore flag in this case
+#. Simply print a message as this should *never* happen
+#: ../sdaps/recognize/buddies.py:142
+#, python-format
+msgid ""
+"Got a simplex document where two adjacent pages had a known page number. "
+"This should never happen as even simplex scans are converted to duplex by "
+"inserting dummy pages. Maybe you did a simplex scan but added it in duplex "
+"mode? The pages in question are %s, %i and %s, %i."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:159
+#, python-format
+msgid "Images %s, %i and %s, %i do not have consecutive page numbers!"
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:175
+#, python-format
+msgid "No page number for page %s, %i exists."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:180
+#, python-format
+msgid "Page number for page %s, %i already used by another image."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:186
+#, python-format
+msgid "Page number %i for page %s, %i is out of range."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:203
+#, python-format
+msgid "%s, %i: Could not read survey ID, but should be able to."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:207
+#, python-format
+msgid "Could not read survey ID of either %s, %i or %s, %i!"
+msgstr ""
+
+#. Broken survey ID ...
+#: ../sdaps/recognize/buddies.py:214
+#, python-format
+msgid "Got a wrong survey ID (%s, %i)! It is %s, but should be %i."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:236
+#, python-format
+msgid "%s, %i: Could not read questionnaire ID, but should be able to."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:240
+#, python-format
+msgid "Could not read questionnaire ID of either %s, %i or %s, %i!"
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:267
+msgid ""
+"Got different IDs on different pages for at least one sheet! Do *NOT* try to "
+"use filters with this survey! You have to run a \"reorder\" step for this to "
+"work properly!"
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:323
+msgid "No style buddy loaded. This needs to be done for the \"custom\" style!"
+msgstr ""
+
+#: ../sdaps/report/answers.py:185
+#, python-format
+msgid "Answers: %i"
+msgstr "الاجابات: %i"
+
+#: ../sdaps/report/answers.py:187
+#, python-format
+msgid "Mean: %.2f"
+msgstr "المتوسط: %.2f"
+
+#: ../sdaps/report/answers.py:189
+#, python-format
+msgid "Standard Deviation: %.2f"
+msgstr ""
+
+#: ../sdaps/report/__init__.py:75 ../sdaps/reporttex/__init__.py:140
+msgid "Turned in Questionnaires"
+msgstr ""
+
+#: ../sdaps/report/__init__.py:92 ../sdaps/reporttex/__init__.py:139
+msgid "sdaps report"
+msgstr "تقرير سدابس"
+
+#: ../sdaps/setup/buddies.py:61
+#, python-format
+msgid "Head %(l0)i got no title."
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:74
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got no question."
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:120
+#, python-format
+msgid "Error in question \"%s\""
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:123
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got no boxes."
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:148
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got not exactly two answers."
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:170
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got not exactly one box."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:45 ../sdaps/setuptex/__init__.py:68
+msgid "The survey directory already exists."
+msgstr "مسار الاستفتاء موجود مسبقا."
+
+#: ../sdaps/setupodt/__init__.py:50
+#, python-format
+msgid ""
+"Unknown file type (%s). questionnaire_odt should be application/vnd.oasis."
+"opendocument.text."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:55
+#, python-format
+msgid "Unknown file type (%s). questionnaire_pdf should be application/pdf."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:61 ../sdaps/setuptex/__init__.py:79
+#, python-format
+msgid "Unknown file type (%s). additionalqobjects should be text/plain."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:90
+msgid "Caught an Exception while parsing the ODT file. The current state is:"
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:93
+msgid ""
+"If the dependencies for the \"annotate\" command are installed, then an "
+"annotated version will be created next to the original PDF file."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:123 ../sdaps/setuptex/__init__.py:147
+msgid ""
+"Some combination of options and project properties do not work. Aborted "
+"Setup."
+msgstr ""
+
+#: ../sdaps/setupodt/boxesparser.py:121 ../sdaps/setupodt/boxesparser.py:190
+#, python-format
+msgid ""
+"Warning: Ignoring a box (page: %i, x: %.1f, y: %.1f, width: %.1f, height: "
+"%.1f)."
+msgstr ""
+
+#: ../sdaps/stamp/__init__.py:38
+msgid ""
+"You may not specify the number of sheets for this survey. All questionnaires "
+"will be identical as the survey has been configured to not use questionnaire "
+"IDs for each sheet."
+msgstr ""
+
+#: ../sdaps/stamp/__init__.py:76
+msgid ""
+"This survey has been configured to use questionnaire IDs. Each questionnaire "
+"will be unique. You need to use on of the options to add new IDs or use the "
+"existing ones."
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:37
+#, python-format
+msgid "Survey-ID: %i"
+msgstr "معرف-الاستفتاء: %i"
+
+#: ../sdaps/stamp/generic.py:51
+#, python-format
+msgid "Questionnaire-ID: %i"
+msgstr "معرف-السؤال: %i"
+
+#: ../sdaps/stamp/generic.py:336
+msgid ""
+"You need to have either pdftk or pyPdf installed. pdftk is the faster method."
+msgstr ""
+
+#. bottomup = False =>(0, 0) is the upper left corner
+#: ../sdaps/stamp/generic.py:352
+#, python-format
+msgid "Creating stamp PDF for %i sheet"
+msgid_plural "Creating stamp PDF for %i sheets"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/stamp/generic.py:410 ../sdaps/stamp/generic.py:543
+#, python-format
+msgid "%i sheet; %f seconds per sheet"
+msgid_plural "%i sheet; %f seconds per sheet"
+msgstr[0] ""
+msgstr[1] ""
+
+#. Merge using pdftk
+#: ../sdaps/stamp/generic.py:419
+msgid "Stamping using pdftk"
+msgstr ""
+
+#. Shortcut if we only have one sheet.
+#. In this case form data in the PDF will *not* break, in
+#. the other code path it *will* break.
+#: ../sdaps/stamp/generic.py:426
+msgid "pdftk: Overlaying the original PDF with the markings."
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:435
+#, python-format
+msgid "pdftk: Splitting out page %d of each sheet."
+msgid_plural "pdftk: Splitting out page %d of each sheet."
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/stamp/generic.py:449
+msgid "pdftk: Splitting the questionnaire for watermarking."
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:460 ../sdaps/stamp/generic.py:469
+#, python-format
+msgid "pdftk: Watermarking page %d of all sheets."
+msgid_plural "pdftk: Watermarking page %d of all sheets."
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/stamp/generic.py:492
+msgid "pdftk: Assembling everything into the final PDF."
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:529
+msgid "Stamping using pyPdf. For faster stamping, install pdftk."
+msgstr ""
+
+#: ../sdaps/stamp/latex.py:21
+msgid ""
+"There should be no need to stamp a SDAPS Project that uses LaTeX and does "
+"not have different questionnaire IDs printed on each sheet.\n"
+"I am going to do so anyways."
+msgstr ""
+
+#: ../sdaps/stamp/latex.py:43
+#, python-format
+msgid "Running %s now twice to generate the stamped questionnaire."
+msgstr ""
+
+#: ../sdaps/stamp/latex.py:47 ../sdaps/setuptex/__init__.py:119
+#: ../sdaps/setuptex/__init__.py:158 ../sdaps/reporttex/__init__.py:161
+#, python-format
+msgid "Error running \"%s\" to compile the LaTeX file."
+msgstr ""
+
+#: ../sdaps/stamp/latex.py:53
+#, python-format
+msgid ""
+"An error occured during creation of the report. Temporary files left in '%s'."
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:73
+#, python-format
+msgid "Unknown file type (%s). questionnaire_tex should be of type text/x-tex."
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:74
+msgid "Will keep going, but expect failure!"
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:115 ../sdaps/setuptex/__init__.py:153
+#, python-format
+msgid "Running %s now twice to generate the questionnaire."
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:133
+msgid "Caught an Exception while parsing the SDAPS file. The current state is:"
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:174
+msgid ""
+"An error occured in the setup routine. The survey directory still exists. "
+"You can for example check the questionnaire.log file for LaTeX compile "
+"errors."
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:107
+msgid "author|Unknown"
+msgstr "المؤلف|غير معروف"
+
+#: ../sdaps/reporttex/__init__.py:138
+msgid "tex language|english"
+msgstr "لغة لتيك|الانجليزية"
+
+#: ../sdaps/reporttex/__init__.py:154
+#, python-format
+msgid "The TeX project with the report data is located at '%s'."
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:157
+#, python-format
+msgid "Running %s now twice to generate the report."
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:167
+#, python-format
+msgid "An occured during creation of the report. Temporary files left in '%s'."
+msgstr ""
diff --git a/po/de.po b/po/de.po
new file mode 100644
index 0000000..636e381
--- /dev/null
+++ b/po/de.po
@@ -0,0 +1,1293 @@
+#
+# Benjamin Berg <benjamin at sipsolutions.net>, 2011, 2012, 2013, 2014, 2015.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: SDAPS 0.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-03-13 19:45+0100\n"
+"PO-Revision-Date: 2015-03-13 19:46+0100\n"
+"Last-Translator: Benjamin Berg <benjamin at sipsolutions.net>\n"
+"Language-Team: German <>\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n!=1);\n"
+"X-Generator: Gtranslator 2.91.6\n"
+"X-Poedit-Language: German\n"
+"X-Poedit-Country: GERMANY\n"
+"X-Poedit-SourceCharset: utf-8\n"
+
+#: ../sdaps/script.py:41
+msgid "SDAPS -- Paper based survey tool."
+msgstr "SDAPS -- Werkzeug für Umfragen mit Papier-Fragebögen."
+
+#: ../sdaps/script.py:45
+msgid "project directory|The SDAPS project."
+msgstr "Das SDAPS Projekt."
+
+#: ../sdaps/script.py:46
+msgid "command list|Commands:"
+msgstr "Kommandos:"
+
+#: ../sdaps/add/__init__.py:55
+#, python-format
+msgid ""
+"Invalid input file %s. You need to specify a (multipage) monochrome TIFF as "
+"input."
+msgstr ""
+"Ungültige Eingabedatei %s. Ein (mehrseitiges) monochromes TIFF-Bild muss "
+"angegeben werden."
+
+#: ../sdaps/add/__init__.py:67
+#, python-format
+msgid ""
+"Not adding %s because it has a wrong page count (needs to be a mulitple of "
+"%i)."
+msgstr ""
+"Füge Bild %s nicht hinzu, da die Anzahl der Seiten falsch ist (muss ein "
+"Vielfaches von %i sein)."
+
+#: ../sdaps/boxgallery/__init__.py:108
+#, python-format
+msgid "Rendering boxgallery for metric \"%s\"."
+msgstr "Erstelle Überblick über die Boxen sortiert nach der Metrik \"%s\"."
+
+#: ../sdaps/log.py:37
+msgid "Warning: "
+msgstr "Warnung: "
+
+#: ../sdaps/log.py:41
+msgid "Error: "
+msgstr "Fehler: "
+
+#: ../sdaps/cmdline/add.py:31
+msgid "Add scanned questionnaires to the survey."
+msgstr "Hinzufügen von gescannten Fragebögen."
+
+#: ../sdaps/cmdline/add.py:32
+msgid ""
+"This command is used to add scanned images to the survey.\n"
+"    The image data needs to be a (multipage) 300dpi monochrome TIFF file. "
+"You\n"
+"    may choose not to copy the data into the project directory. In that "
+"case\n"
+"    the data will be referenced using a relative path."
+msgstr ""
+"Dieser Befehl wird benutzt um gescannte Bilder zu der Umfrage hinzuzufügen. "
+"Die Bilder müss als eine (mehrseitige) 300dpi monochromes TIFF Datei "
+"übergeben werden. Es ist möglich auszuwählen, dass die Datei nicht in das "
+"Projektverzeichniss kopiert wird. In diesem Fall wird ein zum "
+"Projektverzeichniss relativer Pfad gespeichert."
+
+#: ../sdaps/cmdline/add.py:38
+msgid "Convert given files and add the result."
+msgstr "Konvertiere die angegebenen Dateien und füge sie hinzu."
+
+#: ../sdaps/cmdline/add.py:43 ../sdaps/cmdline/convert.py:39
+msgid ""
+"Do a 3D-transformation after finding the corner marks. If the\n"
+"        corner marks are not found then the image will be added as-is."
+msgstr ""
+"Wende eine 3D-Transformation an, nachdem die Eckmarkierungen gefunden "
+"wurden. Falls keine Eckmarken gefunden wird das Bild unverändert hinzugefügt."
+
+#: ../sdaps/cmdline/add.py:49
+msgid ""
+"Force adding the images even if the page count is wrong (only use if you "
+"know what you are doing)."
+msgstr ""
+"Hinzufügen der Bilder forcieren, selbst wenn die Seitenzahl nicht stimmt "
+"(nur in einigen Fällen sinnvoll)."
+
+#: ../sdaps/cmdline/add.py:53
+msgid "Copy the files into the directory (default)."
+msgstr "Kopier das TIFF-Bild in das Projektverzeichniss (Standard)."
+
+#: ../sdaps/cmdline/add.py:58
+msgid "Do not copy the files into the directory."
+msgstr "Speichere einen relativen Pfad zum Bild."
+
+#: ../sdaps/cmdline/add.py:62
+msgid ""
+"Images contain a duplex scan of a simplex questionnaire (default: simplex "
+"scan)."
+msgstr ""
+"Die Bilder sind ein duplex Scan eines simplex Fragebogens (Standard: simplex "
+"Scan)."
+
+#: ../sdaps/cmdline/add.py:68 ../sdaps/cmdline/convert.py:49
+msgid "A number of TIFF image files."
+msgstr "Ein oder mehere TIFF Dateien."
+
+#: ../sdaps/cmdline/add.py:94
+msgid "The --no-copy option is not compatible with --convert!"
+msgstr ""
+"Die Optionen --no-copy und --convert können nicht gleichzeitig verwendet "
+"werden!"
+
+#: ../sdaps/cmdline/add.py:103
+msgid "Converting input files into a single temporary file."
+msgstr "Konvertiere die Eingabedateien in eine gemeinsame temporäre Datei."
+
+#: ../sdaps/cmdline/add.py:115
+msgid ""
+"The page count of the created temporary file does not work with this survey."
+msgstr ""
+"Die Seitenzahl der erstellten temporären Datei passt nicht zur Umfrage."
+
+#: ../sdaps/cmdline/add.py:120
+msgid "Running the conversion failed."
+msgstr "Die Konvertierung ist fehlgeschlagen."
+
+#: ../sdaps/cmdline/add.py:125
+#, python-format
+msgid "Processing %s"
+msgstr "Beartbeite %s"
+
+#: ../sdaps/cmdline/add.py:129
+msgid "Done"
+msgstr "Fertig"
+
+#: ../sdaps/cmdline/annotate.py:28
+msgid "Annotate the questionnaire and show the recognized positions."
+msgstr ""
+"Vermerkt den Fragebogen mit Anmerkungen zu den erkannten Positionen und "
+"Fragen."
+
+#: ../sdaps/cmdline/annotate.py:29
+msgid ""
+"This command is mainly a debug utility. It creates an\n"
+"    annotated version of the questionnaire, with the information that SDAPS\n"
+"    knows about it overlayed on top."
+msgstr ""
+"Dieser Befehl ist Hauptsächlich als Werkzeug zur Fehlerkorrektur gedacht. "
+"Es\n"
+"versieht den Fragebogen mit Anmerkungen. Damit ist es möglich die von SDAPS\n"
+"erkannten Informationen zu überprüfen."
+
+#: ../sdaps/cmdline/boxgallery.py:31
+msgid "Create PDFs with boxes sorted by the detection heuristics."
+msgstr "Erstellt ein PDF mit den Kästchen um die Heuristiken zu bewerten."
+
+#: ../sdaps/cmdline/boxgallery.py:32
+msgid ""
+"SDAPS uses multiple heuristics to detect determine the\n"
+"    state of checkboxes. There is a list for each heuristic giving the "
+"expected\n"
+"    state and the quality of the value (see defs.py). Using this command a "
+"PDF\n"
+"    will be created for each of the heuristics so that one can adjust the\n"
+"    values."
+msgstr ""
+"SDAPS benutzt mehrere Heuristiken um den Status der Ankreuzfelder zu "
+"erkennen. Diese\n"
+"werden über eine Liste (siehe defs.py) einem Zustand und einer Qualität "
+"zugeordner. Mit\n"
+"hilfe von diesem Kommando können PDFs erstellt werden um die Heuristiken zu "
+"bewerten und\n"
+"die Zuordnung anzupassen."
+
+#: ../sdaps/cmdline/boxgallery.py:40
+msgid ""
+"Reruns part of the recognition process and retrieves debug images from this "
+"step."
+msgstr ""
+"Führt den Erkenner noch einmal aus, und speichert zusätzliche Bilder zur "
+"Fehlersuche."
+
+#: ../sdaps/cmdline/convert.py:30
+msgid "Convert a set of images to the correct image format."
+msgstr "Konvertiert einen Satz von Bildern in das passende Format."
+
+#: ../sdaps/cmdline/convert.py:31
+msgid ""
+"This command can be used if you scanned files in a different\n"
+"        mode than the expected monochrome TIFF file. All the given files "
+"will\n"
+"        be loaded, converted to monochrome and stored into a multipage 1bpp\n"
+"        TIFF file. Optionally you can select that a 3D transformation "
+"should\n"
+"        be performed, using this it may be possible to work with photos of\n"
+"        questionnaires instead of scans."
+msgstr ""
+"Dieser Befehl kann angewndet werden, falls die Bilder nicht als monochromes "
+"TIFF gescannt wurden. Alle angegebenen Dateien werden geladen, konvertiert "
+"und als mehrseitiges TIFF mit einem Bit Farbtiefe gespeichert. Optional kann "
+"eine 3D-Transformation angewendet werden. Dadurch können auch Fotos anstelle "
+"von Scans verarbeitet werden."
+
+#: ../sdaps/cmdline/convert.py:45
+msgid "The location of the output file. Must be given."
+msgstr "Speicherort der Ausgabe. Pflichtparamter."
+
+#: ../sdaps/cmdline/convert.py:58
+msgid "No output filename specified!"
+msgstr "Ausgabedatei nicht angegeben!"
+
+#: ../sdaps/cmdline/cover.py:27
+msgid "Create a cover for the questionnaires."
+msgstr "Erstellt ein Deckblatt für die Fragebögen."
+
+#: ../sdaps/cmdline/cover.py:28
+msgid ""
+"This command creates a cover page for questionnaires. All\n"
+"    the metadata of the survey will be printed on the page."
+msgstr ""
+"Dieses Kommando erstellt ein Deckblatt für die Fragebögen. Dies ist\n"
+"eine Zusammenfassung der Metainformationen zu der Umfrage."
+
+#: ../sdaps/cmdline/cover.py:31
+#, python-format
+msgid "Filename to store the data to (default: cover_%%i.pdf)"
+msgstr "Ausgabedatei (Standard: cover_%%i.pdf)"
+
+#: ../sdaps/cmdline/csvdata.py:34
+msgid "Import or export data to/from CSV files."
+msgstr "Importieren oder exportieren von Daten im CSV Format."
+
+#: ../sdaps/cmdline/csvdata.py:35
+msgid ""
+"Import or export data to/from a CSV file. The first line\n"
+"    is a header which defines questionnaire_id and global_id, and a column\n"
+"    for each checkbox and textfield. Note that the import is currently very\n"
+"    limited, as you need to specifiy the questionnaire ID to select the "
+"sheet\n"
+"    which should be updated."
+msgstr ""
+"Dieser Befehl ermöglicht den Import und Export der Daten im CSV Format. "
+"Dabei\n"
+"ist die erste Zeile eine Kopfzeile, die angibt, welche Werte in der Spalte\n"
+"gespeichert werden. Der Import kann derzeit nur im Zusammenhang mit der "
+"Fragebogen-ID\n"
+"benutzt werden."
+
+#: ../sdaps/cmdline/csvdata.py:44
+msgid "Export data to CSV file."
+msgstr "Daten im CSV Format exportieren."
+
+#: ../sdaps/cmdline/csvdata.py:46
+#, python-format
+msgid "Filename to store the data to (default: data_%%i.csv)"
+msgstr "Dateiname für die CSV Datei (Standard: data_%%i.csv)"
+
+#: ../sdaps/cmdline/csvdata.py:48
+msgid "The delimiter used in the CSV file (default ',')"
+msgstr "Trennzeichen in der CSV Datei (Standard: ',')"
+
+#: ../sdaps/cmdline/csvdata.py:52 ../sdaps/cmdline/report.py:31
+#: ../sdaps/cmdline/reporttex.py:52
+msgid "Filter to only export a partial dataset."
+msgstr "Filter um nur eine Teilmenge der Daten zu exportieren."
+
+#: ../sdaps/cmdline/csvdata.py:54
+msgid "Export images of freeform fields."
+msgstr "Bilder von Freitextfeldern exportieren."
+
+#: ../sdaps/cmdline/csvdata.py:60
+msgid "Export an image for each question that includes all boxes."
+msgstr "Exprtiert ein Bild für jede Frage mit allen Antwortfeldern."
+
+#: ../sdaps/cmdline/csvdata.py:66
+msgid "Export the recognition quality for each checkbox."
+msgstr "Exportiere den Qualitätswert für die Erkennung jeders Ankreuzfeldes."
+
+#: ../sdaps/cmdline/csvdata.py:74
+msgid "Import data to from a CSV file."
+msgstr "Daten aus einer CSV Datei importieren."
+
+#: ../sdaps/cmdline/csvdata.py:76
+msgid "The file to import."
+msgstr "Die zu importierende Datei."
+
+#: ../sdaps/cmdline/gui.py:28
+msgid "Launch a gui. You can view and alter the (recognized) answers with it."
+msgstr "Startet eine Grafische Oberfläche zur Korrektur."
+
+#: ../sdaps/cmdline/gui.py:29
+msgid ""
+"This command launches a graphical user interface that can\n"
+"    be used to correct answer. You need to run \"recognize\" before using "
+"it.\n"
+"    "
+msgstr ""
+"Dieser Befehl startet eine Grafische Oberfläche, mit der es\n"
+"möglich ist Erkennungsfehler zu korrigieren. Bevor dieser Befehl\n"
+"benutzt wird muss der \"recognize\" Schritt schon erfolgt sein."
+
+#: ../sdaps/cmdline/gui.py:34
+msgid "Filter to only show a partial dataset."
+msgstr "Filter um nur einen Teil der Bögen anzuzeigen."
+
+#: ../sdaps/cmdline/ids.py:29
+msgid "Export and import questionnaire IDs."
+msgstr "Export und Import der Fragebogen-IDs."
+
+#: ../sdaps/cmdline/ids.py:30
+msgid ""
+"This command can be used to import and export questionnaire\n"
+"    IDs. It only makes sense in projects where such an ID is printed on the\n"
+"    questionnaire. Note that you can also add IDs by using the stamp "
+"command,\n"
+"    which will give you the PDF at the same time."
+msgstr ""
+"Dieses Kommando kann dafür benutzt werden Fragebogen-IDs zu importieren\n"
+"und exportieren. Dies ist nur bei Projekten sinnvoll, bei denen auch eine\n"
+"eindeutige Fragebogen-ID auf den Fragebogen gedruckt wird.\n"
+"Es ist auch möglich Fragebogen-IDs mit dem \"stamp\" Befehl hinzuzufügen, in "
+"dem\n"
+"Fall wird gleichzeitig auch das druckbare PDF erstellt."
+
+#: ../sdaps/cmdline/ids.py:35
+#, python-format
+msgid "Filename to store the data to (default: ids_%%i)"
+msgstr "Ausgabedatei (Standard: ids_%%i)"
+
+#: ../sdaps/cmdline/ids.py:38
+msgid "Add IDs to the internal list from the specified file."
+msgstr "Fügt die IDs aus der Angegebenen Datei hinzu."
+
+#: ../sdaps/cmdline/info.py:28
+msgid "Display and modify metadata of project."
+msgstr "Metadaten des Projekts anzeigen und verändern."
+
+#: ../sdaps/cmdline/info.py:29
+msgid ""
+"This command lets you modify the metadata of the SDAPS\n"
+"    project. You can modify, add and remove arbitrary keys that will be "
+"printed\n"
+"    on the report. The only key that always exist is \"title\".\n"
+"    If no key is given then a list of defined keys is printed."
+msgstr ""
+"Dieses Kommando ermöglicht es die Metadaten des SDAPS Projekts zu\n"
+"verändern. Es ist möglich beliebige Schlüssel zu verändern, hinzuzufügen\n"
+"oder zu löschen. Lediglich der Schlüssel \"title\" ist immer vorhanden.\n"
+"In dem Fall das kein Schlüssel angegeben wird, gibt dieses Kommando eine "
+"Liste\n"
+"aller definierten Schlüssel aus."
+
+#: ../sdaps/cmdline/info.py:36
+msgid "The key to display or modify."
+msgstr "Der Schlüssel der angezeigt oder geändert werden soll."
+
+#: ../sdaps/cmdline/info.py:40
+msgid "Set the given key to this value."
+msgstr "Neuer Wert für den Schlüssel."
+
+#: ../sdaps/cmdline/info.py:44
+msgid "Delete the key and value pair."
+msgstr "Den angegebenen Schlüssel und Wert löschen."
+
+#: ../sdaps/cmdline/info.py:68
+msgid "Existing fields:\n"
+msgstr "Definierte Felder:\n"
+
+#: ../sdaps/cmdline/recognize.py:28
+msgid "Run the optical mark recognition."
+msgstr "Führt die automatische Erkennung durch."
+
+#: ../sdaps/cmdline/recognize.py:29
+msgid ""
+"Iterates over all images and runs the optical mark\n"
+"    recognition. It will reevaluate sheets even if \"recognize\" has "
+"already\n"
+"    run or manual changes were made."
+msgstr ""
+"Dieser Befehl für den Erkenner für alle im Projekt vorhandenen\n"
+"Bilder durch. Hierbei werden auch manuelle Änderungen\n"
+"wieder zurückgesetzt!"
+
+#: ../sdaps/cmdline/recognize.py:34
+msgid ""
+"Only identify the page properties, but don't recognize the checkbox states."
+msgstr ""
+"Analysiert nur die Eigenschaften der Seite, nicht jedoch die Antworten."
+
+#: ../sdaps/cmdline/recognize.py:39
+msgid ""
+"Rerun the recognition for all pages. The default is to skip all pages that "
+"were recognized or verified already."
+msgstr ""
+"Erkenner über alle Seiten erneut ausführen. Ohne diese Option werden schon "
+"erkannte oder verifizierte Seiten übersprungen."
+
+#: ../sdaps/cmdline/reorder.py:26
+msgid "Reorder pages according to questionnaire ID."
+msgstr "Sortiert die gescannten Seiten nach der Fragebogen-ID."
+
+#: ../sdaps/cmdline/reorder.py:27
+msgid ""
+"This command reorders all pages according to the already\n"
+"    recognized questionnaire ID. To use it add all the files to the "
+"project,\n"
+"    then run a partial recognition using \"recognize --identify\". After "
+"this\n"
+"    you have to run this command to reorder the data for the real "
+"recognition.\n"
+"    "
+msgstr ""
+"Dieser Befehl ordnet alle Seiten nach ihrer Fragebogen-ID. Hierfür\n"
+"müssen die Fragebogen-IDs schon bekannt sein, was mit dem Befehl\n"
+"\"recognize --identify\" geschieht. Danach kann dieser Befehl genutzt\n"
+"werden, und zum Schluss ein normaler \"recognize\" Schritt um die Antworten\n"
+"zu erkennen."
+
+#: ../sdaps/cmdline/report.py:26
+msgid "Create a PDF report."
+msgstr "Erstellt einen PDF Report."
+
+#: ../sdaps/cmdline/report.py:27
+msgid ""
+"This command creates a PDF report using reportlab that\n"
+"    contains statistics and if selected the freeform fields."
+msgstr ""
+"Dieses Kommando erstellt ein PDF Report mit Hilfe von reportlab.\n"
+"Dieser Report enthält Statistiken zu jeder Frage und je nach Einstellung\n"
+"die Freitextfelder."
+
+#: ../sdaps/cmdline/report.py:33
+msgid "Create a filtered report for every checkbox."
+msgstr "Erstellt zu jedem Kästchen einen gefilterten Report."
+
+#: ../sdaps/cmdline/report.py:36
+msgid "Short format (without freeform text fields)."
+msgstr "Kurzversion (ohne Freitextfelder)."
+
+#: ../sdaps/cmdline/report.py:41
+msgid "Detailed output. (default)"
+msgstr "Langversion (Standard)"
+
+#: ../sdaps/cmdline/report.py:47 ../sdaps/cmdline/reporttex.py:30
+msgid ""
+"Do not include original images in the report. This is useful if there are "
+"privacy concerns."
+msgstr ""
+"Unterdrückt die Handschriftfelder im Report. Dies sollte verwendet werden, "
+"wenn es Datenschutzbedenken gibt."
+
+#: ../sdaps/cmdline/report.py:52 ../sdaps/cmdline/reporttex.py:35
+msgid "Do not use substitutions instead of images."
+msgstr "Unterdrückt die Ersetzung von Handschriftfeldern."
+
+#: ../sdaps/cmdline/report.py:58 ../sdaps/cmdline/reporttex.py:46
+msgid "The paper size used for the output (default: locale dependent)"
+msgstr "Die Papiergröße des erstellten Dokuments (Standard: Sprachabhängig)"
+
+#: ../sdaps/cmdline/report.py:61 ../sdaps/cmdline/reporttex.py:49
+#, python-format
+msgid "Filename to store the data to (default: report_%%i.pdf)"
+msgstr "Ausgabedatei (Standard: report_%%i.pdf)"
+
+#: ../sdaps/cmdline/reporttex.py:26
+msgid "Create a PDF report using LaTeX."
+msgstr "Erstellt ein PDF Report mit LaTeX."
+
+#: ../sdaps/cmdline/reporttex.py:27
+msgid ""
+"This command creates a PDF report using LaTeX that\n"
+"    contains statistics and freeform fields."
+msgstr ""
+"Dieses Kommando erstellt ein PDF report mit LaTeX. Der Report enthält\n"
+"Statistiken und die Freitextfelder."
+
+#: ../sdaps/cmdline/reporttex.py:41
+msgid "Save the generated TeX files instead of the final PDF."
+msgstr "Speichert die generierten TeX-Dateien anstelle des erstellten PDFs."
+
+#: ../sdaps/cmdline/setup.py:28
+msgid "Create a new survey using an ODT document."
+msgstr "Erstellt eine neue Umfrage aus einem ODT Dokument."
+
+#: ../sdaps/cmdline/setup.py:29
+msgid ""
+"Create a new surevey from an ODT document. The PDF export\n"
+"    of the file needs to be specified at the same time. SDAPS will import\n"
+"    metadata (properties) and the title from the ODT document."
+msgstr ""
+"Dieser Befehl erstellt eine neue Umfrage aus einem ODT Dokument. Der\n"
+"PDF export des Dokuments muss auch angegeben werden. SDAPS importiert\n"
+"auch die Metadaten und den Titel des ODT Dokuments."
+
+#: ../sdaps/cmdline/setup.py:34
+msgid "The ODT Document"
+msgstr "Das ODT Dokument"
+
+#: ../sdaps/cmdline/setup.py:36
+msgid "PDF export of the ODT document"
+msgstr "PDF Export des ODT Dokuments"
+
+#: ../sdaps/cmdline/setup.py:39 ../sdaps/cmdline/setuptex.py:39
+msgid "Additional questions that are not part of the questionnaire."
+msgstr "Weitere Fragen die nicht Teil des Fragebogens sind."
+
+#: ../sdaps/cmdline/setup.py:48
+msgid "Enable printing of the survey ID (default)."
+msgstr "Ausschalten der Umfrage-ID auf den Bögen (Standard)."
+
+#: ../sdaps/cmdline/setup.py:52
+msgid "Disable printing of the survey ID."
+msgstr "Ausschalten der Umfrage-ID auf den Bögen."
+
+#: ../sdaps/cmdline/setup.py:57
+msgid "Enable printing of the questionnaire ID."
+msgstr "Einschalten der Fragebogen-ID auf den Bögen."
+
+#: ../sdaps/cmdline/setup.py:61
+msgid "Disable printing of the questionnaire ID (default)."
+msgstr "Ausschalten der Fragebogen-ID auf den Bögen (Standard)."
+
+#: ../sdaps/cmdline/setup.py:65
+msgid ""
+"Set an additional global ID for tracking. This can can only be used in the "
+"\"code128\" style."
+msgstr ""
+"Setzt eine zusätzliche Globale-ID die auf die Bögen gedruckt wird. Dies "
+"funktioniert nur im \"code128\" Stil."
+
+#: ../sdaps/cmdline/setup.py:70
+msgid ""
+"The stamping style to use. Should be either \"classic\" or \"code128\". Use "
+"\"code128\" for more features."
+msgstr ""
+"Der Stil der zum Stempeln verwendet wird. Muss entweder \"classic\" oder "
+"\"code128\" sein. \"code128\" hat mehr features."
+
+#: ../sdaps/cmdline/setup.py:76
+msgid "The mode to use when detecting checkboxes."
+msgstr "Erkennungsmodus für Ankreuzfelder."
+
+#: ../sdaps/cmdline/setup.py:82
+msgid ""
+"Use duplex mode (ie. only print the IDs on the back side). Requires a "
+"mulitple of two pages."
+msgstr ""
+"Druckmodus auf Duplex einstellen. Die Umfrage muss eine grade Anzahl von "
+"Seiten besitzen."
+
+#: ../sdaps/cmdline/setup.py:87
+msgid ""
+"Use simplex mode. IDs are printed on each page. You need a simplex scan "
+"currently!"
+msgstr ""
+"Druckmodus auf Simplex einstellen. Vorsicht: Es muss derzeit auch simplex "
+"gescannt werden!"
+
+#: ../sdaps/cmdline/setuptex.py:27
+msgid "Create a new survey using a LaTeX document."
+msgstr "Erstellt eine neue Umfrage aus einem LaTeX Dokument."
+
+#: ../sdaps/cmdline/setuptex.py:28
+msgid ""
+"Create a new survey from a LaTeX document. You need to\n"
+"    be using the SDAPS class. All the metadata and options for the project\n"
+"    can be set inside the LaTeX document."
+msgstr ""
+"Dieses Kommando erstellt eine neue Umfrage aus einem LaTeX Dokument. Das\n"
+"Dokument muss die SDAPS Klasse verwenden. Die Metadaten und andere "
+"einstellungen\n"
+"für das Projekt werden im LaTeX Dokument gesetzt."
+
+#: ../sdaps/cmdline/setuptex.py:33
+msgid "The LaTeX Document"
+msgstr "Das LaTeX Dokument"
+
+#: ../sdaps/cmdline/setuptex.py:35
+msgid ""
+"Additional files that are required by the LaTeX document and need to be "
+"copied into the project directory."
+msgstr ""
+"Zusätzliche Dateien die von dem LaTeX Dokument benötigt werden und daher in "
+"das Projektverzeichnis kopiert werden müssen."
+
+#: ../sdaps/cmdline/stamp.py:26
+msgid "Add marks for automatic processing."
+msgstr "Fügt Markierungen für den Erkenner hinzu."
+
+#: ../sdaps/cmdline/stamp.py:27
+msgid ""
+"This command creates the printable document. Depending on\n"
+"    the projects setting you are required to specifiy a source for "
+"questionnaire\n"
+"    IDs."
+msgstr ""
+"Dieses Kommando erstellt den Druckbaren Fragebogen. Je nach Einstellung\n"
+"muss noch eine Quelle für (eindeutige) Fragebogen-IDs mit angegeben werden."
+
+#: ../sdaps/cmdline/stamp.py:33
+msgid ""
+"If using questionnaire IDs, create N questionnaires with randomized IDs."
+msgstr ""
+"Wenn Fragebogen-IDs genutzt werden, dann erstelle N Fragebögen mit "
+"zufälliger Nummer."
+
+#: ../sdaps/cmdline/stamp.py:36
+msgid ""
+"If using questionnaire IDs, create questionnaires from the IDs read from the "
+"specified file."
+msgstr ""
+"Wenn Fragebogen-IDs genutzt werden, dann lese die IDs aus der angegeben "
+"Datei."
+
+#: ../sdaps/cmdline/stamp.py:39
+msgid "If using questionnaire IDs, create questionnaires for all stored IDs."
+msgstr ""
+"Wenn Fragebogen-IDs genutzt werden, dann erstelle Fragebögen für alle schon "
+"gespeicherten IDs."
+
+#: ../sdaps/cmdline/stamp.py:42
+#, python-format
+msgid "Filename to store the data to (default: stamp_%%i.pdf)"
+msgstr "Ausgabedatei (default: stamp_%%i.pdf)"
+
+#: ../sdaps/convert/__init__.py:35
+#, python-format
+msgid "Could not apply 3D-transformation to image '%s', page %i!"
+msgstr ""
+"Konnte die 3D-Transformation nicht auf Bild '%s' auf Seite %i anwenden!"
+
+#
+#: ../sdaps/cover/__init__.py:40
+msgid "sdaps questionnaire"
+msgstr "sdaps Fragebögen"
+
+#: ../sdaps/gui/__init__.py:57
+msgid ""
+"The survey does not have any images! Please add images (and run recognize) "
+"before using the GUI."
+msgstr ""
+"Die Umfrage enhält noch keine Bilder! Es müssen Bilder hinzugefügt und "
+"\"recognize\" ausgeführt werden befor die GUI benutzbar ist."
+
+#: ../sdaps/gui/__init__.py:202
+msgid "Page|Invalid"
+msgstr "Ungültig"
+
+#: ../sdaps/gui/__init__.py:205
+#, python-format
+msgid "Page %i"
+msgid_plural "Page %i"
+msgstr[0] "Seite %i"
+msgstr[1] "Seite %i"
+
+#: ../sdaps/gui/__init__.py:247
+msgid "Copyright © 2007-2014 The SDAPS Authors"
+msgstr "Copyright © 2007-2014 The SDAPS Authors"
+
+#: ../sdaps/gui/__init__.py:249
+msgid "Scripts for data acquisition with paper based surveys"
+msgstr "SDAPS - Scripts zum auswerten von Papier basierten Umfragen"
+
+#: ../sdaps/gui/__init__.py:250
+msgid "http://sdaps.org"
+msgstr "http://sdaps.org"
+
+#: ../sdaps/gui/__init__.py:251
+msgid "translator-credits"
+msgstr "Benjamin Berg"
+
+#: ../sdaps/gui/__init__.py:296
+#, python-format
+msgid " of %i"
+msgstr " von %i"
+
+#: ../sdaps/gui/__init__.py:297
+#, python-format
+msgid "Recognition Quality: %.2f"
+msgstr "Erkennungsqualität: %.2f"
+
+#: ../sdaps/gui/__init__.py:313
+msgid ""
+"You have reached the first page of the survey. Would you like to go to the "
+"last page?"
+msgstr ""
+"Sie haben die erste Seite der Umfrage errreicht. Möchten Sie zur letzten "
+"Seite springen?"
+
+#: ../sdaps/gui/__init__.py:315
+msgid "Go to last page"
+msgstr "Zur letzten Seite springen"
+
+#: ../sdaps/gui/__init__.py:337
+msgid ""
+"You have reached the last page of the survey. Would you like to go to the "
+"first page?"
+msgstr ""
+"Sie haben die letzte Seite der Umfrage errreicht. Möchten Sie zur ersten "
+"Seite springen?"
+
+#: ../sdaps/gui/__init__.py:339
+msgid "Go to first page"
+msgstr "Zur ersten Seite springen"
+
+#: ../sdaps/gui/__init__.py:447
+msgid "Close without saving"
+msgstr "Beenden ohne zu Speichern"
+
+#: ../sdaps/gui/__init__.py:451
+msgid ""
+"<b>Save the project before closing?</b>\n"
+"\n"
+"If you do not save you may loose data."
+msgstr ""
+"<b>Beenden ohne zu Speichern?</b>\n"
+"Wenn Sie nicht speichern, kann es zu Datenverlust kommen."
+
+#: ../sdaps/gui/main_window.ui.h:1
+msgid "Forward"
+msgstr "Nächste Seite"
+
+#: ../sdaps/gui/main_window.ui.h:2
+msgid "Previous"
+msgstr "Vorherige Seite"
+
+#: ../sdaps/gui/main_window.ui.h:3
+msgid "Zoom In"
+msgstr "Vergrößern"
+
+#: ../sdaps/gui/main_window.ui.h:4
+msgid "Zoom Out"
+msgstr "Verkleinern"
+
+#: ../sdaps/gui/main_window.ui.h:5
+msgid "SDAPS"
+msgstr "SDAPS"
+
+#: ../sdaps/gui/main_window.ui.h:6
+msgid "_File"
+msgstr "_Datei"
+
+#: ../sdaps/gui/main_window.ui.h:7
+msgid "_View"
+msgstr "_Ansicht"
+
+#: ../sdaps/gui/main_window.ui.h:8
+msgid "_Help"
+msgstr "_Hilfe"
+
+#: ../sdaps/gui/main_window.ui.h:9
+msgid "Page Rotated"
+msgstr "Seite Gedreht"
+
+#: ../sdaps/gui/main_window.ui.h:10
+msgid "Sort by Quality"
+msgstr "Nach Qualität sortieren"
+
+#: ../sdaps/gui/main_window.ui.h:11
+msgid "label"
+msgstr "Sollte niemals sichtbar sein."
+
+#: ../sdaps/gui/widget_buddies.py:56
+msgid "<b>Global Properties</b>"
+msgstr "<b>Globale Eigenschaften</b>"
+
+#: ../sdaps/gui/widget_buddies.py:60
+msgid "Sheet valid"
+msgstr "Bogen gültig"
+
+#: ../sdaps/gui/widget_buddies.py:61
+msgid "Verified"
+msgstr "Verifiziert"
+
+#: ../sdaps/gui/widget_buddies.py:62
+msgid "Empty"
+msgstr "Leer"
+
+#: ../sdaps/gui/widget_buddies.py:74 ../sdaps/gui/widget_buddies.py:98
+msgid "<b>Questionnaire ID: </b>"
+msgstr "<b>Fragebogen-ID: </b>"
+
+#: ../sdaps/image/__init__.py:47
+msgid ""
+"It appears you have not build the C extension. Please run \"./setup.py build"
+"\" in the toplevel directory."
+msgstr ""
+"Die C Bibliothek steht nicht zur verfügung. Bitte führen Sie \"./setup.by "
+"build\" im obersten Verzeichniss aus um die Bibliothek zu bauen."
+
+#: ../sdaps/utils/opencv.py:31
+msgid "Cannot convert PDF files as poppler is not installed or usable!"
+msgstr ""
+"PDF Dateien können nicht konvertiert werden, da poppler nicht gefunden wurde "
+"oder nicht verwendet werden kann!"
+
+#: ../sdaps/model/survey.py:253
+#, python-format
+msgid "%i sheet"
+msgid_plural "%i sheets"
+msgstr[0] "%i Bogen"
+msgstr[1] "%i Bögen"
+
+#: ../sdaps/model/survey.py:264
+#, python-format
+msgid "%f seconds per sheet"
+msgstr "%f Sekunden pro Bogen"
+
+#: ../sdaps/model/survey.py:290
+msgid ""
+"A questionnaire that is printed in duplex needs an even amount of pages!"
+msgstr ""
+"Ein Fragebogen der doppelseitig gedruckt werden soll, muss eine gerade "
+"Anzahl von Seiten haben!"
+
+#: ../sdaps/model/survey.py:294
+msgid ""
+"The 'classic' style only supports a maximum of six pages! Use the 'code128' "
+"style if you require more pages."
+msgstr ""
+"Der Stil 'classic' kann nur mit bis zu sechs Seiten umgehen. Für mehr Seiten "
+"kann der 'code128' Stil benutzt werden."
+
+#: ../sdaps/model/survey.py:307
+msgid "IDs need to be integers in \"classic\" style!"
+msgstr "IDs müssen im \"classic\" Stil ganze Zahlen sein!"
+
+#: ../sdaps/model/survey.py:313
+#, python-format
+msgid "Invalid character %s in questionnaire ID \"%s\" in \"code128\" style!"
+msgstr "Ungültiges Zeichen %s in der Fragebogen ID \"%s\" im \"code128\" Stil!"
+
+#: ../sdaps/model/survey.py:317
+msgid ""
+"SDAPS cannot draw a questionnaire ID with the \"custom\" style. Do this "
+"yourself somehow!"
+msgstr ""
+"SDAPS kann im \"custom\"-Stil keine Fragebogen ID eintragen. Tun Sie das "
+"irgendwie selbst!"
+
+#: ../sdaps/model/survey.py:337
+#, python-format
+msgid "Running upgrade routines for file format version %i"
+msgstr "Update für Dateiversion %i ausführen"
+
+#. in simplex mode every page will have a matrix; it might be a None
+#. matrix though
+#: ../sdaps/recognize/buddies.py:72
+#, python-format
+msgid "%s, %i: Matrix not recognized."
+msgstr "%s, %i: Matrix wurde nicht erkannt."
+
+#: ../sdaps/recognize/buddies.py:81
+#, python-format
+msgid "%s, %i: Rotation not found."
+msgstr "%s, %i: Drehung wurde nicht erkannt."
+
+#. Copy the rotation over (if required) and print warning if the rotation is unknown
+#: ../sdaps/recognize/buddies.py:85
+#, python-format
+msgid "Neither %s, %i or %s, %i has a known rotation!"
+msgstr "Weder %s, %i noch %s, %i hat eine bekannte Drehung!"
+
+#: ../sdaps/recognize/buddies.py:97
+#, python-format
+msgid "%s, %i: Matrix not recognized (again)."
+msgstr "%s, %i: Matrix nicht erkannt (nach Drehung)."
+
+#: ../sdaps/recognize/buddies.py:111
+#, python-format
+msgid "%s, %i: Could not get page number."
+msgstr "%s, %i: Seitenzahl konnte nicht gelesen werden."
+
+#. Whoa, that should not happen.
+#: ../sdaps/recognize/buddies.py:131
+#, python-format
+msgid "Neither %s, %i or %s, %i has a known page number!"
+msgstr "Weder %s, %i noch %s, %i haben eine bekannte Seitenzahl!"
+
+#. We don't touch the ignore flag in this case
+#. Simply print a message as this should *never* happen
+#: ../sdaps/recognize/buddies.py:142
+#, python-format
+msgid ""
+"Got a simplex document where two adjacent pages had a known page number. "
+"This should never happen as even simplex scans are converted to duplex by "
+"inserting dummy pages. Maybe you did a simplex scan but added it in duplex "
+"mode? The pages in question are %s, %i and %s, %i."
+msgstr ""
+"Simplex Dokument mit zwei benachbarten Seiten mit bekannter Seitennummer "
+"bearbeitet. Das dürfte nicht passieren, da simplex Scans durch einfügen von "
+"Dummyseiten zu Duplexdokumenten konvertiert werden. VWurde ein simplex Scan "
+"im Duplexmodus hinzugefügt? Es sind die Seiten %s, %i und %s, %i betroffen."
+
+#: ../sdaps/recognize/buddies.py:159
+#, python-format
+msgid "Images %s, %i and %s, %i do not have consecutive page numbers!"
+msgstr "Die Bilder %s, %i und %s, %i besitzten nicht konsekutive Seitenzahlen!"
+
+#: ../sdaps/recognize/buddies.py:175
+#, python-format
+msgid "No page number for page %s, %i exists."
+msgstr "Es existiert keine Seitenzahl für Seite %s, %i."
+
+#: ../sdaps/recognize/buddies.py:180
+#, python-format
+msgid "Page number for page %s, %i already used by another image."
+msgstr "Seitenzahl für Seite %s, %i wurde schon einem anderen Bild zugewiesen."
+
+#: ../sdaps/recognize/buddies.py:186
+#, python-format
+msgid "Page number %i for page %s, %i is out of range."
+msgstr "Seitenzahl %i für Seite %s, %i ist außerhalb des erlaubten Bereichs."
+
+#: ../sdaps/recognize/buddies.py:203
+#, python-format
+msgid "%s, %i: Could not read survey ID, but should be able to."
+msgstr ""
+"%s, %i: Konnte die Umfrage-ID nicht einlesen. Dies sollte aber möglich sein."
+
+#: ../sdaps/recognize/buddies.py:207
+#, python-format
+msgid "Could not read survey ID of either %s, %i or %s, %i!"
+msgstr "Konnte die Umfragen-ID weder auf %s, %i noch auf %s, %i finden!"
+
+#. Broken survey ID ...
+#: ../sdaps/recognize/buddies.py:214
+#, python-format
+msgid "Got a wrong survey ID (%s, %i)! It is %s, but should be %i."
+msgstr ""
+"Falsche Umfrage-ID auf Seite (%s, %i)! Die ID ist %s sollte aber %i sein."
+
+#: ../sdaps/recognize/buddies.py:236
+#, python-format
+msgid "%s, %i: Could not read questionnaire ID, but should be able to."
+msgstr ""
+"%s, %i: Konnte die Fragebogen-ID nicht lesen. Dies sollte jedoch möglich "
+"sein."
+
+#: ../sdaps/recognize/buddies.py:240
+#, python-format
+msgid "Could not read questionnaire ID of either %s, %i or %s, %i!"
+msgstr "Konnte die Fragebogen-ID weder auf %s, %i noch auf %s, %i finden!"
+
+#: ../sdaps/recognize/buddies.py:267
+msgid ""
+"Got different IDs on different pages for at least one sheet! Do *NOT* try to "
+"use filters with this survey! You have to run a \"reorder\" step for this to "
+"work properly!"
+msgstr ""
+"Unterschiedliche IDs auf unterschiedlichen Seiten! Die Erkennung wird "
+"funktionieren, die Zuordnung der Daten ist jedoch Falsch! Falls die "
+"Zuordnung benötigt wird, *muss* der \"reorder\" Befehl genutzt werden, um "
+"die Seiten wieder korrekt zuzuordnen!"
+
+#: ../sdaps/recognize/buddies.py:323
+msgid "No style buddy loaded. This needs to be done for the \"custom\" style!"
+msgstr ""
+"Keine Style-Definition geladen. Dies ist beim \"custom\" Stil erforderlich!"
+
+#: ../sdaps/report/answers.py:185
+#, python-format
+msgid "Answers: %i"
+msgstr "Antworten: %i"
+
+#: ../sdaps/report/answers.py:187
+#, python-format
+msgid "Mean: %.2f"
+msgstr "Mittelwert: %.2f"
+
+#: ../sdaps/report/answers.py:189
+#, python-format
+msgid "Standard Deviation: %.2f"
+msgstr "Standardabweichung: %.2f"
+
+#: ../sdaps/report/__init__.py:75 ../sdaps/reporttex/__init__.py:140
+msgid "Turned in Questionnaires"
+msgstr "Abgegebene Fragebögen"
+
+#: ../sdaps/report/__init__.py:92 ../sdaps/reporttex/__init__.py:139
+msgid "sdaps report"
+msgstr "sdaps Report"
+
+#: ../sdaps/setup/buddies.py:61
+#, python-format
+msgid "Head %(l0)i got no title."
+msgstr "Überschrift %(l0)i hat keinen Titel."
+
+#: ../sdaps/setup/buddies.py:74
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got no question."
+msgstr "%(class)s %(l0)i.%(l1)i hat keine Frage."
+
+#: ../sdaps/setup/buddies.py:120
+#, python-format
+msgid "Error in question \"%s\""
+msgstr "Fehler in der Frage \"%s\""
+
+#: ../sdaps/setup/buddies.py:123
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got no boxes."
+msgstr "%(class)s %(l0)i.%(l1)i hat keine Boxen."
+
+#: ../sdaps/setup/buddies.py:148
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got not exactly two answers."
+msgstr "%(class)s %(l0)i.%(l1)i hat nicht exakt zwei Antworten."
+
+#: ../sdaps/setup/buddies.py:170
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got not exactly one box."
+msgstr "%(class)s %(l0)i.%(l1)i hat nicht exakt eine Box."
+
+#: ../sdaps/setupodt/__init__.py:45 ../sdaps/setuptex/__init__.py:68
+msgid "The survey directory already exists."
+msgstr "Das Umfrageverzeichniss existiert bereits."
+
+#: ../sdaps/setupodt/__init__.py:50
+#, python-format
+msgid ""
+"Unknown file type (%s). questionnaire_odt should be application/vnd.oasis."
+"opendocument.text."
+msgstr ""
+"Unbekanntes Dateiformat (%s). questionnaire_odt muss application/vnd.oasis."
+"opendocument.text sein."
+
+#: ../sdaps/setupodt/__init__.py:55
+#, python-format
+msgid "Unknown file type (%s). questionnaire_pdf should be application/pdf."
+msgstr ""
+"Unbekanntes Dateiformat (%s). questionnaire_pdf muss application/pdf sein."
+
+#: ../sdaps/setupodt/__init__.py:61 ../sdaps/setuptex/__init__.py:79
+#, python-format
+msgid "Unknown file type (%s). additionalqobjects should be text/plain."
+msgstr "Unbekanntes Dateiformat (%s). additionalqobjects muss text/plain sein."
+
+#: ../sdaps/setupodt/__init__.py:90
+msgid "Caught an Exception while parsing the ODT file. The current state is:"
+msgstr ""
+"Beim Parsen der ODT Datei ist ein schwerer Fehler aufgetreten. Der jetzige "
+"Zustand ist:"
+
+#: ../sdaps/setupodt/__init__.py:93
+msgid ""
+"If the dependencies for the \"annotate\" command are installed, then an "
+"annotated version will be created next to the original PDF file."
+msgstr ""
+"Wenn die Abhängigkeiten für den \"Anmerkungs\" Befehl installiert sind wird "
+"die kommentierte Version zusätzlich zum original PDF erstellt."
+
+#: ../sdaps/setupodt/__init__.py:123 ../sdaps/setuptex/__init__.py:147
+msgid ""
+"Some combination of options and project properties do not work. Aborted "
+"Setup."
+msgstr ""
+"Setup abgebrochen, da einige Optionen und Projekteigenschaften so nicht "
+"funktionieren."
+
+#: ../sdaps/setupodt/boxesparser.py:121 ../sdaps/setupodt/boxesparser.py:190
+#, python-format
+msgid ""
+"Warning: Ignoring a box (page: %i, x: %.1f, y: %.1f, width: %.1f, height: "
+"%.1f)."
+msgstr ""
+"Warnung: Ignoriere Box (Seite: %i, x: %.1f, y: %.1f, width: %.1f, height: "
+"%.1f)."
+
+#: ../sdaps/stamp/__init__.py:38
+msgid ""
+"You may not specify the number of sheets for this survey. All questionnaires "
+"will be identical as the survey has been configured to not use questionnaire "
+"IDs for each sheet."
+msgstr ""
+"Es ist nicht möglich eine Anzahl von Fragebögen anzugeben. Alle Fragebögen "
+"sind gleich, da die Umfrage nicht für die Benutzung von Fragebogen-IDs für "
+"jeden Bogen konfiguriert wurde."
+
+#: ../sdaps/stamp/__init__.py:76
+msgid ""
+"This survey has been configured to use questionnaire IDs. Each questionnaire "
+"will be unique. You need to use on of the options to add new IDs or use the "
+"existing ones."
+msgstr ""
+"Diese Umfrage wurde mit Fragebogen-IDs konfiguriert, weshalb all Fragebögen "
+"sind unterschiedlich sind. Daher muss eine der Optionen benutzt werden, um "
+"neue IDs hinzuzufügen, oder schon gespeicherte zu verwenden."
+
+#: ../sdaps/stamp/generic.py:37
+#, python-format
+msgid "Survey-ID: %i"
+msgstr "Umfrage-ID: %i"
+
+#: ../sdaps/stamp/generic.py:51
+#, python-format
+msgid "Questionnaire-ID: %i"
+msgstr "Fragebogen-ID: %i"
+
+#: ../sdaps/stamp/generic.py:336
+msgid ""
+"You need to have either pdftk or pyPdf installed. pdftk is the faster method."
+msgstr ""
+"Sie müssen entweder pdtk oder pyPdf installiert haben.pdftk ist die "
+"schnellere Metode."
+
+#. bottomup = False =>(0, 0) is the upper left corner
+#: ../sdaps/stamp/generic.py:352
+#, python-format
+msgid "Creating stamp PDF for %i sheet"
+msgid_plural "Creating stamp PDF for %i sheets"
+msgstr[0] "Erstelle gestempeltes PDF für %i Fragebogen"
+msgstr[1] "Erstelle gestempeltes PDF für %i Fragebögen"
+
+#: ../sdaps/stamp/generic.py:410 ../sdaps/stamp/generic.py:543
+#, python-format
+msgid "%i sheet; %f seconds per sheet"
+msgid_plural "%i sheet; %f seconds per sheet"
+msgstr[0] "%i Bogen; %f Sekunden pro Bogen"
+msgstr[1] "%i Bögen; %f Sekunden pro Bogen"
+
+#. Merge using pdftk
+#: ../sdaps/stamp/generic.py:419
+msgid "Stamping using pdftk"
+msgstr "Stempele mit Hilfe von pdftk"
+
+#. Shortcut if we only have one sheet.
+#. In this case form data in the PDF will *not* break, in
+#. the other code path it *will* break.
+#: ../sdaps/stamp/generic.py:426
+msgid "pdftk: Overlaying the original PDF with the markings."
+msgstr "pdftk: Einfügen der Markierungen in das ursprüngliche PDF-Dokument."
+
+#: ../sdaps/stamp/generic.py:435
+#, python-format
+msgid "pdftk: Splitting out page %d of each sheet."
+msgid_plural "pdftk: Splitting out page %d of each sheet."
+msgstr[0] "pdftk: Extrahier die %d Seite aus dem Stempel."
+msgstr[1] "pdftk: Extrahier die %d Seite aus dem Stempel."
+
+#: ../sdaps/stamp/generic.py:449
+msgid "pdftk: Splitting the questionnaire for watermarking."
+msgstr "pdftk: Extrahiere die einzelnen Seiten des Fragebogens."
+
+#: ../sdaps/stamp/generic.py:460 ../sdaps/stamp/generic.py:469
+#, python-format
+msgid "pdftk: Watermarking page %d of all sheets."
+msgid_plural "pdftk: Watermarking page %d of all sheets."
+msgstr[0] "pdftk: Stempele die Seite %d."
+msgstr[1] "pdftk: Stempele die Seite %d."
+
+#: ../sdaps/stamp/generic.py:492
+msgid "pdftk: Assembling everything into the final PDF."
+msgstr "pdftk: Erstelle das fertige PDF."
+
+#: ../sdaps/stamp/generic.py:529
+msgid "Stamping using pyPdf. For faster stamping, install pdftk."
+msgstr "Stempele mit hilfe von pyPdf. Dies würde mit pdftk schneller gehen."
+
+#: ../sdaps/stamp/latex.py:21
+msgid ""
+"There should be no need to stamp a SDAPS Project that uses LaTeX and does "
+"not have different questionnaire IDs printed on each sheet.\n"
+"I am going to do so anyways."
+msgstr ""
+"Es sollte nicht notwendig sein ein SDAPS-Projekt welches LaTeX verwendet und "
+"keine Fragebogen IDs hat zu stempeln.\n"
+"Die gewünschte Aktion wird dennoch durchgeführt."
+
+#: ../sdaps/stamp/latex.py:43
+#, python-format
+msgid "Running %s now twice to generate the stamped questionnaire."
+msgstr "Führe %s zwei mal aus um den gestempelten Fragebogen zu erstellen."
+
+#: ../sdaps/stamp/latex.py:47 ../sdaps/setuptex/__init__.py:119
+#: ../sdaps/setuptex/__init__.py:158 ../sdaps/reporttex/__init__.py:161
+#, python-format
+msgid "Error running \"%s\" to compile the LaTeX file."
+msgstr "Fehler beim ausführen von \"%s\" um die LaTeX Datei zu kompilieren."
+
+#: ../sdaps/stamp/latex.py:53
+#, python-format
+msgid ""
+"An error occured during creation of the report. Temporary files left in '%s'."
+msgstr ""
+"Es ist ein Fehler beim erstellen des Reports aufgetreten. Temporäre Dateien "
+"wurden in '%s' belassen."
+
+#: ../sdaps/setuptex/__init__.py:73
+#, python-format
+msgid "Unknown file type (%s). questionnaire_tex should be of type text/x-tex."
+msgstr "Unbekanntes Dateiformat (%s). questionnaire_tex muss text/x-tex sein."
+
+#: ../sdaps/setuptex/__init__.py:74
+msgid "Will keep going, but expect failure!"
+msgstr "Versuche trotzdem fortzusetzten."
+
+#: ../sdaps/setuptex/__init__.py:115 ../sdaps/setuptex/__init__.py:153
+#, python-format
+msgid "Running %s now twice to generate the questionnaire."
+msgstr "Führe %s zwei mal aus um den Fragebogen zu erstellen."
+
+#: ../sdaps/setuptex/__init__.py:133
+msgid "Caught an Exception while parsing the SDAPS file. The current state is:"
+msgstr ""
+"Beim Parsen der SDAPS Datei ist ein schwerer Fehler aufgetreten. Der jetzige "
+"Zustand ist:"
+
+#: ../sdaps/setuptex/__init__.py:174
+msgid ""
+"An error occured in the setup routine. The survey directory still exists. "
+"You can for example check the questionnaire.log file for LaTeX compile "
+"errors."
+msgstr ""
+"Es ist ein Fehler in der Setup Routine aufgetreten. Das ungültige "
+"Projektverzeichniss existiert noch, es ist daher möglich z.B. in die "
+"questionnaire.log auf LaTeX Fehler zu untersuchen."
+
+#: ../sdaps/reporttex/__init__.py:107
+msgid "author|Unknown"
+msgstr "Unbekannt"
+
+#: ../sdaps/reporttex/__init__.py:138
+msgid "tex language|english"
+msgstr "ngerman"
+
+#: ../sdaps/reporttex/__init__.py:154
+#, python-format
+msgid "The TeX project with the report data is located at '%s'."
+msgstr "Das TeX-Projekt für den Report liegt unter '%s'."
+
+#: ../sdaps/reporttex/__init__.py:157
+#, python-format
+msgid "Running %s now twice to generate the report."
+msgstr "Führe %s zwei mal aus um den Report zu erstellen."
+
+#: ../sdaps/reporttex/__init__.py:167
+#, python-format
+msgid "An occured during creation of the report. Temporary files left in '%s'."
+msgstr ""
+"Es ist ein Fehler beim erstellen des Reports aufgetreten. Temporäre Dateien "
+"wurden in '%s' belassen."
+
+#~ msgid ""
+#~ "You selected to reference existing files, however not all files are in "
+#~ "the correct format. If you don't want the file to be copied into the "
+#~ "survey directory, you need to manually convert it first."
+#~ msgstr ""
+#~ "Es sollen bestehende Dateien referenziert werden, aber nicht alle Dateien "
+#~ "haben das passende Format. Wenn die Dateien nicht in das "
+#~ "Projektverzeichniss kopiert werden sollen müssen sie vorher konvertiert "
+#~ "werden."
+
+#~ msgid ""
+#~ "Converted image does not have correct format. This means the page count "
+#~ "is wrong."
+#~ msgstr ""
+#~ "Das konvertierten hat nicht das richtige Format. Die Anzahl der Seiten "
+#~ "ist falsch."
+
+#~ msgid "Sheet Valid (all pages)"
+#~ msgstr "Ausgefüllter Bogen"
+
+#~ msgid "Only generates the TeX project. This option is for debug purpose."
+#~ msgstr ""
+#~ "Generiert nur das TeX-Projekt. Diese Option ist für das Debuggen "
+#~ "interessant."
+
+#~ msgid "%(class)s %(l0)i.%(l1)i got not exactly five boxes."
+#~ msgstr "%(class)s %(l0)i.%(l1)i hat nicht exakt fünf Boxen."
+
+#~ msgid ""
+#~ "Found inconsistency. %s, %i and %s, %i should have the same rotation, but "
+#~ "don't!"
+#~ msgstr ""
+#~ "%s, %i und %s, %i sollten die gleiche Drehung haben, diese stimmt jedoch "
+#~ "nicht überein!"
diff --git a/po/es.po b/po/es.po
new file mode 100644
index 0000000..a5f37f1
--- /dev/null
+++ b/po/es.po
@@ -0,0 +1,1284 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: SDAPS 0.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-03-13 19:45+0100\n"
+"PO-Revision-Date: 2015-01-12 18:13+0200\n"
+"Last-Translator: Rene Martinez <rantmtorres at gmail.com>\n"
+"Language-Team: Spanish <https://hosted.weblate.org/projects/sdaps/master/es/"
+">\n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 2.2-dev\n"
+
+#: ../sdaps/script.py:41
+msgid "SDAPS -- Paper based survey tool."
+msgstr "SDAPS -- Herramienta para el reconocimiento de documentos impresos."
+
+#: ../sdaps/script.py:45
+msgid "project directory|The SDAPS project."
+msgstr "directorio del proyecto|El proyecto SDAPS."
+
+#: ../sdaps/script.py:46
+msgid "command list|Commands:"
+msgstr "lista de comandos|Comandos:"
+
+#: ../sdaps/add/__init__.py:55
+#, python-format
+msgid ""
+"Invalid input file %s. You need to specify a (multipage) monochrome TIFF as "
+"input."
+msgstr ""
+"Archivo de entrada inválido %s. Debe especificar un archivo (multipágina) "
+"monocromático TIFF como entrada."
+
+#: ../sdaps/add/__init__.py:67
+#, python-format
+msgid ""
+"Not adding %s because it has a wrong page count (needs to be a mulitple of "
+"%i)."
+msgstr ""
+"No se ha añadido %s debido a que tiene un conteo de página incorrecto (debe "
+"ser múltiplo de %i)."
+
+#: ../sdaps/boxgallery/__init__.py:108
+#, python-format
+msgid "Rendering boxgallery for metric \"%s\"."
+msgstr "Renderizando cajas por la métrica \"%s\"."
+
+#: ../sdaps/log.py:37
+msgid "Warning: "
+msgstr "Advertencia: "
+
+#: ../sdaps/log.py:41
+msgid "Error: "
+msgstr "Error: "
+
+#: ../sdaps/cmdline/add.py:31
+msgid "Add scanned questionnaires to the survey."
+msgstr "Añade cuestionarios escaneados al reconocimiento."
+
+#: ../sdaps/cmdline/add.py:32
+msgid ""
+"This command is used to add scanned images to the survey.\n"
+"    The image data needs to be a (multipage) 300dpi monochrome TIFF file. "
+"You\n"
+"    may choose not to copy the data into the project directory. In that "
+"case\n"
+"    the data will be referenced using a relative path."
+msgstr ""
+"Este comando se emplea para añadir imágenes escaneadas al reconocimiento.\n"
+"  La imagen de los datos debe ser un archivo (multipágina) monocromático "
+"TIFF de 300dpi. Usted\n"
+"  puede elegir no copiar los datos en el directorio del proyecto. En ese "
+"caso\n"
+"  se hará referencia a los datos empleando una ruta relativa."
+
+#: ../sdaps/cmdline/add.py:38
+msgid "Convert given files and add the result."
+msgstr "Convierte los archivos especificados y añade el resultado."
+
+#: ../sdaps/cmdline/add.py:43 ../sdaps/cmdline/convert.py:39
+msgid ""
+"Do a 3D-transformation after finding the corner marks. If the\n"
+"        corner marks are not found then the image will be added as-is."
+msgstr ""
+"Realiza una transformación 3D después de encontrar las marcas de esquina. Si "
+"no\n"
+"    se encuentran las marcas la imagen será añadida tal cual es."
+
+#: ../sdaps/cmdline/add.py:49
+msgid ""
+"Force adding the images even if the page count is wrong (only use if you "
+"know what you are doing)."
+msgstr ""
+"Fuerza el añadir las imágenes incluso si el conteo de página es incorrecto "
+"(empléelo únicamente si sabe lo que está haciendo."
+
+#: ../sdaps/cmdline/add.py:53
+msgid "Copy the files into the directory (default)."
+msgstr "Copia los archivos en el directorio (por defecto)."
+
+#: ../sdaps/cmdline/add.py:58
+msgid "Do not copy the files into the directory."
+msgstr "No copia los archivos en el directorio."
+
+#: ../sdaps/cmdline/add.py:62
+msgid ""
+"Images contain a duplex scan of a simplex questionnaire (default: simplex "
+"scan)."
+msgstr ""
+"Las imágenes contienen un escaneo doble de un cuestionario sencillo (por "
+"defecto: escaneo sencillo)."
+
+#: ../sdaps/cmdline/add.py:68 ../sdaps/cmdline/convert.py:49
+msgid "A number of TIFF image files."
+msgstr "Un número de archivos de imagen TIFF."
+
+#: ../sdaps/cmdline/add.py:94
+msgid "The --no-copy option is not compatible with --convert!"
+msgstr "La opción -no-copy no es compatible con --convert!"
+
+#: ../sdaps/cmdline/add.py:103
+msgid "Converting input files into a single temporary file."
+msgstr "Convirtiendo archivos de entrada en un único archivo temporal."
+
+#: ../sdaps/cmdline/add.py:115
+msgid ""
+"The page count of the created temporary file does not work with this survey."
+msgstr "El recuento del archivo temporal creado no funciona con esta encuesta."
+
+#: ../sdaps/cmdline/add.py:120
+msgid "Running the conversion failed."
+msgstr "Fallo al ejecutar la conversión."
+
+#: ../sdaps/cmdline/add.py:125
+#, python-format
+msgid "Processing %s"
+msgstr "Procesando %s"
+
+#: ../sdaps/cmdline/add.py:129
+msgid "Done"
+msgstr "Finalizado"
+
+#: ../sdaps/cmdline/annotate.py:28
+msgid "Annotate the questionnaire and show the recognized positions."
+msgstr "Comenta el cuestionario y muestra la posiciones reconocidas."
+
+#: ../sdaps/cmdline/annotate.py:29
+msgid ""
+"This command is mainly a debug utility. It creates an\n"
+"    annotated version of the questionnaire, with the information that SDAPS\n"
+"    knows about it overlayed on top."
+msgstr ""
+"Este comando es principalmente una herramienta para depuración. Crea una\n"
+"  versión comentada del cuestionario con la información que SDAPS\n"
+"  conoce acerca de él sobrepuesta en la parte superior."
+
+#: ../sdaps/cmdline/boxgallery.py:31
+msgid "Create PDFs with boxes sorted by the detection heuristics."
+msgstr ""
+"Crear archivos PDF con casillas ordenadas por la heurística de detección."
+
+#: ../sdaps/cmdline/boxgallery.py:32
+msgid ""
+"SDAPS uses multiple heuristics to detect determine the\n"
+"    state of checkboxes. There is a list for each heuristic giving the "
+"expected\n"
+"    state and the quality of the value (see defs.py). Using this command a "
+"PDF\n"
+"    will be created for each of the heuristics so that one can adjust the\n"
+"    values."
+msgstr ""
+"SDAPS emplea heurística múltiple para determinar el\n"
+"  estado de las casillas de verificación. Existe una lista para cada "
+"heurística que proporciona\n"
+"  el estado y la calidad del valor (ver defs.py). Empleando este comando se "
+"creará\n"
+"  un archivo PDF para cada heurística de tal forma que se puedan ajustar\n"
+"  los valores."
+
+#: ../sdaps/cmdline/boxgallery.py:40
+msgid ""
+"Reruns part of the recognition process and retrieves debug images from this "
+"step."
+msgstr ""
+"Reejecuta parte del proceso de reconocimiento y recupera imágenes de "
+"depuración a partir de este paso."
+
+#: ../sdaps/cmdline/convert.py:30
+msgid "Convert a set of images to the correct image format."
+msgstr "Convierte un conjunto de imágenes al formato correcto de imagen."
+
+#: ../sdaps/cmdline/convert.py:31
+msgid ""
+"This command can be used if you scanned files in a different\n"
+"        mode than the expected monochrome TIFF file. All the given files "
+"will\n"
+"        be loaded, converted to monochrome and stored into a multipage 1bpp\n"
+"        TIFF file. Optionally you can select that a 3D transformation "
+"should\n"
+"        be performed, using this it may be possible to work with photos of\n"
+"        questionnaires instead of scans."
+msgstr ""
+"Este comando puede ser empleado si usted escaneo archivos en un modo\n"
+"     distinto al archivo TIFF monocromático esperado. Todos los archivos "
+"proporcionados\n"
+"     se cargarán, convertirán a monocromático y almacenarán en un archivo "
+"multipágina\n"
+"     1bpp TIFF. Opcionamente, usted puede seleccionar que se realice una "
+"transformación 3D,\n"
+"     haciéndolo es posible trabajar con fotografías de\n"
+"     cuestionarios en lugar de escaneos."
+
+#: ../sdaps/cmdline/convert.py:45
+msgid "The location of the output file. Must be given."
+msgstr "Se debe proporcionar la localización del archivo de salida."
+
+#: ../sdaps/cmdline/convert.py:58
+msgid "No output filename specified!"
+msgstr "¡No se especificó un nombre de archivo de salida!"
+
+#: ../sdaps/cmdline/cover.py:27
+msgid "Create a cover for the questionnaires."
+msgstr "Crea una portada para los cuestionarios."
+
+#: ../sdaps/cmdline/cover.py:28
+msgid ""
+"This command creates a cover page for questionnaires. All\n"
+"    the metadata of the survey will be printed on the page."
+msgstr ""
+"Este comando crea una página portada para los cuestionarios. En ella\n"
+"  se imprimirán todos los metadatos de la captura."
+
+#: ../sdaps/cmdline/cover.py:31
+#, python-format
+msgid "Filename to store the data to (default: cover_%%i.pdf)"
+msgstr ""
+"Nombre del archivo para almacenar los datos (por defecto: cover_%%i.pdf)"
+
+#: ../sdaps/cmdline/csvdata.py:34
+msgid "Import or export data to/from CSV files."
+msgstr "Importar o exportar datos a/desde archivos CSV."
+
+#: ../sdaps/cmdline/csvdata.py:35
+msgid ""
+"Import or export data to/from a CSV file. The first line\n"
+"    is a header which defines questionnaire_id and global_id, and a column\n"
+"    for each checkbox and textfield. Note that the import is currently very\n"
+"    limited, as you need to specifiy the questionnaire ID to select the "
+"sheet\n"
+"    which should be updated."
+msgstr ""
+"Importar o exportar datos a/desde un archivo CSV. La primera línea\n"
+" es un encabezado que define  questionnaire_id y  global_id, así como una "
+"columna\n"
+" para cada casilla de verificación y campo de texto. Tenga en cuenta que "
+"actualmente la\n"
+" importación es muy limitada, dado que debe especificar el ID del "
+"cuestionario para seleccionar\n"
+" la hoja que debe actualizarse."
+
+#: ../sdaps/cmdline/csvdata.py:44
+msgid "Export data to CSV file."
+msgstr "Exportar datos a un archivo CSV."
+
+#: ../sdaps/cmdline/csvdata.py:46
+#, python-format
+msgid "Filename to store the data to (default: data_%%i.csv)"
+msgstr ""
+"Nombre del archivo para almacenar los datos (por defecto: data_%%i.csv)"
+
+#: ../sdaps/cmdline/csvdata.py:48
+msgid "The delimiter used in the CSV file (default ',')"
+msgstr "El delimitador empleado en el archivo CSV (por defecto ',')"
+
+#: ../sdaps/cmdline/csvdata.py:52 ../sdaps/cmdline/report.py:31
+#: ../sdaps/cmdline/reporttex.py:52
+msgid "Filter to only export a partial dataset."
+msgstr "Filtro para exportar solamente un conjunto parcial de datos."
+
+#: ../sdaps/cmdline/csvdata.py:54
+msgid "Export images of freeform fields."
+msgstr "Exportar imágenes de los campos de forma libre."
+
+#: ../sdaps/cmdline/csvdata.py:60
+msgid "Export an image for each question that includes all boxes."
+msgstr ""
+"Exportar una imagen para cada pregunta que incluye a todos los recuadros."
+
+#: ../sdaps/cmdline/csvdata.py:66
+msgid "Export the recognition quality for each checkbox."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:74
+msgid "Import data to from a CSV file."
+msgstr "Importar datos desde un archivo CSV."
+
+#: ../sdaps/cmdline/csvdata.py:76
+msgid "The file to import."
+msgstr "El archivo a importar."
+
+#: ../sdaps/cmdline/gui.py:28
+msgid "Launch a gui. You can view and alter the (recognized) answers with it."
+msgstr ""
+"Lanzar una interfaz gráfica. Puede ver y modificar las respuestas "
+"(reconocidas) con ella."
+
+#: ../sdaps/cmdline/gui.py:29
+msgid ""
+"This command launches a graphical user interface that can\n"
+"    be used to correct answer. You need to run \"recognize\" before using "
+"it.\n"
+"    "
+msgstr ""
+"Este comando inicia una interfaz gráfica que puede utilizarse \n"
+"para corregir la respuesta. Necesitará ejecutar \"recognize\" antes de "
+"emplearlo.\n"
+"    "
+
+#: ../sdaps/cmdline/gui.py:34
+msgid "Filter to only show a partial dataset."
+msgstr "Filtro para mostrar sólo un conjunto de datos parcial."
+
+#: ../sdaps/cmdline/ids.py:29
+msgid "Export and import questionnaire IDs."
+msgstr "Exportación e importación de IDs de cuestionarios."
+
+#: ../sdaps/cmdline/ids.py:30
+msgid ""
+"This command can be used to import and export questionnaire\n"
+"    IDs. It only makes sense in projects where such an ID is printed on the\n"
+"    questionnaire. Note that you can also add IDs by using the stamp "
+"command,\n"
+"    which will give you the PDF at the same time."
+msgstr ""
+"Este comando puede utilizarse para importar y exportar IDs de cuestionarios "
+"IDs.       Sólo tiene sentido en proyectos donde el ID se imprime en el "
+"cuestionario.\n"
+" Tenga en cuenta que también puede agregar IDs mediante el comando stamp,\n"
+" el cual le dará el PDF al mismo tiempo."
+
+#: ../sdaps/cmdline/ids.py:35
+#, python-format
+msgid "Filename to store the data to (default: ids_%%i)"
+msgstr "Nombre del archivo para almacenar los datos (por defecto: ids_%%i)"
+
+#: ../sdaps/cmdline/ids.py:38
+msgid "Add IDs to the internal list from the specified file."
+msgstr "Agregar IDs a la lista interna del archivo especificado."
+
+#: ../sdaps/cmdline/info.py:28
+msgid "Display and modify metadata of project."
+msgstr "Visualizar y modificar los metadatos del proyecto."
+
+#: ../sdaps/cmdline/info.py:29
+msgid ""
+"This command lets you modify the metadata of the SDAPS\n"
+"    project. You can modify, add and remove arbitrary keys that will be "
+"printed\n"
+"    on the report. The only key that always exist is \"title\".\n"
+"    If no key is given then a list of defined keys is printed."
+msgstr ""
+"Este comando permite modificar los metadatos del proyecto SDAPS.\n"
+" Usted puede modificar, agregar y quitar claves arbitrarias que se "
+"imprimirán\n"
+" en el informe. La única llave que siempre existen es \"título\".\n"
+" Si no se proporciona ninguna llave entonces se imprime una lista de claves "
+"ya\n"
+" definidas."
+
+#: ../sdaps/cmdline/info.py:36
+msgid "The key to display or modify."
+msgstr "La llave para mostrar o modificar."
+
+#: ../sdaps/cmdline/info.py:40
+msgid "Set the given key to this value."
+msgstr "Establecer la clave dada a este valor."
+
+#: ../sdaps/cmdline/info.py:44
+msgid "Delete the key and value pair."
+msgstr "Eliminar el par de llave y valor."
+
+#: ../sdaps/cmdline/info.py:68
+msgid "Existing fields:\n"
+msgstr "Campos existentes:\n"
+
+#: ../sdaps/cmdline/recognize.py:28
+msgid "Run the optical mark recognition."
+msgstr "Ejecutar el reconocimiento óptico de marcas."
+
+#: ../sdaps/cmdline/recognize.py:29
+msgid ""
+"Iterates over all images and runs the optical mark\n"
+"    recognition. It will reevaluate sheets even if \"recognize\" has "
+"already\n"
+"    run or manual changes were made."
+msgstr ""
+"Itera sobre todas las imágenes y ejecuta el reconocimiento óptico↵\n"
+"    de marcas. Se reevaluarían las hojas incluso si se ha ejecutado "
+"\"recognize\"↵\n"
+"    o si se han realizado cambios manualmente."
+
+#: ../sdaps/cmdline/recognize.py:34
+msgid ""
+"Only identify the page properties, but don't recognize the checkbox states."
+msgstr ""
+"Sólo identifica las propiedades de la página, pero no se reconocen las "
+"respuestas."
+
+#: ../sdaps/cmdline/recognize.py:39
+msgid ""
+"Rerun the recognition for all pages. The default is to skip all pages that "
+"were recognized or verified already."
+msgstr ""
+"Ejecute nuevamente el reconocimiento para todas las páginas. Por defecto se "
+"omiten las ya reconocidas o verificadas."
+
+#: ../sdaps/cmdline/reorder.py:26
+msgid "Reorder pages according to questionnaire ID."
+msgstr "Reordenar páginas de acuerdo con el ID de los cuestionarios."
+
+#: ../sdaps/cmdline/reorder.py:27
+msgid ""
+"This command reorders all pages according to the already\n"
+"    recognized questionnaire ID. To use it add all the files to the "
+"project,\n"
+"    then run a partial recognition using \"recognize --identify\". After "
+"this\n"
+"    you have to run this command to reorder the data for the real "
+"recognition.\n"
+"    "
+msgstr ""
+"Este comando reordena todas las páginas por las IDs ya reconocidas de↵\n"
+"    los cuestionarios. Para utilizarlo se deben añadir todos los archivos al "
+"proyecto y↵\n"
+"    después realizar un reconocimiento parcial usando \"recognize --identify"
+"\". Luego↵\n"
+"    hay que ejecutar este comando para reordenar los datos antes del "
+"reconocimiento.\n"
+"    "
+
+#: ../sdaps/cmdline/report.py:26
+msgid "Create a PDF report."
+msgstr "Crear un informe en PDF."
+
+#: ../sdaps/cmdline/report.py:27
+msgid ""
+"This command creates a PDF report using reportlab that\n"
+"    contains statistics and if selected the freeform fields."
+msgstr ""
+"Esta orden crea un informe en PDF utilizando reportlab que↵\n"
+"    contiene estadísticas y los campos de forma libre, si se han "
+"seleccionado."
+
+#: ../sdaps/cmdline/report.py:33
+msgid "Create a filtered report for every checkbox."
+msgstr "Crear un informe filtrado para cada casilla."
+
+#: ../sdaps/cmdline/report.py:36
+msgid "Short format (without freeform text fields)."
+msgstr "Formato corto (sin campos de texto de forma libre)."
+
+#: ../sdaps/cmdline/report.py:41
+msgid "Detailed output. (default)"
+msgstr "Salida detallada. (por defecto)"
+
+#: ../sdaps/cmdline/report.py:47 ../sdaps/cmdline/reporttex.py:30
+msgid ""
+"Do not include original images in the report. This is useful if there are "
+"privacy concerns."
+msgstr ""
+"No incluir imágenes originales en el informe. Esto es útil si hubiera "
+"preocupación por la privacidad."
+
+#: ../sdaps/cmdline/report.py:52 ../sdaps/cmdline/reporttex.py:35
+msgid "Do not use substitutions instead of images."
+msgstr "No utilizar sustituciones en vez de imágenes."
+
+#: ../sdaps/cmdline/report.py:58 ../sdaps/cmdline/reporttex.py:46
+msgid "The paper size used for the output (default: locale dependent)"
+msgstr ""
+"El tamaño del papel utilizado para la salida (por defecto: depende de "
+"ajustes locales)"
+
+#: ../sdaps/cmdline/report.py:61 ../sdaps/cmdline/reporttex.py:49
+#, python-format
+msgid "Filename to store the data to (default: report_%%i.pdf)"
+msgstr ""
+"Nombre del archivo para almacenar los datos (por defecto: report_%%i.pdf)"
+
+#: ../sdaps/cmdline/reporttex.py:26
+msgid "Create a PDF report using LaTeX."
+msgstr "Crea un informe en PDF utilizando LaTeX."
+
+#: ../sdaps/cmdline/reporttex.py:27
+msgid ""
+"This command creates a PDF report using LaTeX that\n"
+"    contains statistics and freeform fields."
+msgstr ""
+"Este comando crea un informe PDF utilizando LaTex que↵\n"
+"    contiene estadísticas y campos de forma libre."
+
+#: ../sdaps/cmdline/reporttex.py:41
+msgid "Save the generated TeX files instead of the final PDF."
+msgstr "Guarda los archivos TeX generados en vez del PDF final."
+
+#: ../sdaps/cmdline/setup.py:28
+msgid "Create a new survey using an ODT document."
+msgstr "Crear un cuestionario nuevo utilizando un documento ODT."
+
+#: ../sdaps/cmdline/setup.py:29
+msgid ""
+"Create a new surevey from an ODT document. The PDF export\n"
+"    of the file needs to be specified at the same time. SDAPS will import\n"
+"    metadata (properties) and the title from the ODT document."
+msgstr ""
+"Crea un nuevo cuestionario a partir de un documento ODT. El archivo en PDF↵\n"
+"    exportado debe ser especificado al mismo tiempo. SDAPS importará↵\n"
+"    los metadatos (propiedades) y el título desde el documento ODT."
+
+#: ../sdaps/cmdline/setup.py:34
+msgid "The ODT Document"
+msgstr "El documento ODT"
+
+#: ../sdaps/cmdline/setup.py:36
+msgid "PDF export of the ODT document"
+msgstr "Archivo PDF exportado a partir del documento ODT"
+
+#: ../sdaps/cmdline/setup.py:39 ../sdaps/cmdline/setuptex.py:39
+msgid "Additional questions that are not part of the questionnaire."
+msgstr "Otras preguntas que no forman parte del cuestionario."
+
+#: ../sdaps/cmdline/setup.py:48
+msgid "Enable printing of the survey ID (default)."
+msgstr "Activar la impresión del ID del cuestionario (por defecto)."
+
+#: ../sdaps/cmdline/setup.py:52
+msgid "Disable printing of the survey ID."
+msgstr "Desactivar la impresión del ID del cuestionario."
+
+#: ../sdaps/cmdline/setup.py:57
+msgid "Enable printing of the questionnaire ID."
+msgstr "Activar la impresión del ID del cuestionario."
+
+#: ../sdaps/cmdline/setup.py:61
+msgid "Disable printing of the questionnaire ID (default)."
+msgstr "Desactivar la impresión del ID del cuestionario (por defecto)."
+
+#: ../sdaps/cmdline/setup.py:65
+msgid ""
+"Set an additional global ID for tracking. This can can only be used in the "
+"\"code128\" style."
+msgstr ""
+"Definir un ID globar para la monitorización. Sólo se puede emplear en el "
+"estilo \"code128\"."
+
+#: ../sdaps/cmdline/setup.py:70
+msgid ""
+"The stamping style to use. Should be either \"classic\" or \"code128\". Use "
+"\"code128\" for more features."
+msgstr ""
+"El estilo utilizado para estampar. Debe ser \"classic\" o \"code128\". Use "
+"\"code128\" para tener más recursos disponibles."
+
+#: ../sdaps/cmdline/setup.py:76
+msgid "The mode to use when detecting checkboxes."
+msgstr "El modo a utilizar para detectar casillas."
+
+#: ../sdaps/cmdline/setup.py:82
+msgid ""
+"Use duplex mode (ie. only print the IDs on the back side). Requires a "
+"mulitple of two pages."
+msgstr ""
+"Utilizar modo duplex (esto es, imprimir sólo los IDs en el reverso de las "
+"hojas). Requiere un múltiplo de dos páginas."
+
+#: ../sdaps/cmdline/setup.py:87
+msgid ""
+"Use simplex mode. IDs are printed on each page. You need a simplex scan "
+"currently!"
+msgstr ""
+"Utilizar modo simplex. Los IDs se imprimen en cada página. ¡El escaneo debe "
+"realizarse en modo simplex!"
+
+#: ../sdaps/cmdline/setuptex.py:27
+msgid "Create a new survey using a LaTeX document."
+msgstr "Crear una nueva encuesta utilizando un documento LaTeX."
+
+#: ../sdaps/cmdline/setuptex.py:28
+msgid ""
+"Create a new survey from a LaTeX document. You need to\n"
+"    be using the SDAPS class. All the metadata and options for the project\n"
+"    can be set inside the LaTeX document."
+msgstr ""
+"Crear una nueva encuesta desde un documento LaTeX. Es necesario estar↵\n"
+"    utilizando la clase SDAPS. Todos los metadatos y opciones para el "
+"proyecto↵\n"
+"    pueden ser definidos en el documento LaTeX."
+
+#: ../sdaps/cmdline/setuptex.py:33
+msgid "The LaTeX Document"
+msgstr "El documento LaTeX"
+
+#: ../sdaps/cmdline/setuptex.py:35
+msgid ""
+"Additional files that are required by the LaTeX document and need to be "
+"copied into the project directory."
+msgstr ""
+"Ficheros adicionares que son necesarios para el documento LaTeX y que "
+"necesitan ser copiados en el directorio del proyecto."
+
+#: ../sdaps/cmdline/stamp.py:26
+msgid "Add marks for automatic processing."
+msgstr "Añadir marcas para el procesamiento automático."
+
+#: ../sdaps/cmdline/stamp.py:27
+msgid ""
+"This command creates the printable document. Depending on\n"
+"    the projects setting you are required to specifiy a source for "
+"questionnaire\n"
+"    IDs."
+msgstr ""
+"Este comando crea el documento para la impresión. Dependiendo de las↵\n"
+"    configuraciones del proyecto se debe especificar un origen para los↵\n"
+"    IDs del cuestionario."
+
+#: ../sdaps/cmdline/stamp.py:33
+msgid ""
+"If using questionnaire IDs, create N questionnaires with randomized IDs."
+msgstr ""
+"Si se están utilizando IDs de cuestionario, crear N cuestionarios con IDs "
+"aleatorios."
+
+#: ../sdaps/cmdline/stamp.py:36
+msgid ""
+"If using questionnaire IDs, create questionnaires from the IDs read from the "
+"specified file."
+msgstr ""
+"Si se están utilizando IDs de cuestionario, crear cuestionarios desde las "
+"IDs leídas en el fichero especificado."
+
+#: ../sdaps/cmdline/stamp.py:39
+msgid "If using questionnaire IDs, create questionnaires for all stored IDs."
+msgstr ""
+"Si se están utilizando IDs de cuestionario, crear cuestionarios para todas "
+"las IDs almacenadas."
+
+#: ../sdaps/cmdline/stamp.py:42
+#, python-format
+msgid "Filename to store the data to (default: stamp_%%i.pdf)"
+msgstr ""
+"Nombre del archivo para almacenar los datos (por defecto: stamp_%%i.pdf)"
+
+#: ../sdaps/convert/__init__.py:35
+#, python-format
+msgid "Could not apply 3D-transformation to image '%s', page %i!"
+msgstr ""
+"No fue posible aplicar la transformación 3D a la imagen '%s', página %i!"
+
+#: ../sdaps/cover/__init__.py:40
+msgid "sdaps questionnaire"
+msgstr "cuestionario de sdaps"
+
+#: ../sdaps/gui/__init__.py:57
+msgid ""
+"The survey does not have any images! Please add images (and run recognize) "
+"before using the GUI."
+msgstr ""
+"¡La encuesta no tiene ninguna imagen! Por favor añadir imágenes (y ejecutar "
+"reconocimiento) antes de emplear la GUI."
+
+#: ../sdaps/gui/__init__.py:202
+msgid "Page|Invalid"
+msgstr "Página|No válida"
+
+#: ../sdaps/gui/__init__.py:205
+#, python-format
+msgid "Page %i"
+msgid_plural "Page %i"
+msgstr[0] "Página %i"
+msgstr[1] "Página %i"
+
+#: ../sdaps/gui/__init__.py:247
+msgid "Copyright © 2007-2014 The SDAPS Authors"
+msgstr "Copyright © 2007-2014 los autores SDAPS"
+
+#: ../sdaps/gui/__init__.py:249
+msgid "Scripts for data acquisition with paper based surveys"
+msgstr "Guiones para la adquisición de datos en encuestas impresas"
+
+#: ../sdaps/gui/__init__.py:250
+msgid "http://sdaps.org"
+msgstr "http://sdaps.org"
+
+#: ../sdaps/gui/__init__.py:251
+msgid "translator-credits"
+msgstr "Traductor-créditos"
+
+#: ../sdaps/gui/__init__.py:296
+#, python-format
+msgid " of %i"
+msgstr " de %i"
+
+#: ../sdaps/gui/__init__.py:297
+#, python-format
+msgid "Recognition Quality: %.2f"
+msgstr "Calidad de reconocimiento: %.2f"
+
+#: ../sdaps/gui/__init__.py:313
+msgid ""
+"You have reached the first page of the survey. Would you like to go to the "
+"last page?"
+msgstr ""
+"Ha llegado a la primera página de la encuesta. ¿Desea ir a la última página?"
+
+#: ../sdaps/gui/__init__.py:315
+msgid "Go to last page"
+msgstr "Ir a la última página"
+
+#: ../sdaps/gui/__init__.py:337
+msgid ""
+"You have reached the last page of the survey. Would you like to go to the "
+"first page?"
+msgstr ""
+"Ha llegado a la última página de la encuesta. ¿Desea ir a la primera página?"
+
+#: ../sdaps/gui/__init__.py:339
+msgid "Go to first page"
+msgstr "Ir a la primera página"
+
+#: ../sdaps/gui/__init__.py:447
+msgid "Close without saving"
+msgstr "Cerrar sin guardar"
+
+#: ../sdaps/gui/__init__.py:451
+msgid ""
+"<b>Save the project before closing?</b>\n"
+"\n"
+"If you do not save you may loose data."
+msgstr ""
+"<b>¿Guardar el proyecto antes de cerrar?</b>\n"
+"\n"
+"Si no guarda puede perder datos."
+
+#: ../sdaps/gui/main_window.ui.h:1
+msgid "Forward"
+msgstr "Adelante"
+
+#: ../sdaps/gui/main_window.ui.h:2
+msgid "Previous"
+msgstr "Anterior"
+
+#: ../sdaps/gui/main_window.ui.h:3
+msgid "Zoom In"
+msgstr "Acercar"
+
+#: ../sdaps/gui/main_window.ui.h:4
+msgid "Zoom Out"
+msgstr "Alejar"
+
+#: ../sdaps/gui/main_window.ui.h:5
+msgid "SDAPS"
+msgstr "SDAPS"
+
+#: ../sdaps/gui/main_window.ui.h:6
+msgid "_File"
+msgstr "_Archivo"
+
+#: ../sdaps/gui/main_window.ui.h:7
+msgid "_View"
+msgstr "_Ver"
+
+#: ../sdaps/gui/main_window.ui.h:8
+msgid "_Help"
+msgstr "_Ayuda"
+
+#: ../sdaps/gui/main_window.ui.h:9
+msgid "Page Rotated"
+msgstr "Página rotada"
+
+#: ../sdaps/gui/main_window.ui.h:10
+msgid "Sort by Quality"
+msgstr "Ordenar por Calidad"
+
+#: ../sdaps/gui/main_window.ui.h:11
+msgid "label"
+msgstr "etiqueta"
+
+#: ../sdaps/gui/widget_buddies.py:56
+msgid "<b>Global Properties</b>"
+msgstr "<b>Propiedades globales</b>"
+
+#: ../sdaps/gui/widget_buddies.py:60
+msgid "Sheet valid"
+msgstr "Hoja válida"
+
+#: ../sdaps/gui/widget_buddies.py:61
+msgid "Verified"
+msgstr "Verificado"
+
+#: ../sdaps/gui/widget_buddies.py:62
+msgid "Empty"
+msgstr "Vacío"
+
+#: ../sdaps/gui/widget_buddies.py:74 ../sdaps/gui/widget_buddies.py:98
+msgid "<b>Questionnaire ID: </b>"
+msgstr "<b>ID del Cuestionario: </b>"
+
+#: ../sdaps/image/__init__.py:47
+msgid ""
+"It appears you have not build the C extension. Please run \"./setup.py build"
+"\" in the toplevel directory."
+msgstr ""
+"Parece que no se ha construído la extensión C. Ejecute \"./setup.py build\" "
+"en el directorio de nivel superior."
+
+#: ../sdaps/utils/opencv.py:31
+msgid "Cannot convert PDF files as poppler is not installed or usable!"
+msgstr ""
+"¡No se pueden convertir los archivos PDF porque poppler no está instalado o "
+"no se puede utilizar!"
+
+#: ../sdaps/model/survey.py:253
+#, python-format
+msgid "%i sheet"
+msgid_plural "%i sheets"
+msgstr[0] "%i hoja"
+msgstr[1] "%i hojas"
+
+#: ../sdaps/model/survey.py:264
+#, python-format
+msgid "%f seconds per sheet"
+msgstr "%f segundos por hoja"
+
+#: ../sdaps/model/survey.py:290
+msgid ""
+"A questionnaire that is printed in duplex needs an even amount of pages!"
+msgstr ""
+"¡Un cuestionario que se imprime en dúplex necesita una cantidad par de "
+"páginas!"
+
+#: ../sdaps/model/survey.py:294
+msgid ""
+"The 'classic' style only supports a maximum of six pages! Use the 'code128' "
+"style if you require more pages."
+msgstr ""
+"¡El estilo 'clásico' sólo admite un máximo de seis páginas! Utilice el "
+"estilo 'code128' si requiere de más páginas."
+
+#: ../sdaps/model/survey.py:307
+msgid "IDs need to be integers in \"classic\" style!"
+msgstr "¡Los IDs deben ser enteros en el estilo clásico!"
+
+#: ../sdaps/model/survey.py:313
+#, python-format
+msgid "Invalid character %s in questionnaire ID \"%s\" in \"code128\" style!"
+msgstr ""
+"¡Caracter inválido %s en el ID del cuestionario \"%s\" en el estilo "
+"\"code128\"!"
+
+#: ../sdaps/model/survey.py:317
+msgid ""
+"SDAPS cannot draw a questionnaire ID with the \"custom\" style. Do this "
+"yourself somehow!"
+msgstr ""
+"SDAPS no puede dibujar un cuestionario ID en el estilo \"custom\" ¡Debe "
+"Hacerlo por sí mismo de alguna manera!"
+
+#: ../sdaps/model/survey.py:337
+#, python-format
+msgid "Running upgrade routines for file format version %i"
+msgstr ""
+"Ejecutando rutinas de actualización para la versión de formato de archivo %i"
+
+#. in simplex mode every page will have a matrix; it might be a None
+#. matrix though
+#: ../sdaps/recognize/buddies.py:72
+#, python-format
+msgid "%s, %i: Matrix not recognized."
+msgstr "%s, %i: Matriz no reconocida."
+
+#: ../sdaps/recognize/buddies.py:81
+#, python-format
+msgid "%s, %i: Rotation not found."
+msgstr "%s, %i: Rotación no encontrada."
+
+#. Copy the rotation over (if required) and print warning if the rotation is unknown
+#: ../sdaps/recognize/buddies.py:85
+#, python-format
+msgid "Neither %s, %i or %s, %i has a known rotation!"
+msgstr "¡Ni %s, %i o %s, %i tienen una rotación conocida!"
+
+#: ../sdaps/recognize/buddies.py:97
+#, python-format
+msgid "%s, %i: Matrix not recognized (again)."
+msgstr "%s, %i: Matriz no reconocida (otra vez)."
+
+#: ../sdaps/recognize/buddies.py:111
+#, python-format
+msgid "%s, %i: Could not get page number."
+msgstr "%s, %i: No se pudo obtener el número de página."
+
+#. Whoa, that should not happen.
+#: ../sdaps/recognize/buddies.py:131
+#, python-format
+msgid "Neither %s, %i or %s, %i has a known page number!"
+msgstr "¡Ni %s, %i o %s, %i tienen un número de página conocido!"
+
+#. We don't touch the ignore flag in this case
+#. Simply print a message as this should *never* happen
+#: ../sdaps/recognize/buddies.py:142
+#, python-format
+msgid ""
+"Got a simplex document where two adjacent pages had a known page number. "
+"This should never happen as even simplex scans are converted to duplex by "
+"inserting dummy pages. Maybe you did a simplex scan but added it in duplex "
+"mode? The pages in question are %s, %i and %s, %i."
+msgstr ""
+"Se obtuvo un documento simple donde dos páginas adyacentes tenían un número "
+"de página conocido. Esto nunca debe suceder dado que  incluso exploraciones "
+"simples se convierten en dúplex insertando páginas vacías. ¿Tal vez se "
+"realizó un escáneo simple pero se añadió en modo duplex? Las páginas en "
+"cuestión son %s, %i y %s, %i."
+
+#: ../sdaps/recognize/buddies.py:159
+#, python-format
+msgid "Images %s, %i and %s, %i do not have consecutive page numbers!"
+msgstr ""
+"¡Las imágenes %s, %i y %s, %i no tienen números de página consecutivos!"
+
+#: ../sdaps/recognize/buddies.py:175
+#, python-format
+msgid "No page number for page %s, %i exists."
+msgstr "No existe número de página para la página %s, %i."
+
+#: ../sdaps/recognize/buddies.py:180
+#, python-format
+msgid "Page number for page %s, %i already used by another image."
+msgstr ""
+"El número de página para la página %s, %i ya es utilizado por otra imagen."
+
+#: ../sdaps/recognize/buddies.py:186
+#, python-format
+msgid "Page number %i for page %s, %i is out of range."
+msgstr "El número de página %i para la página %s, %i está fuera de rango."
+
+#: ../sdaps/recognize/buddies.py:203
+#, python-format
+msgid "%s, %i: Could not read survey ID, but should be able to."
+msgstr ""
+"%s, %i: No se pudo leer el ID del reconocimiento, pero debiera ser capaz de "
+"hacerse."
+
+#: ../sdaps/recognize/buddies.py:207
+#, python-format
+msgid "Could not read survey ID of either %s, %i or %s, %i!"
+msgstr "No se pudo leer el ID de la encuesta de %s, %i o %s, %i!"
+
+#. Broken survey ID ...
+#: ../sdaps/recognize/buddies.py:214
+#, python-format
+msgid "Got a wrong survey ID (%s, %i)! It is %s, but should be %i."
+msgstr ""
+"Se obtuvo una identificación equivocada de la encuesta (%s, %i)! Es %s, pero "
+"debe ser %i."
+
+#: ../sdaps/recognize/buddies.py:236
+#, python-format
+msgid "%s, %i: Could not read questionnaire ID, but should be able to."
+msgstr ""
+"%s, %i: No se pudo leer el ID del cuestionario, pero usted debe ser capaz de "
+"hacerlo."
+
+#: ../sdaps/recognize/buddies.py:240
+#, python-format
+msgid "Could not read questionnaire ID of either %s, %i or %s, %i!"
+msgstr "No se pudo leer el ID del cuestionario de %s, %i o %s, %i!"
+
+#: ../sdaps/recognize/buddies.py:267
+msgid ""
+"Got different IDs on different pages for at least one sheet! Do *NOT* try to "
+"use filters with this survey! You have to run a \"reorder\" step for this to "
+"work properly!"
+msgstr ""
+"¡Se obtuvieron diferentes IDs en las diferentes páginas para por lo menos "
+"una hoja! *NO* intentar utilizar filtros con esta encuesta! ¡Tiene que "
+"ejecutar el paso de \"reordenar\" para que ello funcione correctamente!"
+
+#: ../sdaps/recognize/buddies.py:323
+msgid "No style buddy loaded. This needs to be done for the \"custom\" style!"
+msgstr ""
+"No se ha cargado definición de estilo. ¡Esto se debe hacer para el estilo "
+"\"custom\"!"
+
+#: ../sdaps/report/answers.py:185
+#, python-format
+msgid "Answers: %i"
+msgstr "Respuestas: %i"
+
+#: ../sdaps/report/answers.py:187
+#, python-format
+msgid "Mean: %.2f"
+msgstr "Media: %.2f"
+
+#: ../sdaps/report/answers.py:189
+#, python-format
+msgid "Standard Deviation: %.2f"
+msgstr "Desviación Típica: %.2f"
+
+#: ../sdaps/report/__init__.py:75 ../sdaps/reporttex/__init__.py:140
+msgid "Turned in Questionnaires"
+msgstr "Cantidad de cuestionarios"
+
+#: ../sdaps/report/__init__.py:92 ../sdaps/reporttex/__init__.py:139
+msgid "sdaps report"
+msgstr "informe sdaps"
+
+#: ../sdaps/setup/buddies.py:61
+#, python-format
+msgid "Head %(l0)i got no title."
+msgstr "La cabecera %(l0)i no tiene título."
+
+#: ../sdaps/setup/buddies.py:74
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got no question."
+msgstr "%(class)s %(l0)i.%(l1)i no contiene preguntas."
+
+#: ../sdaps/setup/buddies.py:120
+#, python-format
+msgid "Error in question \"%s\""
+msgstr "Error en la pregunta \"%s\""
+
+#: ../sdaps/setup/buddies.py:123
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got no boxes."
+msgstr "%(class)s %(l0)i.%(l1)i no tiene casillas."
+
+#: ../sdaps/setup/buddies.py:148
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got not exactly two answers."
+msgstr "%(class)s %(l0)i.%(l1)i no tiene exactamente dos respuestas."
+
+#: ../sdaps/setup/buddies.py:170
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got not exactly one box."
+msgstr "%(class)s %(l0)i.%(l1)i no tiene exactamente una casilla."
+
+#: ../sdaps/setupodt/__init__.py:45 ../sdaps/setuptex/__init__.py:68
+msgid "The survey directory already exists."
+msgstr "El directorio de la encuesta ya existe."
+
+#: ../sdaps/setupodt/__init__.py:50
+#, python-format
+msgid ""
+"Unknown file type (%s). questionnaire_odt should be application/vnd.oasis."
+"opendocument.text."
+msgstr ""
+"Tipo de archivo desconocido (%s). El questionnaire_odt debe ser del tipo "
+"application/vnd.oasis.opendocument.text."
+
+#: ../sdaps/setupodt/__init__.py:55
+#, python-format
+msgid "Unknown file type (%s). questionnaire_pdf should be application/pdf."
+msgstr ""
+"Tipo de archivo desconocido (%s). questionnaire_pdf debe ser del tipo "
+"application/pdf."
+
+#: ../sdaps/setupodt/__init__.py:61 ../sdaps/setuptex/__init__.py:79
+#, python-format
+msgid "Unknown file type (%s). additionalqobjects should be text/plain."
+msgstr ""
+"Tipo de archivo desconocido (%s). additionalqobjects debe ser del tipo text/"
+"plain."
+
+#: ../sdaps/setupodt/__init__.py:90
+msgid "Caught an Exception while parsing the ODT file. The current state is:"
+msgstr ""
+"Se produjo una excepción al analizar el archivo ODT. El estado actual es:"
+
+#: ../sdaps/setupodt/__init__.py:93
+msgid ""
+"If the dependencies for the \"annotate\" command are installed, then an "
+"annotated version will be created next to the original PDF file."
+msgstr ""
+"Si las dependencias para el comando \"annotate\" están instaladas, se creará "
+"una versión con anotaciones junto al archivo PDF original."
+
+#: ../sdaps/setupodt/__init__.py:123 ../sdaps/setuptex/__init__.py:147
+msgid ""
+"Some combination of options and project properties do not work. Aborted "
+"Setup."
+msgstr ""
+"Alguna combinación de opciones y propiedades del proyecto no funciona. "
+"Instalación interrumpida."
+
+#: ../sdaps/setupodt/boxesparser.py:121 ../sdaps/setupodt/boxesparser.py:190
+#, python-format
+msgid ""
+"Warning: Ignoring a box (page: %i, x: %.1f, y: %.1f, width: %.1f, height: "
+"%.1f)."
+msgstr ""
+"Atención: Ignorando una caja (página: %i, x: %.1f, y: %.1f, anchura: %.1f, "
+"altura: %.1f)."
+
+#: ../sdaps/stamp/__init__.py:38
+msgid ""
+"You may not specify the number of sheets for this survey. All questionnaires "
+"will be identical as the survey has been configured to not use questionnaire "
+"IDs for each sheet."
+msgstr ""
+"No debe especificar el número de hojas para esta encuesta. Todos los "
+"cuestionarios serán idénticos, ya que se ha especificado que no se usen IDs "
+"de cuestionario en cada hoja."
+
+#: ../sdaps/stamp/__init__.py:76
+msgid ""
+"This survey has been configured to use questionnaire IDs. Each questionnaire "
+"will be unique. You need to use on of the options to add new IDs or use the "
+"existing ones."
+msgstr ""
+"Se ha configurado que esta encuesta utilice IDs de cuestionario. Cada "
+"cuestionario será único. Por tanto, debe indicar si se utilizarán nuevos IDs "
+"o los ya almacenados."
+
+#: ../sdaps/stamp/generic.py:37
+#, python-format
+msgid "Survey-ID: %i"
+msgstr "ID de Encuesta: %i"
+
+#: ../sdaps/stamp/generic.py:51
+#, python-format
+msgid "Questionnaire-ID: %i"
+msgstr "ID de Cuestionario: %i"
+
+#: ../sdaps/stamp/generic.py:336
+msgid ""
+"You need to have either pdftk or pyPdf installed. pdftk is the faster method."
+msgstr "Debe tener instalado pdftk o pyPdf. pdftk es el método más rápido."
+
+#. bottomup = False =>(0, 0) is the upper left corner
+#: ../sdaps/stamp/generic.py:352
+#, python-format
+msgid "Creating stamp PDF for %i sheet"
+msgid_plural "Creating stamp PDF for %i sheets"
+msgstr[0] "Estampando PDF para la hoja %i"
+msgstr[1] "Estampando PDF para las hojas %i"
+
+#: ../sdaps/stamp/generic.py:410 ../sdaps/stamp/generic.py:543
+#, python-format
+msgid "%i sheet; %f seconds per sheet"
+msgid_plural "%i sheet; %f seconds per sheet"
+msgstr[0] "%i hoja; %f segundos por hoja"
+msgstr[1] "%i hojas; %f segundos por hoja"
+
+#. Merge using pdftk
+#: ../sdaps/stamp/generic.py:419
+msgid "Stamping using pdftk"
+msgstr "Estampando utilizando pdftk"
+
+#. Shortcut if we only have one sheet.
+#. In this case form data in the PDF will *not* break, in
+#. the other code path it *will* break.
+#: ../sdaps/stamp/generic.py:426
+msgid "pdftk: Overlaying the original PDF with the markings."
+msgstr "pdftk: Creando una nueva capa en el PDF original con las marcas."
+
+#: ../sdaps/stamp/generic.py:435
+#, python-format
+msgid "pdftk: Splitting out page %d of each sheet."
+msgid_plural "pdftk: Splitting out page %d of each sheet."
+msgstr[0] "pdftk: Extrayendo la página %d de cada hoja."
+msgstr[1] "pdftk: Extrayendo las páginas %d de cada hoja."
+
+#: ../sdaps/stamp/generic.py:449
+msgid "pdftk: Splitting the questionnaire for watermarking."
+msgstr "pdftk: Dividiendo el cuestionario para la marca de agua."
+
+#: ../sdaps/stamp/generic.py:460 ../sdaps/stamp/generic.py:469
+#, python-format
+msgid "pdftk: Watermarking page %d of all sheets."
+msgid_plural "pdftk: Watermarking page %d of all sheets."
+msgstr[0] "pdftk: Creando marca de agua en la página %d de todas las hojas."
+msgstr[1] "pdftk: Creando marca de agua en las páginas %d de todas las hojas."
+
+#: ../sdaps/stamp/generic.py:492
+msgid "pdftk: Assembling everything into the final PDF."
+msgstr "pdftk: Uniendo todo en el PDF final."
+
+#: ../sdaps/stamp/generic.py:529
+msgid "Stamping using pyPdf. For faster stamping, install pdftk."
+msgstr "Estampando usando pyPdf. Para estampación más rápida, instale pdftk."
+
+#: ../sdaps/stamp/latex.py:21
+msgid ""
+"There should be no need to stamp a SDAPS Project that uses LaTeX and does "
+"not have different questionnaire IDs printed on each sheet.\n"
+"I am going to do so anyways."
+msgstr ""
+"No debería ser necesario estampar un proyecto SDAPS que utiliza LaTeX y no "
+"tiene diferentes IDs de cuestionario impreso en cada hoja.↵\n"
+"Lo voy a hacer igualmente."
+
+#: ../sdaps/stamp/latex.py:43
+#, python-format
+msgid "Running %s now twice to generate the stamped questionnaire."
+msgstr "Ejecutando %s dos veces para generar el cuestionario estampado."
+
+#: ../sdaps/stamp/latex.py:47 ../sdaps/setuptex/__init__.py:119
+#: ../sdaps/setuptex/__init__.py:158 ../sdaps/reporttex/__init__.py:161
+#, python-format
+msgid "Error running \"%s\" to compile the LaTeX file."
+msgstr "Error ejecutando \"%s\" para compilar el archivo LaTeX."
+
+#: ../sdaps/stamp/latex.py:53
+#, python-format
+msgid ""
+"An error occured during creation of the report. Temporary files left in '%s'."
+msgstr ""
+"Se produjo un error durante la creación del informe. Los archivos temporales "
+"se hallan en '%s'."
+
+#: ../sdaps/setuptex/__init__.py:73
+#, python-format
+msgid "Unknown file type (%s). questionnaire_tex should be of type text/x-tex."
+msgstr ""
+"Tipo de archivo desconocido (%s).  questionnaire_tex debería ser del tipo "
+"text/x-tex."
+
+#: ../sdaps/setuptex/__init__.py:74
+msgid "Will keep going, but expect failure!"
+msgstr "¡Se continuará, pero se esperan errores!"
+
+#: ../sdaps/setuptex/__init__.py:115 ../sdaps/setuptex/__init__.py:153
+#, python-format
+msgid "Running %s now twice to generate the questionnaire."
+msgstr "Ejecutando %s dos veces para generar el cuestionario."
+
+#: ../sdaps/setuptex/__init__.py:133
+msgid "Caught an Exception while parsing the SDAPS file. The current state is:"
+msgstr ""
+"Se produjo una excepción al analizar el archivo SDAPS. El estado actual es:"
+
+#: ../sdaps/setuptex/__init__.py:174
+msgid ""
+"An error occured in the setup routine. The survey directory still exists. "
+"You can for example check the questionnaire.log file for LaTeX compile "
+"errors."
+msgstr ""
+"Se produjo un error durante la instalación. El directorio de la encuesta "
+"todavía existe. Puede ver por ejemplo el archivo questionnaire.log para "
+"verificar errores de compilación de LaTeX."
+
+#: ../sdaps/reporttex/__init__.py:107
+msgid "author|Unknown"
+msgstr "autor|Desconocido"
+
+#: ../sdaps/reporttex/__init__.py:138
+msgid "tex language|english"
+msgstr "lenguaje tex|inglés"
+
+#: ../sdaps/reporttex/__init__.py:154
+#, python-format
+msgid "The TeX project with the report data is located at '%s'."
+msgstr "El proyecto TeX con los datos del informe se halla en '%s'."
+
+#: ../sdaps/reporttex/__init__.py:157
+#, python-format
+msgid "Running %s now twice to generate the report."
+msgstr "Ejecutando %s dos veces para generar el informe."
+
+#: ../sdaps/reporttex/__init__.py:167
+#, python-format
+msgid "An occured during creation of the report. Temporary files left in '%s'."
+msgstr ""
+"Se produjo un error durante la creación del informe. Los archivos temporales "
+"se hallan en '%s'."
diff --git a/po/fi.po b/po/fi.po
new file mode 100644
index 0000000..da192de
--- /dev/null
+++ b/po/fi.po
@@ -0,0 +1,1111 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: SDAPS\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-03-13 19:45+0100\n"
+"PO-Revision-Date: 2014-08-17 13:09+0200\n"
+"Last-Translator: Joonas Joensuu <joonas.joensuu at gmail.com>\n"
+"Language-Team: Finnish <https://hosted.weblate.org/projects/sdaps/master/fi/"
+">\n"
+"Language: fi\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 1.10-dev\n"
+
+#: ../sdaps/script.py:41
+msgid "SDAPS -- Paper based survey tool."
+msgstr "SDAPS -- Paperipohjainen kyselytyokalu."
+
+#: ../sdaps/script.py:45
+msgid "project directory|The SDAPS project."
+msgstr "projektin hakemisto|SDAPS-projekti."
+
+#: ../sdaps/script.py:46
+msgid "command list|Commands:"
+msgstr "komentolistaus|Komennot:"
+
+#: ../sdaps/add/__init__.py:55
+#, python-format
+msgid ""
+"Invalid input file %s. You need to specify a (multipage) monochrome TIFF as "
+"input."
+msgstr ""
+"Virheellinen syötetiedosto %s. Syötetiedosto pitää olla muodossa "
+"(monisivuinen) yksivärinen TIFF."
+
+#: ../sdaps/add/__init__.py:67
+#, python-format
+msgid ""
+"Not adding %s because it has a wrong page count (needs to be a mulitple of "
+"%i)."
+msgstr ""
+"Ei lisätä %s, koska sen sivumäärä on väärä (tarvitsee olla kerroin luvusta "
+"%i)."
+
+#: ../sdaps/boxgallery/__init__.py:108
+#, fuzzy, python-format
+msgid "Rendering boxgallery for metric \"%s\"."
+msgstr "Renderöidään laatikkogalleria metriseksi \"%s\"."
+
+#: ../sdaps/log.py:37
+msgid "Warning: "
+msgstr "Varoitus: "
+
+#: ../sdaps/log.py:41
+msgid "Error: "
+msgstr "Virhe: "
+
+#: ../sdaps/cmdline/add.py:31
+msgid "Add scanned questionnaires to the survey."
+msgstr "Lisää skannatut kyselylomakkeet kyselyyn."
+
+#: ../sdaps/cmdline/add.py:32
+#, fuzzy
+msgid ""
+"This command is used to add scanned images to the survey.\n"
+"    The image data needs to be a (multipage) 300dpi monochrome TIFF file. "
+"You\n"
+"    may choose not to copy the data into the project directory. In that "
+"case\n"
+"    the data will be referenced using a relative path."
+msgstr ""
+"Tällä komennolla voit lisätä skannattuja kuvia kyselyyn.\n"
+"    Kuvatiedodoston tulee olla (monisivuinen) 300dpi mustavalko-TIFF-"
+"tiedosto.\n"
+"    Voit olla kopioimatta tiedot projektin hakemistoon.\n"
+"    Tässä tapauksessa tietoja käsitellään käyttämällä suhteellista polkua."
+
+#: ../sdaps/cmdline/add.py:38
+msgid "Convert given files and add the result."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:43 ../sdaps/cmdline/convert.py:39
+msgid ""
+"Do a 3D-transformation after finding the corner marks. If the\n"
+"        corner marks are not found then the image will be added as-is."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:49
+msgid ""
+"Force adding the images even if the page count is wrong (only use if you "
+"know what you are doing)."
+msgstr ""
+"Pakota käyttämään kuvia, vaikka sivumäärä olisikin väärin (käytä vain, jos "
+"tiedä mitä olet tekemässä)."
+
+#: ../sdaps/cmdline/add.py:53
+msgid "Copy the files into the directory (default)."
+msgstr "Kopioi tiedostot hakemistoon (oletus)."
+
+#: ../sdaps/cmdline/add.py:58
+msgid "Do not copy the files into the directory."
+msgstr "Älä kopioi tiedostoja hakemistoon."
+
+#: ../sdaps/cmdline/add.py:62
+msgid ""
+"Images contain a duplex scan of a simplex questionnaire (default: simplex "
+"scan)."
+msgstr ""
+"Kuvat sisältävät kaksipuolisen skannauksen yksipuolisesta kyselykaavakkeesta "
+"(oletus: yksipuolinen skannaus)."
+
+#: ../sdaps/cmdline/add.py:68 ../sdaps/cmdline/convert.py:49
+msgid "A number of TIFF image files."
+msgstr "TIFF-kuvatiedostojen lukumäärä."
+
+#: ../sdaps/cmdline/add.py:94
+msgid "The --no-copy option is not compatible with --convert!"
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:103
+msgid "Converting input files into a single temporary file."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:115
+msgid ""
+"The page count of the created temporary file does not work with this survey."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:120
+msgid "Running the conversion failed."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:125
+#, python-format
+msgid "Processing %s"
+msgstr "Käsittelee %s"
+
+#: ../sdaps/cmdline/add.py:129
+msgid "Done"
+msgstr "Valmis"
+
+#: ../sdaps/cmdline/annotate.py:28
+#, fuzzy
+msgid "Annotate the questionnaire and show the recognized positions."
+msgstr "Lisää huomautuksia kyselykaavakkeeseen ja näytä tunnistetut sijainnit."
+
+#: ../sdaps/cmdline/annotate.py:29
+#, fuzzy
+msgid ""
+"This command is mainly a debug utility. It creates an\n"
+"    annotated version of the questionnaire, with the information that SDAPS\n"
+"    knows about it overlayed on top."
+msgstr ""
+"Tämä komento on pääasiassa virheenkorjauksen apuohjelma. Se luo \n"
+" huomautuksilla varustetun version kyselylomakkeesta ja \n"
+" lisää SDAPS:in tietämän informaation sen päälle."
+
+#: ../sdaps/cmdline/boxgallery.py:31
+msgid "Create PDFs with boxes sorted by the detection heuristics."
+msgstr ""
+"Luo PDF-tiedostot, joissa laatikot on lajiteltu tunnistusheuristiikan "
+"mukaisesti."
+
+#: ../sdaps/cmdline/boxgallery.py:32
+msgid ""
+"SDAPS uses multiple heuristics to detect determine the\n"
+"    state of checkboxes. There is a list for each heuristic giving the "
+"expected\n"
+"    state and the quality of the value (see defs.py). Using this command a "
+"PDF\n"
+"    will be created for each of the heuristics so that one can adjust the\n"
+"    values."
+msgstr ""
+
+#: ../sdaps/cmdline/boxgallery.py:40
+msgid ""
+"Reruns part of the recognition process and retrieves debug images from this "
+"step."
+msgstr ""
+
+#: ../sdaps/cmdline/convert.py:30
+msgid "Convert a set of images to the correct image format."
+msgstr ""
+
+#: ../sdaps/cmdline/convert.py:31
+msgid ""
+"This command can be used if you scanned files in a different\n"
+"        mode than the expected monochrome TIFF file. All the given files "
+"will\n"
+"        be loaded, converted to monochrome and stored into a multipage 1bpp\n"
+"        TIFF file. Optionally you can select that a 3D transformation "
+"should\n"
+"        be performed, using this it may be possible to work with photos of\n"
+"        questionnaires instead of scans."
+msgstr ""
+
+#: ../sdaps/cmdline/convert.py:45
+msgid "The location of the output file. Must be given."
+msgstr ""
+
+#: ../sdaps/cmdline/convert.py:58
+msgid "No output filename specified!"
+msgstr ""
+
+#: ../sdaps/cmdline/cover.py:27
+msgid "Create a cover for the questionnaires."
+msgstr ""
+
+#: ../sdaps/cmdline/cover.py:28
+msgid ""
+"This command creates a cover page for questionnaires. All\n"
+"    the metadata of the survey will be printed on the page."
+msgstr ""
+
+#: ../sdaps/cmdline/cover.py:31
+#, python-format
+msgid "Filename to store the data to (default: cover_%%i.pdf)"
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:34
+msgid "Import or export data to/from CSV files."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:35
+msgid ""
+"Import or export data to/from a CSV file. The first line\n"
+"    is a header which defines questionnaire_id and global_id, and a column\n"
+"    for each checkbox and textfield. Note that the import is currently very\n"
+"    limited, as you need to specifiy the questionnaire ID to select the "
+"sheet\n"
+"    which should be updated."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:44
+msgid "Export data to CSV file."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:46
+#, python-format
+msgid "Filename to store the data to (default: data_%%i.csv)"
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:48
+msgid "The delimiter used in the CSV file (default ',')"
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:52 ../sdaps/cmdline/report.py:31
+#: ../sdaps/cmdline/reporttex.py:52
+msgid "Filter to only export a partial dataset."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:54
+msgid "Export images of freeform fields."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:60
+msgid "Export an image for each question that includes all boxes."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:66
+msgid "Export the recognition quality for each checkbox."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:74
+msgid "Import data to from a CSV file."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:76
+msgid "The file to import."
+msgstr ""
+
+#: ../sdaps/cmdline/gui.py:28
+msgid "Launch a gui. You can view and alter the (recognized) answers with it."
+msgstr ""
+
+#: ../sdaps/cmdline/gui.py:29
+msgid ""
+"This command launches a graphical user interface that can\n"
+"    be used to correct answer. You need to run \"recognize\" before using "
+"it.\n"
+"    "
+msgstr ""
+
+#: ../sdaps/cmdline/gui.py:34
+msgid "Filter to only show a partial dataset."
+msgstr ""
+
+#: ../sdaps/cmdline/ids.py:29
+msgid "Export and import questionnaire IDs."
+msgstr ""
+
+#: ../sdaps/cmdline/ids.py:30
+msgid ""
+"This command can be used to import and export questionnaire\n"
+"    IDs. It only makes sense in projects where such an ID is printed on the\n"
+"    questionnaire. Note that you can also add IDs by using the stamp "
+"command,\n"
+"    which will give you the PDF at the same time."
+msgstr ""
+
+#: ../sdaps/cmdline/ids.py:35
+#, python-format
+msgid "Filename to store the data to (default: ids_%%i)"
+msgstr ""
+
+#: ../sdaps/cmdline/ids.py:38
+msgid "Add IDs to the internal list from the specified file."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:28
+msgid "Display and modify metadata of project."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:29
+msgid ""
+"This command lets you modify the metadata of the SDAPS\n"
+"    project. You can modify, add and remove arbitrary keys that will be "
+"printed\n"
+"    on the report. The only key that always exist is \"title\".\n"
+"    If no key is given then a list of defined keys is printed."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:36
+msgid "The key to display or modify."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:40
+msgid "Set the given key to this value."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:44
+msgid "Delete the key and value pair."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:68
+msgid "Existing fields:\n"
+msgstr ""
+
+#: ../sdaps/cmdline/recognize.py:28
+msgid "Run the optical mark recognition."
+msgstr ""
+
+#: ../sdaps/cmdline/recognize.py:29
+msgid ""
+"Iterates over all images and runs the optical mark\n"
+"    recognition. It will reevaluate sheets even if \"recognize\" has "
+"already\n"
+"    run or manual changes were made."
+msgstr ""
+
+#: ../sdaps/cmdline/recognize.py:34
+msgid ""
+"Only identify the page properties, but don't recognize the checkbox states."
+msgstr ""
+
+#: ../sdaps/cmdline/recognize.py:39
+msgid ""
+"Rerun the recognition for all pages. The default is to skip all pages that "
+"were recognized or verified already."
+msgstr ""
+
+#: ../sdaps/cmdline/reorder.py:26
+msgid "Reorder pages according to questionnaire ID."
+msgstr ""
+
+#: ../sdaps/cmdline/reorder.py:27
+msgid ""
+"This command reorders all pages according to the already\n"
+"    recognized questionnaire ID. To use it add all the files to the "
+"project,\n"
+"    then run a partial recognition using \"recognize --identify\". After "
+"this\n"
+"    you have to run this command to reorder the data for the real "
+"recognition.\n"
+"    "
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:26
+msgid "Create a PDF report."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:27
+msgid ""
+"This command creates a PDF report using reportlab that\n"
+"    contains statistics and if selected the freeform fields."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:33
+msgid "Create a filtered report for every checkbox."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:36
+msgid "Short format (without freeform text fields)."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:41
+msgid "Detailed output. (default)"
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:47 ../sdaps/cmdline/reporttex.py:30
+msgid ""
+"Do not include original images in the report. This is useful if there are "
+"privacy concerns."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:52 ../sdaps/cmdline/reporttex.py:35
+msgid "Do not use substitutions instead of images."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:58 ../sdaps/cmdline/reporttex.py:46
+msgid "The paper size used for the output (default: locale dependent)"
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:61 ../sdaps/cmdline/reporttex.py:49
+#, python-format
+msgid "Filename to store the data to (default: report_%%i.pdf)"
+msgstr ""
+
+#: ../sdaps/cmdline/reporttex.py:26
+msgid "Create a PDF report using LaTeX."
+msgstr ""
+
+#: ../sdaps/cmdline/reporttex.py:27
+msgid ""
+"This command creates a PDF report using LaTeX that\n"
+"    contains statistics and freeform fields."
+msgstr ""
+
+#: ../sdaps/cmdline/reporttex.py:41
+msgid "Save the generated TeX files instead of the final PDF."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:28
+msgid "Create a new survey using an ODT document."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:29
+msgid ""
+"Create a new surevey from an ODT document. The PDF export\n"
+"    of the file needs to be specified at the same time. SDAPS will import\n"
+"    metadata (properties) and the title from the ODT document."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:34
+msgid "The ODT Document"
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:36
+msgid "PDF export of the ODT document"
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:39 ../sdaps/cmdline/setuptex.py:39
+msgid "Additional questions that are not part of the questionnaire."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:48
+msgid "Enable printing of the survey ID (default)."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:52
+msgid "Disable printing of the survey ID."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:57
+msgid "Enable printing of the questionnaire ID."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:61
+msgid "Disable printing of the questionnaire ID (default)."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:65
+msgid ""
+"Set an additional global ID for tracking. This can can only be used in the "
+"\"code128\" style."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:70
+msgid ""
+"The stamping style to use. Should be either \"classic\" or \"code128\". Use "
+"\"code128\" for more features."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:76
+msgid "The mode to use when detecting checkboxes."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:82
+msgid ""
+"Use duplex mode (ie. only print the IDs on the back side). Requires a "
+"mulitple of two pages."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:87
+msgid ""
+"Use simplex mode. IDs are printed on each page. You need a simplex scan "
+"currently!"
+msgstr ""
+
+#: ../sdaps/cmdline/setuptex.py:27
+msgid "Create a new survey using a LaTeX document."
+msgstr ""
+
+#: ../sdaps/cmdline/setuptex.py:28
+msgid ""
+"Create a new survey from a LaTeX document. You need to\n"
+"    be using the SDAPS class. All the metadata and options for the project\n"
+"    can be set inside the LaTeX document."
+msgstr ""
+
+#: ../sdaps/cmdline/setuptex.py:33
+msgid "The LaTeX Document"
+msgstr ""
+
+#: ../sdaps/cmdline/setuptex.py:35
+msgid ""
+"Additional files that are required by the LaTeX document and need to be "
+"copied into the project directory."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:26
+msgid "Add marks for automatic processing."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:27
+msgid ""
+"This command creates the printable document. Depending on\n"
+"    the projects setting you are required to specifiy a source for "
+"questionnaire\n"
+"    IDs."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:33
+msgid ""
+"If using questionnaire IDs, create N questionnaires with randomized IDs."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:36
+msgid ""
+"If using questionnaire IDs, create questionnaires from the IDs read from the "
+"specified file."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:39
+msgid "If using questionnaire IDs, create questionnaires for all stored IDs."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:42
+#, python-format
+msgid "Filename to store the data to (default: stamp_%%i.pdf)"
+msgstr ""
+
+#: ../sdaps/convert/__init__.py:35
+#, python-format
+msgid "Could not apply 3D-transformation to image '%s', page %i!"
+msgstr ""
+
+#: ../sdaps/cover/__init__.py:40
+msgid "sdaps questionnaire"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:57
+msgid ""
+"The survey does not have any images! Please add images (and run recognize) "
+"before using the GUI."
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:202
+msgid "Page|Invalid"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:205
+#, python-format
+msgid "Page %i"
+msgid_plural "Page %i"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/gui/__init__.py:247
+msgid "Copyright © 2007-2014 The SDAPS Authors"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:249
+msgid "Scripts for data acquisition with paper based surveys"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:250
+msgid "http://sdaps.org"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:251
+msgid "translator-credits"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:296
+#, python-format
+msgid " of %i"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:297
+#, python-format
+msgid "Recognition Quality: %.2f"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:313
+msgid ""
+"You have reached the first page of the survey. Would you like to go to the "
+"last page?"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:315
+msgid "Go to last page"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:337
+msgid ""
+"You have reached the last page of the survey. Would you like to go to the "
+"first page?"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:339
+msgid "Go to first page"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:447
+msgid "Close without saving"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:451
+msgid ""
+"<b>Save the project before closing?</b>\n"
+"\n"
+"If you do not save you may loose data."
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:1
+msgid "Forward"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:2
+msgid "Previous"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:3
+msgid "Zoom In"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:4
+msgid "Zoom Out"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:5
+msgid "SDAPS"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:6
+msgid "_File"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:7
+msgid "_View"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:8
+msgid "_Help"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:9
+msgid "Page Rotated"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:10
+msgid "Sort by Quality"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:11
+msgid "label"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:56
+msgid "<b>Global Properties</b>"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:60
+msgid "Sheet valid"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:61
+msgid "Verified"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:62
+msgid "Empty"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:74 ../sdaps/gui/widget_buddies.py:98
+msgid "<b>Questionnaire ID: </b>"
+msgstr ""
+
+#: ../sdaps/image/__init__.py:47
+msgid ""
+"It appears you have not build the C extension. Please run \"./setup.py build"
+"\" in the toplevel directory."
+msgstr ""
+
+#: ../sdaps/utils/opencv.py:31
+msgid "Cannot convert PDF files as poppler is not installed or usable!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:253
+#, python-format
+msgid "%i sheet"
+msgid_plural "%i sheets"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/model/survey.py:264
+#, python-format
+msgid "%f seconds per sheet"
+msgstr ""
+
+#: ../sdaps/model/survey.py:290
+msgid ""
+"A questionnaire that is printed in duplex needs an even amount of pages!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:294
+msgid ""
+"The 'classic' style only supports a maximum of six pages! Use the 'code128' "
+"style if you require more pages."
+msgstr ""
+
+#: ../sdaps/model/survey.py:307
+msgid "IDs need to be integers in \"classic\" style!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:313
+#, python-format
+msgid "Invalid character %s in questionnaire ID \"%s\" in \"code128\" style!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:317
+msgid ""
+"SDAPS cannot draw a questionnaire ID with the \"custom\" style. Do this "
+"yourself somehow!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:337
+#, python-format
+msgid "Running upgrade routines for file format version %i"
+msgstr ""
+
+#. in simplex mode every page will have a matrix; it might be a None
+#. matrix though
+#: ../sdaps/recognize/buddies.py:72
+#, python-format
+msgid "%s, %i: Matrix not recognized."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:81
+#, python-format
+msgid "%s, %i: Rotation not found."
+msgstr ""
+
+#. Copy the rotation over (if required) and print warning if the rotation is unknown
+#: ../sdaps/recognize/buddies.py:85
+#, python-format
+msgid "Neither %s, %i or %s, %i has a known rotation!"
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:97
+#, python-format
+msgid "%s, %i: Matrix not recognized (again)."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:111
+#, python-format
+msgid "%s, %i: Could not get page number."
+msgstr ""
+
+#. Whoa, that should not happen.
+#: ../sdaps/recognize/buddies.py:131
+#, python-format
+msgid "Neither %s, %i or %s, %i has a known page number!"
+msgstr ""
+
+#. We don't touch the ignore flag in this case
+#. Simply print a message as this should *never* happen
+#: ../sdaps/recognize/buddies.py:142
+#, python-format
+msgid ""
+"Got a simplex document where two adjacent pages had a known page number. "
+"This should never happen as even simplex scans are converted to duplex by "
+"inserting dummy pages. Maybe you did a simplex scan but added it in duplex "
+"mode? The pages in question are %s, %i and %s, %i."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:159
+#, python-format
+msgid "Images %s, %i and %s, %i do not have consecutive page numbers!"
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:175
+#, python-format
+msgid "No page number for page %s, %i exists."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:180
+#, python-format
+msgid "Page number for page %s, %i already used by another image."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:186
+#, python-format
+msgid "Page number %i for page %s, %i is out of range."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:203
+#, python-format
+msgid "%s, %i: Could not read survey ID, but should be able to."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:207
+#, python-format
+msgid "Could not read survey ID of either %s, %i or %s, %i!"
+msgstr ""
+
+#. Broken survey ID ...
+#: ../sdaps/recognize/buddies.py:214
+#, python-format
+msgid "Got a wrong survey ID (%s, %i)! It is %s, but should be %i."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:236
+#, python-format
+msgid "%s, %i: Could not read questionnaire ID, but should be able to."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:240
+#, python-format
+msgid "Could not read questionnaire ID of either %s, %i or %s, %i!"
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:267
+msgid ""
+"Got different IDs on different pages for at least one sheet! Do *NOT* try to "
+"use filters with this survey! You have to run a \"reorder\" step for this to "
+"work properly!"
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:323
+msgid "No style buddy loaded. This needs to be done for the \"custom\" style!"
+msgstr ""
+
+#: ../sdaps/report/answers.py:185
+#, python-format
+msgid "Answers: %i"
+msgstr ""
+
+#: ../sdaps/report/answers.py:187
+#, python-format
+msgid "Mean: %.2f"
+msgstr ""
+
+#: ../sdaps/report/answers.py:189
+#, python-format
+msgid "Standard Deviation: %.2f"
+msgstr ""
+
+#: ../sdaps/report/__init__.py:75 ../sdaps/reporttex/__init__.py:140
+msgid "Turned in Questionnaires"
+msgstr ""
+
+#: ../sdaps/report/__init__.py:92 ../sdaps/reporttex/__init__.py:139
+msgid "sdaps report"
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:61
+#, python-format
+msgid "Head %(l0)i got no title."
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:74
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got no question."
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:120
+#, python-format
+msgid "Error in question \"%s\""
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:123
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got no boxes."
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:148
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got not exactly two answers."
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:170
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got not exactly one box."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:45 ../sdaps/setuptex/__init__.py:68
+msgid "The survey directory already exists."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:50
+#, python-format
+msgid ""
+"Unknown file type (%s). questionnaire_odt should be application/vnd.oasis."
+"opendocument.text."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:55
+#, python-format
+msgid "Unknown file type (%s). questionnaire_pdf should be application/pdf."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:61 ../sdaps/setuptex/__init__.py:79
+#, python-format
+msgid "Unknown file type (%s). additionalqobjects should be text/plain."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:90
+msgid "Caught an Exception while parsing the ODT file. The current state is:"
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:93
+msgid ""
+"If the dependencies for the \"annotate\" command are installed, then an "
+"annotated version will be created next to the original PDF file."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:123 ../sdaps/setuptex/__init__.py:147
+msgid ""
+"Some combination of options and project properties do not work. Aborted "
+"Setup."
+msgstr ""
+
+#: ../sdaps/setupodt/boxesparser.py:121 ../sdaps/setupodt/boxesparser.py:190
+#, python-format
+msgid ""
+"Warning: Ignoring a box (page: %i, x: %.1f, y: %.1f, width: %.1f, height: "
+"%.1f)."
+msgstr ""
+
+#: ../sdaps/stamp/__init__.py:38
+msgid ""
+"You may not specify the number of sheets for this survey. All questionnaires "
+"will be identical as the survey has been configured to not use questionnaire "
+"IDs for each sheet."
+msgstr ""
+
+#: ../sdaps/stamp/__init__.py:76
+msgid ""
+"This survey has been configured to use questionnaire IDs. Each questionnaire "
+"will be unique. You need to use on of the options to add new IDs or use the "
+"existing ones."
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:37
+#, python-format
+msgid "Survey-ID: %i"
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:51
+#, python-format
+msgid "Questionnaire-ID: %i"
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:336
+msgid ""
+"You need to have either pdftk or pyPdf installed. pdftk is the faster method."
+msgstr ""
+
+#. bottomup = False =>(0, 0) is the upper left corner
+#: ../sdaps/stamp/generic.py:352
+#, python-format
+msgid "Creating stamp PDF for %i sheet"
+msgid_plural "Creating stamp PDF for %i sheets"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/stamp/generic.py:410 ../sdaps/stamp/generic.py:543
+#, python-format
+msgid "%i sheet; %f seconds per sheet"
+msgid_plural "%i sheet; %f seconds per sheet"
+msgstr[0] ""
+msgstr[1] ""
+
+#. Merge using pdftk
+#: ../sdaps/stamp/generic.py:419
+msgid "Stamping using pdftk"
+msgstr ""
+
+#. Shortcut if we only have one sheet.
+#. In this case form data in the PDF will *not* break, in
+#. the other code path it *will* break.
+#: ../sdaps/stamp/generic.py:426
+msgid "pdftk: Overlaying the original PDF with the markings."
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:435
+#, python-format
+msgid "pdftk: Splitting out page %d of each sheet."
+msgid_plural "pdftk: Splitting out page %d of each sheet."
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/stamp/generic.py:449
+msgid "pdftk: Splitting the questionnaire for watermarking."
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:460 ../sdaps/stamp/generic.py:469
+#, python-format
+msgid "pdftk: Watermarking page %d of all sheets."
+msgid_plural "pdftk: Watermarking page %d of all sheets."
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/stamp/generic.py:492
+msgid "pdftk: Assembling everything into the final PDF."
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:529
+msgid "Stamping using pyPdf. For faster stamping, install pdftk."
+msgstr ""
+
+#: ../sdaps/stamp/latex.py:21
+msgid ""
+"There should be no need to stamp a SDAPS Project that uses LaTeX and does "
+"not have different questionnaire IDs printed on each sheet.\n"
+"I am going to do so anyways."
+msgstr ""
+
+#: ../sdaps/stamp/latex.py:43
+#, python-format
+msgid "Running %s now twice to generate the stamped questionnaire."
+msgstr ""
+
+#: ../sdaps/stamp/latex.py:47 ../sdaps/setuptex/__init__.py:119
+#: ../sdaps/setuptex/__init__.py:158 ../sdaps/reporttex/__init__.py:161
+#, python-format
+msgid "Error running \"%s\" to compile the LaTeX file."
+msgstr ""
+
+#: ../sdaps/stamp/latex.py:53
+#, python-format
+msgid ""
+"An error occured during creation of the report. Temporary files left in '%s'."
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:73
+#, python-format
+msgid "Unknown file type (%s). questionnaire_tex should be of type text/x-tex."
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:74
+msgid "Will keep going, but expect failure!"
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:115 ../sdaps/setuptex/__init__.py:153
+#, python-format
+msgid "Running %s now twice to generate the questionnaire."
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:133
+msgid "Caught an Exception while parsing the SDAPS file. The current state is:"
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:174
+msgid ""
+"An error occured in the setup routine. The survey directory still exists. "
+"You can for example check the questionnaire.log file for LaTeX compile "
+"errors."
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:107
+msgid "author|Unknown"
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:138
+msgid "tex language|english"
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:154
+#, python-format
+msgid "The TeX project with the report data is located at '%s'."
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:157
+#, python-format
+msgid "Running %s now twice to generate the report."
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:167
+#, python-format
+msgid "An occured during creation of the report. Temporary files left in '%s'."
+msgstr ""
diff --git a/po/fr.po b/po/fr.po
new file mode 100644
index 0000000..7b5db7d
--- /dev/null
+++ b/po/fr.po
@@ -0,0 +1,1155 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: SDAPS 0.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-03-13 19:45+0100\n"
+"PO-Revision-Date: 2017-05-30 18:23+0000\n"
+"Last-Translator: Berteh <berteh at gmail.com>\n"
+"Language-Team: French <https://hosted.weblate.org/projects/sdaps/master/fr/>"
+"\n"
+"Language: fr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n > 1;\n"
+"X-Generator: Weblate 2.14.1\n"
+
+#: ../sdaps/script.py:41
+msgid "SDAPS -- Paper based survey tool."
+msgstr "SDAPS -- Sondages papier faciles."
+
+#: ../sdaps/script.py:45
+msgid "project directory|The SDAPS project."
+msgstr "répertoire du projet|Le projet SDAPS."
+
+#: ../sdaps/script.py:46
+msgid "command list|Commands:"
+msgstr "liste des commandes|Commandes :"
+
+#: ../sdaps/add/__init__.py:55
+#, python-format
+msgid ""
+"Invalid input file %s. You need to specify a (multipage) monochrome TIFF as "
+"input."
+msgstr ""
+"Fichier %s non valide. Veuillez donner une image TIFF monochrome (multipage) "
+"en entrée."
+
+#: ../sdaps/add/__init__.py:67
+#, python-format
+msgid ""
+"Not adding %s because it has a wrong page count (needs to be a mulitple of "
+"%i)."
+msgstr ""
+"%s n'a pas été ajouté parce que le nombre de pages est incorrect (doit être "
+"multiple de %i)."
+
+#: ../sdaps/boxgallery/__init__.py:108
+#, python-format
+msgid "Rendering boxgallery for metric \"%s\"."
+msgstr ""
+
+#: ../sdaps/log.py:37
+msgid "Warning: "
+msgstr "Avertissement : "
+
+#: ../sdaps/log.py:41
+msgid "Error: "
+msgstr "Erreur : "
+
+#: ../sdaps/cmdline/add.py:31
+msgid "Add scanned questionnaires to the survey."
+msgstr "Ajouter des questionnaires scannés au sondage."
+
+#: ../sdaps/cmdline/add.py:32
+msgid ""
+"This command is used to add scanned images to the survey.\n"
+"    The image data needs to be a (multipage) 300dpi monochrome TIFF file. "
+"You\n"
+"    may choose not to copy the data into the project directory. In that "
+"case\n"
+"    the data will be referenced using a relative path."
+msgstr ""
+"Cette commande est utilisée pour ajouter des images scannées au sondage.\n"
+"…Les images doivent être un fichier TIFF 300dpi monochrome (multipage). "
+"Vous\n"
+"…pouvez décider de ne pas copier les images dans le répertoire du projet. "
+"Dans\n"
+"…ce cas, les fichiers seront référencés par un chemin relatif."
+
+#: ../sdaps/cmdline/add.py:38
+msgid "Convert given files and add the result."
+msgstr "Convertir les fichiers donnés et ajouter le résultat."
+
+#: ../sdaps/cmdline/add.py:43 ../sdaps/cmdline/convert.py:39
+msgid ""
+"Do a 3D-transformation after finding the corner marks. If the\n"
+"        corner marks are not found then the image will be added as-is."
+msgstr ""
+"Effectuer une transformation 3D après avoir repéré les marques des coins. Si "
+"les\n"
+"\tmarques des coins ne sont pas trouvées l'image est ajoutée en l'état."
+
+#: ../sdaps/cmdline/add.py:49
+msgid ""
+"Force adding the images even if the page count is wrong (only use if you "
+"know what you are doing)."
+msgstr ""
+"Ajouter les images même si le nombre de pages est incorrect (n'utilisez ceci "
+"que si vous savez ce que vous faites)."
+
+#: ../sdaps/cmdline/add.py:53
+msgid "Copy the files into the directory (default)."
+msgstr "Copie les fichiers dans le répertoire (par défaut)."
+
+#: ../sdaps/cmdline/add.py:58
+msgid "Do not copy the files into the directory."
+msgstr "Ne copie pas les fichiers dans le répertoire."
+
+#: ../sdaps/cmdline/add.py:62
+msgid ""
+"Images contain a duplex scan of a simplex questionnaire (default: simplex "
+"scan)."
+msgstr ""
+"Les images contiennent un scan recto-verso d'un questionnaire en recto "
+"simple (par défaut: scan recto simple)."
+
+#: ../sdaps/cmdline/add.py:68 ../sdaps/cmdline/convert.py:49
+msgid "A number of TIFF image files."
+msgstr "Un nombre de fichiers TIFF."
+
+#: ../sdaps/cmdline/add.py:94
+msgid "The --no-copy option is not compatible with --convert!"
+msgstr "L'option --no-copy n'est pas compatible avec --convert !"
+
+#: ../sdaps/cmdline/add.py:103
+msgid "Converting input files into a single temporary file."
+msgstr "Convertir les fichiers d'entrée dans un fichier temporaire unique."
+
+#: ../sdaps/cmdline/add.py:115
+msgid ""
+"The page count of the created temporary file does not work with this survey."
+msgstr ""
+"Le nombre de pages du fichier temporaire n'est pas compatible avec ce "
+"sondage."
+
+#: ../sdaps/cmdline/add.py:120
+msgid "Running the conversion failed."
+msgstr "La conversion a échoué."
+
+#: ../sdaps/cmdline/add.py:125
+#, python-format
+msgid "Processing %s"
+msgstr "Traitement de %s"
+
+#: ../sdaps/cmdline/add.py:129
+msgid "Done"
+msgstr "Fait"
+
+#: ../sdaps/cmdline/annotate.py:28
+msgid "Annotate the questionnaire and show the recognized positions."
+msgstr "Annoter le questionnaire et montrer les positions reconnues."
+
+#: ../sdaps/cmdline/annotate.py:29
+msgid ""
+"This command is mainly a debug utility. It creates an\n"
+"    annotated version of the questionnaire, with the information that SDAPS\n"
+"    knows about it overlayed on top."
+msgstr ""
+"Cette commande est principalement faite pour le débogage. Elle crée une\n"
+"\tversion du questionnaire annotée, comportant les informations \n"
+"\tconnues par SDAPS en surimpression."
+
+#: ../sdaps/cmdline/boxgallery.py:31
+msgid "Create PDFs with boxes sorted by the detection heuristics."
+msgstr "Créer des PDF avec des cadres triés par l'heuristique de détection."
+
+#: ../sdaps/cmdline/boxgallery.py:32
+msgid ""
+"SDAPS uses multiple heuristics to detect determine the\n"
+"    state of checkboxes. There is a list for each heuristic giving the "
+"expected\n"
+"    state and the quality of the value (see defs.py). Using this command a "
+"PDF\n"
+"    will be created for each of the heuristics so that one can adjust the\n"
+"    values."
+msgstr ""
+"SDAPS utilise plusieurs méthodes pour déterminer l'état d'une\n"
+"\tcase à cocher. Il y a une liste pour chacune d'elles donnant l'état\n"
+"\tattendu et la qualité du résultat (voir defs.py). Un fichier PDF\n"
+"\tsera généré pour chaque méthode pouvant influer sur le résultat."
+
+#: ../sdaps/cmdline/boxgallery.py:40
+msgid ""
+"Reruns part of the recognition process and retrieves debug images from this "
+"step."
+msgstr ""
+"Exécute à nouveau une partie du processus de reconnaissance et récupère les "
+"images de débogage depuis cette étape."
+
+#: ../sdaps/cmdline/convert.py:30
+msgid "Convert a set of images to the correct image format."
+msgstr "Convertir un lot d'images dans le bon format."
+
+#: ../sdaps/cmdline/convert.py:31
+msgid ""
+"This command can be used if you scanned files in a different\n"
+"        mode than the expected monochrome TIFF file. All the given files "
+"will\n"
+"        be loaded, converted to monochrome and stored into a multipage 1bpp\n"
+"        TIFF file. Optionally you can select that a 3D transformation "
+"should\n"
+"        be performed, using this it may be possible to work with photos of\n"
+"        questionnaires instead of scans."
+msgstr ""
+"Cette commande peut être utilisée si vous numérisez dans un autre\n"
+"\tformat que le TIFF monochrome. Tous les fichiers doivent être\n"
+"\tchargés et enregistrés dans un seul fichier TIFF monochrome (1bpp).\n"
+"\tPour travailler avec des photos des questionnaires il est possible \n"
+"\tde leur appliquer directement une transformation 3D."
+
+#: ../sdaps/cmdline/convert.py:45
+msgid "The location of the output file. Must be given."
+msgstr "L'emplacement du fichier de sortie. Obligatoire."
+
+#: ../sdaps/cmdline/convert.py:58
+msgid "No output filename specified!"
+msgstr "Pas de nom de fichier de sortie spécifié !"
+
+#: ../sdaps/cmdline/cover.py:27
+msgid "Create a cover for the questionnaires."
+msgstr "Créer une jaquette pour les questionnaires."
+
+#: ../sdaps/cmdline/cover.py:28
+msgid ""
+"This command creates a cover page for questionnaires. All\n"
+"    the metadata of the survey will be printed on the page."
+msgstr ""
+"Cette commande crée une jaquette pour les questionnaires. Toutes\n"
+"\tles métadonnées du sondage y seront imprimées."
+
+#: ../sdaps/cmdline/cover.py:31
+#, python-format
+msgid "Filename to store the data to (default: cover_%%i.pdf)"
+msgstr "Nom du fichier des données (par défaut : cover_%%i.pdf)"
+
+#: ../sdaps/cmdline/csvdata.py:34
+msgid "Import or export data to/from CSV files."
+msgstr "Importer ou exporter des données depuis/vers un fichier CSV."
+
+#: ../sdaps/cmdline/csvdata.py:35
+msgid ""
+"Import or export data to/from a CSV file. The first line\n"
+"    is a header which defines questionnaire_id and global_id, and a column\n"
+"    for each checkbox and textfield. Note that the import is currently very\n"
+"    limited, as you need to specifiy the questionnaire ID to select the "
+"sheet\n"
+"    which should be updated."
+msgstr ""
+"Importer ou exporter des données depuis/vers un fichier CSV. La première "
+"ligne\n"
+"\test un entête contenant questionnaire_id, global_id puis\n"
+"\tchaque case à cocher et champ texte. Notez que l'importation est "
+"actuellement très\n"
+"\tlimitée ; vous devez indiquer l'identifiant du questionnaire pour choisir "
+"la fiche\n"
+"\tqui doit être mise à jour."
+
+#: ../sdaps/cmdline/csvdata.py:44
+msgid "Export data to CSV file."
+msgstr "Exporter les données dans un fichiers CSV."
+
+#: ../sdaps/cmdline/csvdata.py:46
+#, python-format
+msgid "Filename to store the data to (default: data_%%i.csv)"
+msgstr "Nom du fichier des données (par défaut : data_%%i.csv)"
+
+#: ../sdaps/cmdline/csvdata.py:48
+msgid "The delimiter used in the CSV file (default ',')"
+msgstr "Le délimiteur utilisé dans le fichier CSV (par défaut ',')"
+
+#: ../sdaps/cmdline/csvdata.py:52 ../sdaps/cmdline/report.py:31
+#: ../sdaps/cmdline/reporttex.py:52
+msgid "Filter to only export a partial dataset."
+msgstr "Filtrer pour exporter un lot de données partiel."
+
+#: ../sdaps/cmdline/csvdata.py:54
+msgid "Export images of freeform fields."
+msgstr "Exporter les images des champs libres."
+
+#: ../sdaps/cmdline/csvdata.py:60
+msgid "Export an image for each question that includes all boxes."
+msgstr "Exporter une image pour chaque question qui inclut toutes les cases."
+
+#: ../sdaps/cmdline/csvdata.py:66
+msgid "Export the recognition quality for each checkbox."
+msgstr "Exporter la qualité de la reconnaissance pour chaque case à cocher."
+
+#: ../sdaps/cmdline/csvdata.py:74
+msgid "Import data to from a CSV file."
+msgstr "Importer des données depuis vers un fichier CSV."
+
+#: ../sdaps/cmdline/csvdata.py:76
+msgid "The file to import."
+msgstr "Le fichier à importer."
+
+#: ../sdaps/cmdline/gui.py:28
+msgid "Launch a gui. You can view and alter the (recognized) answers with it."
+msgstr ""
+"Lancer l'interface graphique. Vous pouvez y afficher et modifier les "
+"réponses (reconnues)."
+
+#: ../sdaps/cmdline/gui.py:29
+msgid ""
+"This command launches a graphical user interface that can\n"
+"    be used to correct answer. You need to run \"recognize\" before using "
+"it.\n"
+"    "
+msgstr ""
+"Cette commande affiche une interface graphique qui permet\n"
+"\tde corriger une réponse. Auparavant, vous devrez lancer la commande \""
+"reconnaître\".\n"
+"    "
+
+#: ../sdaps/cmdline/gui.py:34
+msgid "Filter to only show a partial dataset."
+msgstr "Filtrer pour ne montrer qu'une partie des données."
+
+#: ../sdaps/cmdline/ids.py:29
+msgid "Export and import questionnaire IDs."
+msgstr "Exportez et importez les identifiants des questionnaires."
+
+#: ../sdaps/cmdline/ids.py:30
+msgid ""
+"This command can be used to import and export questionnaire\n"
+"    IDs. It only makes sense in projects where such an ID is printed on the\n"
+"    questionnaire. Note that you can also add IDs by using the stamp "
+"command,\n"
+"    which will give you the PDF at the same time."
+msgstr ""
+"Cette commande peut être utilisée pour importer et exporter des\n"
+"\tIDs de questionnaire. N'est pertinente que dans les projets où l'ID est \n"
+"\timprimé sur le questionnaire. Vous pouvez également ajouter ID à l’aide\n"
+"\tde la commande stamp, qui génère également le PDF.."
+
+#: ../sdaps/cmdline/ids.py:35
+#, python-format
+msgid "Filename to store the data to (default: ids_%%i)"
+msgstr "Nom de fichier pour stocker les données (par défaut : ids_%%i)"
+
+#: ../sdaps/cmdline/ids.py:38
+msgid "Add IDs to the internal list from the specified file."
+msgstr "Ajouter les IDs à partir du fichier spécifié."
+
+#: ../sdaps/cmdline/info.py:28
+msgid "Display and modify metadata of project."
+msgstr "Affichez et modifiez les métadonnées du projet."
+
+#: ../sdaps/cmdline/info.py:29
+msgid ""
+"This command lets you modify the metadata of the SDAPS\n"
+"    project. You can modify, add and remove arbitrary keys that will be "
+"printed\n"
+"    on the report. The only key that always exist is \"title\".\n"
+"    If no key is given then a list of defined keys is printed."
+msgstr ""
+"Cette commande vous permet de modifier les métadonnées du projet \n"
+"\tSDAPS. Vous pouvez modifier, ajouter et supprimer toutes clés utiles \n"
+"\tqui seront imprimées sur le rapport. La seule clé qui existent toujours "
+"est « title ».\n"
+"\tSi aucune clé n’est donnée une liste de clés définies est imprimée."
+
+#: ../sdaps/cmdline/info.py:36
+msgid "The key to display or modify."
+msgstr "La clé à afficher ou modifier."
+
+#: ../sdaps/cmdline/info.py:40
+msgid "Set the given key to this value."
+msgstr "La valeur à donner à cette clé."
+
+#: ../sdaps/cmdline/info.py:44
+msgid "Delete the key and value pair."
+msgstr "Supprimer la paire clé / valeur."
+
+#: ../sdaps/cmdline/info.py:68
+msgid "Existing fields:\n"
+msgstr "Champs existants :\n"
+
+#: ../sdaps/cmdline/recognize.py:28
+msgid "Run the optical mark recognition."
+msgstr "Exécutez la reconnaissance optique de marques."
+
+#: ../sdaps/cmdline/recognize.py:29
+msgid ""
+"Iterates over all images and runs the optical mark\n"
+"    recognition. It will reevaluate sheets even if \"recognize\" has "
+"already\n"
+"    run or manual changes were made."
+msgstr ""
+"Effectue une itération sur toutes les images et exécute la reconnaissance \n"
+"\toptique de marques. Les pages seront évaluées à nouveau, même si l'option "
+"\n"
+"\trecognize a déjà été exécutée ou si des modifications manuelles ont été "
+"apportées."
+
+#: ../sdaps/cmdline/recognize.py:34
+msgid ""
+"Only identify the page properties, but don't recognize the checkbox states."
+msgstr ""
+"Identifie les propriétés de la page, sans reconnaître la valeur des cases à "
+"cocher."
+
+#: ../sdaps/cmdline/recognize.py:39
+msgid ""
+"Rerun the recognition for all pages. The default is to skip all pages that "
+"were recognized or verified already."
+msgstr ""
+
+#: ../sdaps/cmdline/reorder.py:26
+msgid "Reorder pages according to questionnaire ID."
+msgstr ""
+
+#: ../sdaps/cmdline/reorder.py:27
+msgid ""
+"This command reorders all pages according to the already\n"
+"    recognized questionnaire ID. To use it add all the files to the "
+"project,\n"
+"    then run a partial recognition using \"recognize --identify\". After "
+"this\n"
+"    you have to run this command to reorder the data for the real "
+"recognition.\n"
+"    "
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:26
+msgid "Create a PDF report."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:27
+msgid ""
+"This command creates a PDF report using reportlab that\n"
+"    contains statistics and if selected the freeform fields."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:33
+msgid "Create a filtered report for every checkbox."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:36
+msgid "Short format (without freeform text fields)."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:41
+msgid "Detailed output. (default)"
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:47 ../sdaps/cmdline/reporttex.py:30
+msgid ""
+"Do not include original images in the report. This is useful if there are "
+"privacy concerns."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:52 ../sdaps/cmdline/reporttex.py:35
+msgid "Do not use substitutions instead of images."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:58 ../sdaps/cmdline/reporttex.py:46
+msgid "The paper size used for the output (default: locale dependent)"
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:61 ../sdaps/cmdline/reporttex.py:49
+#, python-format
+msgid "Filename to store the data to (default: report_%%i.pdf)"
+msgstr ""
+
+#: ../sdaps/cmdline/reporttex.py:26
+msgid "Create a PDF report using LaTeX."
+msgstr ""
+
+#: ../sdaps/cmdline/reporttex.py:27
+msgid ""
+"This command creates a PDF report using LaTeX that\n"
+"    contains statistics and freeform fields."
+msgstr ""
+
+#: ../sdaps/cmdline/reporttex.py:41
+msgid "Save the generated TeX files instead of the final PDF."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:28
+msgid "Create a new survey using an ODT document."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:29
+msgid ""
+"Create a new surevey from an ODT document. The PDF export\n"
+"    of the file needs to be specified at the same time. SDAPS will import\n"
+"    metadata (properties) and the title from the ODT document."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:34
+msgid "The ODT Document"
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:36
+msgid "PDF export of the ODT document"
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:39 ../sdaps/cmdline/setuptex.py:39
+msgid "Additional questions that are not part of the questionnaire."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:48
+msgid "Enable printing of the survey ID (default)."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:52
+msgid "Disable printing of the survey ID."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:57
+msgid "Enable printing of the questionnaire ID."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:61
+msgid "Disable printing of the questionnaire ID (default)."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:65
+msgid ""
+"Set an additional global ID for tracking. This can can only be used in the "
+"\"code128\" style."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:70
+msgid ""
+"The stamping style to use. Should be either \"classic\" or \"code128\". Use "
+"\"code128\" for more features."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:76
+msgid "The mode to use when detecting checkboxes."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:82
+msgid ""
+"Use duplex mode (ie. only print the IDs on the back side). Requires a "
+"mulitple of two pages."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:87
+msgid ""
+"Use simplex mode. IDs are printed on each page. You need a simplex scan "
+"currently!"
+msgstr ""
+
+#: ../sdaps/cmdline/setuptex.py:27
+msgid "Create a new survey using a LaTeX document."
+msgstr ""
+
+#: ../sdaps/cmdline/setuptex.py:28
+msgid ""
+"Create a new survey from a LaTeX document. You need to\n"
+"    be using the SDAPS class. All the metadata and options for the project\n"
+"    can be set inside the LaTeX document."
+msgstr ""
+
+#: ../sdaps/cmdline/setuptex.py:33
+msgid "The LaTeX Document"
+msgstr ""
+
+#: ../sdaps/cmdline/setuptex.py:35
+msgid ""
+"Additional files that are required by the LaTeX document and need to be "
+"copied into the project directory."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:26
+msgid "Add marks for automatic processing."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:27
+msgid ""
+"This command creates the printable document. Depending on\n"
+"    the projects setting you are required to specifiy a source for "
+"questionnaire\n"
+"    IDs."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:33
+msgid ""
+"If using questionnaire IDs, create N questionnaires with randomized IDs."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:36
+msgid ""
+"If using questionnaire IDs, create questionnaires from the IDs read from the "
+"specified file."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:39
+msgid "If using questionnaire IDs, create questionnaires for all stored IDs."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:42
+#, python-format
+msgid "Filename to store the data to (default: stamp_%%i.pdf)"
+msgstr ""
+
+#: ../sdaps/convert/__init__.py:35
+#, python-format
+msgid "Could not apply 3D-transformation to image '%s', page %i!"
+msgstr ""
+
+#: ../sdaps/cover/__init__.py:40
+msgid "sdaps questionnaire"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:57
+msgid ""
+"The survey does not have any images! Please add images (and run recognize) "
+"before using the GUI."
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:202
+msgid "Page|Invalid"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:205
+#, python-format
+msgid "Page %i"
+msgid_plural "Page %i"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/gui/__init__.py:247
+msgid "Copyright © 2007-2014 The SDAPS Authors"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:249
+msgid "Scripts for data acquisition with paper based surveys"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:250
+msgid "http://sdaps.org"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:251
+msgid "translator-credits"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:296
+#, python-format
+msgid " of %i"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:297
+#, python-format
+msgid "Recognition Quality: %.2f"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:313
+msgid ""
+"You have reached the first page of the survey. Would you like to go to the "
+"last page?"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:315
+msgid "Go to last page"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:337
+msgid ""
+"You have reached the last page of the survey. Would you like to go to the "
+"first page?"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:339
+msgid "Go to first page"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:447
+msgid "Close without saving"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:451
+msgid ""
+"<b>Save the project before closing?</b>\n"
+"\n"
+"If you do not save you may loose data."
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:1
+msgid "Forward"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:2
+msgid "Previous"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:3
+msgid "Zoom In"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:4
+msgid "Zoom Out"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:5
+msgid "SDAPS"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:6
+msgid "_File"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:7
+msgid "_View"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:8
+msgid "_Help"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:9
+msgid "Page Rotated"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:10
+msgid "Sort by Quality"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:11
+msgid "label"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:56
+msgid "<b>Global Properties</b>"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:60
+msgid "Sheet valid"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:61
+msgid "Verified"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:62
+msgid "Empty"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:74 ../sdaps/gui/widget_buddies.py:98
+msgid "<b>Questionnaire ID: </b>"
+msgstr ""
+
+#: ../sdaps/image/__init__.py:47
+msgid ""
+"It appears you have not build the C extension. Please run \"./setup.py build"
+"\" in the toplevel directory."
+msgstr ""
+
+#: ../sdaps/utils/opencv.py:31
+msgid "Cannot convert PDF files as poppler is not installed or usable!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:253
+#, python-format
+msgid "%i sheet"
+msgid_plural "%i sheets"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/model/survey.py:264
+#, python-format
+msgid "%f seconds per sheet"
+msgstr ""
+
+#: ../sdaps/model/survey.py:290
+msgid ""
+"A questionnaire that is printed in duplex needs an even amount of pages!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:294
+msgid ""
+"The 'classic' style only supports a maximum of six pages! Use the 'code128' "
+"style if you require more pages."
+msgstr ""
+
+#: ../sdaps/model/survey.py:307
+msgid "IDs need to be integers in \"classic\" style!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:313
+#, python-format
+msgid "Invalid character %s in questionnaire ID \"%s\" in \"code128\" style!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:317
+msgid ""
+"SDAPS cannot draw a questionnaire ID with the \"custom\" style. Do this "
+"yourself somehow!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:337
+#, python-format
+msgid "Running upgrade routines for file format version %i"
+msgstr ""
+
+#. in simplex mode every page will have a matrix; it might be a None
+#. matrix though
+#: ../sdaps/recognize/buddies.py:72
+#, python-format
+msgid "%s, %i: Matrix not recognized."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:81
+#, python-format
+msgid "%s, %i: Rotation not found."
+msgstr ""
+
+#. Copy the rotation over (if required) and print warning if the rotation is unknown
+#: ../sdaps/recognize/buddies.py:85
+#, python-format
+msgid "Neither %s, %i or %s, %i has a known rotation!"
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:97
+#, python-format
+msgid "%s, %i: Matrix not recognized (again)."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:111
+#, python-format
+msgid "%s, %i: Could not get page number."
+msgstr ""
+
+#. Whoa, that should not happen.
+#: ../sdaps/recognize/buddies.py:131
+#, python-format
+msgid "Neither %s, %i or %s, %i has a known page number!"
+msgstr ""
+
+#. We don't touch the ignore flag in this case
+#. Simply print a message as this should *never* happen
+#: ../sdaps/recognize/buddies.py:142
+#, python-format
+msgid ""
+"Got a simplex document where two adjacent pages had a known page number. "
+"This should never happen as even simplex scans are converted to duplex by "
+"inserting dummy pages. Maybe you did a simplex scan but added it in duplex "
+"mode? The pages in question are %s, %i and %s, %i."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:159
+#, python-format
+msgid "Images %s, %i and %s, %i do not have consecutive page numbers!"
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:175
+#, python-format
+msgid "No page number for page %s, %i exists."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:180
+#, python-format
+msgid "Page number for page %s, %i already used by another image."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:186
+#, python-format
+msgid "Page number %i for page %s, %i is out of range."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:203
+#, python-format
+msgid "%s, %i: Could not read survey ID, but should be able to."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:207
+#, python-format
+msgid "Could not read survey ID of either %s, %i or %s, %i!"
+msgstr ""
+
+#. Broken survey ID ...
+#: ../sdaps/recognize/buddies.py:214
+#, python-format
+msgid "Got a wrong survey ID (%s, %i)! It is %s, but should be %i."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:236
+#, python-format
+msgid "%s, %i: Could not read questionnaire ID, but should be able to."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:240
+#, python-format
+msgid "Could not read questionnaire ID of either %s, %i or %s, %i!"
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:267
+msgid ""
+"Got different IDs on different pages for at least one sheet! Do *NOT* try to "
+"use filters with this survey! You have to run a \"reorder\" step for this to "
+"work properly!"
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:323
+msgid "No style buddy loaded. This needs to be done for the \"custom\" style!"
+msgstr ""
+
+#: ../sdaps/report/answers.py:185
+#, python-format
+msgid "Answers: %i"
+msgstr ""
+
+#: ../sdaps/report/answers.py:187
+#, python-format
+msgid "Mean: %.2f"
+msgstr ""
+
+#: ../sdaps/report/answers.py:189
+#, python-format
+msgid "Standard Deviation: %.2f"
+msgstr ""
+
+#: ../sdaps/report/__init__.py:75 ../sdaps/reporttex/__init__.py:140
+msgid "Turned in Questionnaires"
+msgstr ""
+
+#: ../sdaps/report/__init__.py:92 ../sdaps/reporttex/__init__.py:139
+msgid "sdaps report"
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:61
+#, python-format
+msgid "Head %(l0)i got no title."
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:74
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got no question."
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:120
+#, python-format
+msgid "Error in question \"%s\""
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:123
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got no boxes."
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:148
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got not exactly two answers."
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:170
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got not exactly one box."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:45 ../sdaps/setuptex/__init__.py:68
+msgid "The survey directory already exists."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:50
+#, python-format
+msgid ""
+"Unknown file type (%s). questionnaire_odt should be application/vnd.oasis."
+"opendocument.text."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:55
+#, python-format
+msgid "Unknown file type (%s). questionnaire_pdf should be application/pdf."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:61 ../sdaps/setuptex/__init__.py:79
+#, python-format
+msgid "Unknown file type (%s). additionalqobjects should be text/plain."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:90
+msgid "Caught an Exception while parsing the ODT file. The current state is:"
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:93
+msgid ""
+"If the dependencies for the \"annotate\" command are installed, then an "
+"annotated version will be created next to the original PDF file."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:123 ../sdaps/setuptex/__init__.py:147
+msgid ""
+"Some combination of options and project properties do not work. Aborted "
+"Setup."
+msgstr ""
+
+#: ../sdaps/setupodt/boxesparser.py:121 ../sdaps/setupodt/boxesparser.py:190
+#, python-format
+msgid ""
+"Warning: Ignoring a box (page: %i, x: %.1f, y: %.1f, width: %.1f, height: "
+"%.1f)."
+msgstr ""
+
+#: ../sdaps/stamp/__init__.py:38
+msgid ""
+"You may not specify the number of sheets for this survey. All questionnaires "
+"will be identical as the survey has been configured to not use questionnaire "
+"IDs for each sheet."
+msgstr ""
+
+#: ../sdaps/stamp/__init__.py:76
+msgid ""
+"This survey has been configured to use questionnaire IDs. Each questionnaire "
+"will be unique. You need to use on of the options to add new IDs or use the "
+"existing ones."
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:37
+#, python-format
+msgid "Survey-ID: %i"
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:51
+#, python-format
+msgid "Questionnaire-ID: %i"
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:336
+msgid ""
+"You need to have either pdftk or pyPdf installed. pdftk is the faster method."
+msgstr ""
+
+#. bottomup = False =>(0, 0) is the upper left corner
+#: ../sdaps/stamp/generic.py:352
+#, python-format
+msgid "Creating stamp PDF for %i sheet"
+msgid_plural "Creating stamp PDF for %i sheets"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/stamp/generic.py:410 ../sdaps/stamp/generic.py:543
+#, python-format
+msgid "%i sheet; %f seconds per sheet"
+msgid_plural "%i sheet; %f seconds per sheet"
+msgstr[0] ""
+msgstr[1] ""
+
+#. Merge using pdftk
+#: ../sdaps/stamp/generic.py:419
+msgid "Stamping using pdftk"
+msgstr ""
+
+#. Shortcut if we only have one sheet.
+#. In this case form data in the PDF will *not* break, in
+#. the other code path it *will* break.
+#: ../sdaps/stamp/generic.py:426
+msgid "pdftk: Overlaying the original PDF with the markings."
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:435
+#, python-format
+msgid "pdftk: Splitting out page %d of each sheet."
+msgid_plural "pdftk: Splitting out page %d of each sheet."
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/stamp/generic.py:449
+msgid "pdftk: Splitting the questionnaire for watermarking."
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:460 ../sdaps/stamp/generic.py:469
+#, python-format
+msgid "pdftk: Watermarking page %d of all sheets."
+msgid_plural "pdftk: Watermarking page %d of all sheets."
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/stamp/generic.py:492
+msgid "pdftk: Assembling everything into the final PDF."
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:529
+msgid "Stamping using pyPdf. For faster stamping, install pdftk."
+msgstr ""
+
+#: ../sdaps/stamp/latex.py:21
+msgid ""
+"There should be no need to stamp a SDAPS Project that uses LaTeX and does "
+"not have different questionnaire IDs printed on each sheet.\n"
+"I am going to do so anyways."
+msgstr ""
+
+#: ../sdaps/stamp/latex.py:43
+#, python-format
+msgid "Running %s now twice to generate the stamped questionnaire."
+msgstr ""
+
+#: ../sdaps/stamp/latex.py:47 ../sdaps/setuptex/__init__.py:119
+#: ../sdaps/setuptex/__init__.py:158 ../sdaps/reporttex/__init__.py:161
+#, python-format
+msgid "Error running \"%s\" to compile the LaTeX file."
+msgstr ""
+
+#: ../sdaps/stamp/latex.py:53
+#, python-format
+msgid ""
+"An error occured during creation of the report. Temporary files left in '%s'."
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:73
+#, python-format
+msgid "Unknown file type (%s). questionnaire_tex should be of type text/x-tex."
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:74
+msgid "Will keep going, but expect failure!"
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:115 ../sdaps/setuptex/__init__.py:153
+#, python-format
+msgid "Running %s now twice to generate the questionnaire."
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:133
+msgid "Caught an Exception while parsing the SDAPS file. The current state is:"
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:174
+msgid ""
+"An error occured in the setup routine. The survey directory still exists. "
+"You can for example check the questionnaire.log file for LaTeX compile "
+"errors."
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:107
+msgid "author|Unknown"
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:138
+msgid "tex language|english"
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:154
+#, python-format
+msgid "The TeX project with the report data is located at '%s'."
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:157
+#, python-format
+msgid "Running %s now twice to generate the report."
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:167
+#, python-format
+msgid "An occured during creation of the report. Temporary files left in '%s'."
+msgstr ""
diff --git a/po/nl.po b/po/nl.po
new file mode 100644
index 0000000..c3ad3e0
--- /dev/null
+++ b/po/nl.po
@@ -0,0 +1,1089 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-03-13 19:45+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"Language: nl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: ../sdaps/script.py:41
+msgid "SDAPS -- Paper based survey tool."
+msgstr ""
+
+#: ../sdaps/script.py:45
+msgid "project directory|The SDAPS project."
+msgstr ""
+
+#: ../sdaps/script.py:46
+msgid "command list|Commands:"
+msgstr ""
+
+#: ../sdaps/add/__init__.py:55
+#, python-format
+msgid ""
+"Invalid input file %s. You need to specify a (multipage) monochrome TIFF as "
+"input."
+msgstr ""
+
+#: ../sdaps/add/__init__.py:67
+#, python-format
+msgid ""
+"Not adding %s because it has a wrong page count (needs to be a mulitple of "
+"%i)."
+msgstr ""
+
+#: ../sdaps/boxgallery/__init__.py:108
+#, python-format
+msgid "Rendering boxgallery for metric \"%s\"."
+msgstr ""
+
+#: ../sdaps/log.py:37
+msgid "Warning: "
+msgstr ""
+
+#: ../sdaps/log.py:41
+msgid "Error: "
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:31
+msgid "Add scanned questionnaires to the survey."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:32
+msgid ""
+"This command is used to add scanned images to the survey.\n"
+"    The image data needs to be a (multipage) 300dpi monochrome TIFF file. "
+"You\n"
+"    may choose not to copy the data into the project directory. In that "
+"case\n"
+"    the data will be referenced using a relative path."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:38
+msgid "Convert given files and add the result."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:43 ../sdaps/cmdline/convert.py:39
+msgid ""
+"Do a 3D-transformation after finding the corner marks. If the\n"
+"        corner marks are not found then the image will be added as-is."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:49
+msgid ""
+"Force adding the images even if the page count is wrong (only use if you "
+"know what you are doing)."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:53
+msgid "Copy the files into the directory (default)."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:58
+msgid "Do not copy the files into the directory."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:62
+msgid ""
+"Images contain a duplex scan of a simplex questionnaire (default: simplex "
+"scan)."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:68 ../sdaps/cmdline/convert.py:49
+msgid "A number of TIFF image files."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:94
+msgid "The --no-copy option is not compatible with --convert!"
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:103
+msgid "Converting input files into a single temporary file."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:115
+msgid ""
+"The page count of the created temporary file does not work with this survey."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:120
+msgid "Running the conversion failed."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:125
+#, python-format
+msgid "Processing %s"
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:129
+msgid "Done"
+msgstr ""
+
+#: ../sdaps/cmdline/annotate.py:28
+msgid "Annotate the questionnaire and show the recognized positions."
+msgstr ""
+
+#: ../sdaps/cmdline/annotate.py:29
+msgid ""
+"This command is mainly a debug utility. It creates an\n"
+"    annotated version of the questionnaire, with the information that SDAPS\n"
+"    knows about it overlayed on top."
+msgstr ""
+
+#: ../sdaps/cmdline/boxgallery.py:31
+msgid "Create PDFs with boxes sorted by the detection heuristics."
+msgstr ""
+
+#: ../sdaps/cmdline/boxgallery.py:32
+msgid ""
+"SDAPS uses multiple heuristics to detect determine the\n"
+"    state of checkboxes. There is a list for each heuristic giving the "
+"expected\n"
+"    state and the quality of the value (see defs.py). Using this command a "
+"PDF\n"
+"    will be created for each of the heuristics so that one can adjust the\n"
+"    values."
+msgstr ""
+
+#: ../sdaps/cmdline/boxgallery.py:40
+msgid ""
+"Reruns part of the recognition process and retrieves debug images from this "
+"step."
+msgstr ""
+
+#: ../sdaps/cmdline/convert.py:30
+msgid "Convert a set of images to the correct image format."
+msgstr ""
+
+#: ../sdaps/cmdline/convert.py:31
+msgid ""
+"This command can be used if you scanned files in a different\n"
+"        mode than the expected monochrome TIFF file. All the given files "
+"will\n"
+"        be loaded, converted to monochrome and stored into a multipage 1bpp\n"
+"        TIFF file. Optionally you can select that a 3D transformation "
+"should\n"
+"        be performed, using this it may be possible to work with photos of\n"
+"        questionnaires instead of scans."
+msgstr ""
+
+#: ../sdaps/cmdline/convert.py:45
+msgid "The location of the output file. Must be given."
+msgstr ""
+
+#: ../sdaps/cmdline/convert.py:58
+msgid "No output filename specified!"
+msgstr ""
+
+#: ../sdaps/cmdline/cover.py:27
+msgid "Create a cover for the questionnaires."
+msgstr ""
+
+#: ../sdaps/cmdline/cover.py:28
+msgid ""
+"This command creates a cover page for questionnaires. All\n"
+"    the metadata of the survey will be printed on the page."
+msgstr ""
+
+#: ../sdaps/cmdline/cover.py:31
+#, python-format
+msgid "Filename to store the data to (default: cover_%%i.pdf)"
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:34
+msgid "Import or export data to/from CSV files."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:35
+msgid ""
+"Import or export data to/from a CSV file. The first line\n"
+"    is a header which defines questionnaire_id and global_id, and a column\n"
+"    for each checkbox and textfield. Note that the import is currently very\n"
+"    limited, as you need to specifiy the questionnaire ID to select the "
+"sheet\n"
+"    which should be updated."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:44
+msgid "Export data to CSV file."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:46
+#, python-format
+msgid "Filename to store the data to (default: data_%%i.csv)"
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:48
+msgid "The delimiter used in the CSV file (default ',')"
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:52 ../sdaps/cmdline/report.py:31
+#: ../sdaps/cmdline/reporttex.py:52
+msgid "Filter to only export a partial dataset."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:54
+msgid "Export images of freeform fields."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:60
+msgid "Export an image for each question that includes all boxes."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:66
+msgid "Export the recognition quality for each checkbox."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:74
+msgid "Import data to from a CSV file."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:76
+msgid "The file to import."
+msgstr ""
+
+#: ../sdaps/cmdline/gui.py:28
+msgid "Launch a gui. You can view and alter the (recognized) answers with it."
+msgstr ""
+
+#: ../sdaps/cmdline/gui.py:29
+msgid ""
+"This command launches a graphical user interface that can\n"
+"    be used to correct answer. You need to run \"recognize\" before using "
+"it.\n"
+"    "
+msgstr ""
+
+#: ../sdaps/cmdline/gui.py:34
+msgid "Filter to only show a partial dataset."
+msgstr ""
+
+#: ../sdaps/cmdline/ids.py:29
+msgid "Export and import questionnaire IDs."
+msgstr ""
+
+#: ../sdaps/cmdline/ids.py:30
+msgid ""
+"This command can be used to import and export questionnaire\n"
+"    IDs. It only makes sense in projects where such an ID is printed on the\n"
+"    questionnaire. Note that you can also add IDs by using the stamp "
+"command,\n"
+"    which will give you the PDF at the same time."
+msgstr ""
+
+#: ../sdaps/cmdline/ids.py:35
+#, python-format
+msgid "Filename to store the data to (default: ids_%%i)"
+msgstr ""
+
+#: ../sdaps/cmdline/ids.py:38
+msgid "Add IDs to the internal list from the specified file."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:28
+msgid "Display and modify metadata of project."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:29
+msgid ""
+"This command lets you modify the metadata of the SDAPS\n"
+"    project. You can modify, add and remove arbitrary keys that will be "
+"printed\n"
+"    on the report. The only key that always exist is \"title\".\n"
+"    If no key is given then a list of defined keys is printed."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:36
+msgid "The key to display or modify."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:40
+msgid "Set the given key to this value."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:44
+msgid "Delete the key and value pair."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:68
+msgid "Existing fields:\n"
+msgstr ""
+
+#: ../sdaps/cmdline/recognize.py:28
+msgid "Run the optical mark recognition."
+msgstr ""
+
+#: ../sdaps/cmdline/recognize.py:29
+msgid ""
+"Iterates over all images and runs the optical mark\n"
+"    recognition. It will reevaluate sheets even if \"recognize\" has "
+"already\n"
+"    run or manual changes were made."
+msgstr ""
+
+#: ../sdaps/cmdline/recognize.py:34
+msgid ""
+"Only identify the page properties, but don't recognize the checkbox states."
+msgstr ""
+
+#: ../sdaps/cmdline/recognize.py:39
+msgid ""
+"Rerun the recognition for all pages. The default is to skip all pages that "
+"were recognized or verified already."
+msgstr ""
+
+#: ../sdaps/cmdline/reorder.py:26
+msgid "Reorder pages according to questionnaire ID."
+msgstr ""
+
+#: ../sdaps/cmdline/reorder.py:27
+msgid ""
+"This command reorders all pages according to the already\n"
+"    recognized questionnaire ID. To use it add all the files to the "
+"project,\n"
+"    then run a partial recognition using \"recognize --identify\". After "
+"this\n"
+"    you have to run this command to reorder the data for the real "
+"recognition.\n"
+"    "
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:26
+msgid "Create a PDF report."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:27
+msgid ""
+"This command creates a PDF report using reportlab that\n"
+"    contains statistics and if selected the freeform fields."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:33
+msgid "Create a filtered report for every checkbox."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:36
+msgid "Short format (without freeform text fields)."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:41
+msgid "Detailed output. (default)"
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:47 ../sdaps/cmdline/reporttex.py:30
+msgid ""
+"Do not include original images in the report. This is useful if there are "
+"privacy concerns."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:52 ../sdaps/cmdline/reporttex.py:35
+msgid "Do not use substitutions instead of images."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:58 ../sdaps/cmdline/reporttex.py:46
+msgid "The paper size used for the output (default: locale dependent)"
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:61 ../sdaps/cmdline/reporttex.py:49
+#, python-format
+msgid "Filename to store the data to (default: report_%%i.pdf)"
+msgstr ""
+
+#: ../sdaps/cmdline/reporttex.py:26
+msgid "Create a PDF report using LaTeX."
+msgstr ""
+
+#: ../sdaps/cmdline/reporttex.py:27
+msgid ""
+"This command creates a PDF report using LaTeX that\n"
+"    contains statistics and freeform fields."
+msgstr ""
+
+#: ../sdaps/cmdline/reporttex.py:41
+msgid "Save the generated TeX files instead of the final PDF."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:28
+msgid "Create a new survey using an ODT document."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:29
+msgid ""
+"Create a new surevey from an ODT document. The PDF export\n"
+"    of the file needs to be specified at the same time. SDAPS will import\n"
+"    metadata (properties) and the title from the ODT document."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:34
+msgid "The ODT Document"
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:36
+msgid "PDF export of the ODT document"
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:39 ../sdaps/cmdline/setuptex.py:39
+msgid "Additional questions that are not part of the questionnaire."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:48
+msgid "Enable printing of the survey ID (default)."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:52
+msgid "Disable printing of the survey ID."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:57
+msgid "Enable printing of the questionnaire ID."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:61
+msgid "Disable printing of the questionnaire ID (default)."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:65
+msgid ""
+"Set an additional global ID for tracking. This can can only be used in the "
+"\"code128\" style."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:70
+msgid ""
+"The stamping style to use. Should be either \"classic\" or \"code128\". Use "
+"\"code128\" for more features."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:76
+msgid "The mode to use when detecting checkboxes."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:82
+msgid ""
+"Use duplex mode (ie. only print the IDs on the back side). Requires a "
+"mulitple of two pages."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:87
+msgid ""
+"Use simplex mode. IDs are printed on each page. You need a simplex scan "
+"currently!"
+msgstr ""
+
+#: ../sdaps/cmdline/setuptex.py:27
+msgid "Create a new survey using a LaTeX document."
+msgstr ""
+
+#: ../sdaps/cmdline/setuptex.py:28
+msgid ""
+"Create a new survey from a LaTeX document. You need to\n"
+"    be using the SDAPS class. All the metadata and options for the project\n"
+"    can be set inside the LaTeX document."
+msgstr ""
+
+#: ../sdaps/cmdline/setuptex.py:33
+msgid "The LaTeX Document"
+msgstr ""
+
+#: ../sdaps/cmdline/setuptex.py:35
+msgid ""
+"Additional files that are required by the LaTeX document and need to be "
+"copied into the project directory."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:26
+msgid "Add marks for automatic processing."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:27
+msgid ""
+"This command creates the printable document. Depending on\n"
+"    the projects setting you are required to specifiy a source for "
+"questionnaire\n"
+"    IDs."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:33
+msgid ""
+"If using questionnaire IDs, create N questionnaires with randomized IDs."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:36
+msgid ""
+"If using questionnaire IDs, create questionnaires from the IDs read from the "
+"specified file."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:39
+msgid "If using questionnaire IDs, create questionnaires for all stored IDs."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:42
+#, python-format
+msgid "Filename to store the data to (default: stamp_%%i.pdf)"
+msgstr ""
+
+#: ../sdaps/convert/__init__.py:35
+#, python-format
+msgid "Could not apply 3D-transformation to image '%s', page %i!"
+msgstr ""
+
+#: ../sdaps/cover/__init__.py:40
+msgid "sdaps questionnaire"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:57
+msgid ""
+"The survey does not have any images! Please add images (and run recognize) "
+"before using the GUI."
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:202
+msgid "Page|Invalid"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:205
+#, python-format
+msgid "Page %i"
+msgid_plural "Page %i"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/gui/__init__.py:247
+msgid "Copyright © 2007-2014 The SDAPS Authors"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:249
+msgid "Scripts for data acquisition with paper based surveys"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:250
+msgid "http://sdaps.org"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:251
+msgid "translator-credits"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:296
+#, python-format
+msgid " of %i"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:297
+#, python-format
+msgid "Recognition Quality: %.2f"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:313
+msgid ""
+"You have reached the first page of the survey. Would you like to go to the "
+"last page?"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:315
+msgid "Go to last page"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:337
+msgid ""
+"You have reached the last page of the survey. Would you like to go to the "
+"first page?"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:339
+msgid "Go to first page"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:447
+msgid "Close without saving"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:451
+msgid ""
+"<b>Save the project before closing?</b>\n"
+"\n"
+"If you do not save you may loose data."
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:1
+msgid "Forward"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:2
+msgid "Previous"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:3
+msgid "Zoom In"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:4
+msgid "Zoom Out"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:5
+msgid "SDAPS"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:6
+msgid "_File"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:7
+msgid "_View"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:8
+msgid "_Help"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:9
+msgid "Page Rotated"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:10
+msgid "Sort by Quality"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:11
+msgid "label"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:56
+msgid "<b>Global Properties</b>"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:60
+msgid "Sheet valid"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:61
+msgid "Verified"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:62
+msgid "Empty"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:74 ../sdaps/gui/widget_buddies.py:98
+msgid "<b>Questionnaire ID: </b>"
+msgstr ""
+
+#: ../sdaps/image/__init__.py:47
+msgid ""
+"It appears you have not build the C extension. Please run \"./setup.py build"
+"\" in the toplevel directory."
+msgstr ""
+
+#: ../sdaps/utils/opencv.py:31
+msgid "Cannot convert PDF files as poppler is not installed or usable!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:253
+#, python-format
+msgid "%i sheet"
+msgid_plural "%i sheets"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/model/survey.py:264
+#, python-format
+msgid "%f seconds per sheet"
+msgstr ""
+
+#: ../sdaps/model/survey.py:290
+msgid ""
+"A questionnaire that is printed in duplex needs an even amount of pages!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:294
+msgid ""
+"The 'classic' style only supports a maximum of six pages! Use the 'code128' "
+"style if you require more pages."
+msgstr ""
+
+#: ../sdaps/model/survey.py:307
+msgid "IDs need to be integers in \"classic\" style!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:313
+#, python-format
+msgid "Invalid character %s in questionnaire ID \"%s\" in \"code128\" style!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:317
+msgid ""
+"SDAPS cannot draw a questionnaire ID with the \"custom\" style. Do this "
+"yourself somehow!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:337
+#, python-format
+msgid "Running upgrade routines for file format version %i"
+msgstr ""
+
+#. in simplex mode every page will have a matrix; it might be a None
+#. matrix though
+#: ../sdaps/recognize/buddies.py:72
+#, python-format
+msgid "%s, %i: Matrix not recognized."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:81
+#, python-format
+msgid "%s, %i: Rotation not found."
+msgstr ""
+
+#. Copy the rotation over (if required) and print warning if the rotation is unknown
+#: ../sdaps/recognize/buddies.py:85
+#, python-format
+msgid "Neither %s, %i or %s, %i has a known rotation!"
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:97
+#, python-format
+msgid "%s, %i: Matrix not recognized (again)."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:111
+#, python-format
+msgid "%s, %i: Could not get page number."
+msgstr ""
+
+#. Whoa, that should not happen.
+#: ../sdaps/recognize/buddies.py:131
+#, python-format
+msgid "Neither %s, %i or %s, %i has a known page number!"
+msgstr ""
+
+#. We don't touch the ignore flag in this case
+#. Simply print a message as this should *never* happen
+#: ../sdaps/recognize/buddies.py:142
+#, python-format
+msgid ""
+"Got a simplex document where two adjacent pages had a known page number. "
+"This should never happen as even simplex scans are converted to duplex by "
+"inserting dummy pages. Maybe you did a simplex scan but added it in duplex "
+"mode? The pages in question are %s, %i and %s, %i."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:159
+#, python-format
+msgid "Images %s, %i and %s, %i do not have consecutive page numbers!"
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:175
+#, python-format
+msgid "No page number for page %s, %i exists."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:180
+#, python-format
+msgid "Page number for page %s, %i already used by another image."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:186
+#, python-format
+msgid "Page number %i for page %s, %i is out of range."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:203
+#, python-format
+msgid "%s, %i: Could not read survey ID, but should be able to."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:207
+#, python-format
+msgid "Could not read survey ID of either %s, %i or %s, %i!"
+msgstr ""
+
+#. Broken survey ID ...
+#: ../sdaps/recognize/buddies.py:214
+#, python-format
+msgid "Got a wrong survey ID (%s, %i)! It is %s, but should be %i."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:236
+#, python-format
+msgid "%s, %i: Could not read questionnaire ID, but should be able to."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:240
+#, python-format
+msgid "Could not read questionnaire ID of either %s, %i or %s, %i!"
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:267
+msgid ""
+"Got different IDs on different pages for at least one sheet! Do *NOT* try to "
+"use filters with this survey! You have to run a \"reorder\" step for this to "
+"work properly!"
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:323
+msgid "No style buddy loaded. This needs to be done for the \"custom\" style!"
+msgstr ""
+
+#: ../sdaps/report/answers.py:185
+#, python-format
+msgid "Answers: %i"
+msgstr ""
+
+#: ../sdaps/report/answers.py:187
+#, python-format
+msgid "Mean: %.2f"
+msgstr ""
+
+#: ../sdaps/report/answers.py:189
+#, python-format
+msgid "Standard Deviation: %.2f"
+msgstr ""
+
+#: ../sdaps/report/__init__.py:75 ../sdaps/reporttex/__init__.py:140
+msgid "Turned in Questionnaires"
+msgstr ""
+
+#: ../sdaps/report/__init__.py:92 ../sdaps/reporttex/__init__.py:139
+msgid "sdaps report"
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:61
+#, python-format
+msgid "Head %(l0)i got no title."
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:74
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got no question."
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:120
+#, python-format
+msgid "Error in question \"%s\""
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:123
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got no boxes."
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:148
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got not exactly two answers."
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:170
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got not exactly one box."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:45 ../sdaps/setuptex/__init__.py:68
+msgid "The survey directory already exists."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:50
+#, python-format
+msgid ""
+"Unknown file type (%s). questionnaire_odt should be application/vnd.oasis."
+"opendocument.text."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:55
+#, python-format
+msgid "Unknown file type (%s). questionnaire_pdf should be application/pdf."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:61 ../sdaps/setuptex/__init__.py:79
+#, python-format
+msgid "Unknown file type (%s). additionalqobjects should be text/plain."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:90
+msgid "Caught an Exception while parsing the ODT file. The current state is:"
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:93
+msgid ""
+"If the dependencies for the \"annotate\" command are installed, then an "
+"annotated version will be created next to the original PDF file."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:123 ../sdaps/setuptex/__init__.py:147
+msgid ""
+"Some combination of options and project properties do not work. Aborted "
+"Setup."
+msgstr ""
+
+#: ../sdaps/setupodt/boxesparser.py:121 ../sdaps/setupodt/boxesparser.py:190
+#, python-format
+msgid ""
+"Warning: Ignoring a box (page: %i, x: %.1f, y: %.1f, width: %.1f, height: "
+"%.1f)."
+msgstr ""
+
+#: ../sdaps/stamp/__init__.py:38
+msgid ""
+"You may not specify the number of sheets for this survey. All questionnaires "
+"will be identical as the survey has been configured to not use questionnaire "
+"IDs for each sheet."
+msgstr ""
+
+#: ../sdaps/stamp/__init__.py:76
+msgid ""
+"This survey has been configured to use questionnaire IDs. Each questionnaire "
+"will be unique. You need to use on of the options to add new IDs or use the "
+"existing ones."
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:37
+#, python-format
+msgid "Survey-ID: %i"
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:51
+#, python-format
+msgid "Questionnaire-ID: %i"
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:336
+msgid ""
+"You need to have either pdftk or pyPdf installed. pdftk is the faster method."
+msgstr ""
+
+#. bottomup = False =>(0, 0) is the upper left corner
+#: ../sdaps/stamp/generic.py:352
+#, python-format
+msgid "Creating stamp PDF for %i sheet"
+msgid_plural "Creating stamp PDF for %i sheets"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/stamp/generic.py:410 ../sdaps/stamp/generic.py:543
+#, python-format
+msgid "%i sheet; %f seconds per sheet"
+msgid_plural "%i sheet; %f seconds per sheet"
+msgstr[0] ""
+msgstr[1] ""
+
+#. Merge using pdftk
+#: ../sdaps/stamp/generic.py:419
+msgid "Stamping using pdftk"
+msgstr ""
+
+#. Shortcut if we only have one sheet.
+#. In this case form data in the PDF will *not* break, in
+#. the other code path it *will* break.
+#: ../sdaps/stamp/generic.py:426
+msgid "pdftk: Overlaying the original PDF with the markings."
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:435
+#, python-format
+msgid "pdftk: Splitting out page %d of each sheet."
+msgid_plural "pdftk: Splitting out page %d of each sheet."
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/stamp/generic.py:449
+msgid "pdftk: Splitting the questionnaire for watermarking."
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:460 ../sdaps/stamp/generic.py:469
+#, python-format
+msgid "pdftk: Watermarking page %d of all sheets."
+msgid_plural "pdftk: Watermarking page %d of all sheets."
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/stamp/generic.py:492
+msgid "pdftk: Assembling everything into the final PDF."
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:529
+msgid "Stamping using pyPdf. For faster stamping, install pdftk."
+msgstr ""
+
+#: ../sdaps/stamp/latex.py:21
+msgid ""
+"There should be no need to stamp a SDAPS Project that uses LaTeX and does "
+"not have different questionnaire IDs printed on each sheet.\n"
+"I am going to do so anyways."
+msgstr ""
+
+#: ../sdaps/stamp/latex.py:43
+#, python-format
+msgid "Running %s now twice to generate the stamped questionnaire."
+msgstr ""
+
+#: ../sdaps/stamp/latex.py:47 ../sdaps/setuptex/__init__.py:119
+#: ../sdaps/setuptex/__init__.py:158 ../sdaps/reporttex/__init__.py:161
+#, python-format
+msgid "Error running \"%s\" to compile the LaTeX file."
+msgstr ""
+
+#: ../sdaps/stamp/latex.py:53
+#, python-format
+msgid ""
+"An error occured during creation of the report. Temporary files left in '%s'."
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:73
+#, python-format
+msgid "Unknown file type (%s). questionnaire_tex should be of type text/x-tex."
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:74
+msgid "Will keep going, but expect failure!"
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:115 ../sdaps/setuptex/__init__.py:153
+#, python-format
+msgid "Running %s now twice to generate the questionnaire."
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:133
+msgid "Caught an Exception while parsing the SDAPS file. The current state is:"
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:174
+msgid ""
+"An error occured in the setup routine. The survey directory still exists. "
+"You can for example check the questionnaire.log file for LaTeX compile "
+"errors."
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:107
+msgid "author|Unknown"
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:138
+msgid "tex language|english"
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:154
+#, python-format
+msgid "The TeX project with the report data is located at '%s'."
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:157
+#, python-format
+msgid "Running %s now twice to generate the report."
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:167
+#, python-format
+msgid "An occured during creation of the report. Temporary files left in '%s'."
+msgstr ""
diff --git a/po/pt.po b/po/pt.po
new file mode 100644
index 0000000..da62e9c
--- /dev/null
+++ b/po/pt.po
@@ -0,0 +1,1258 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: SDAPS 0.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-03-13 19:45+0100\n"
+"PO-Revision-Date: 2014-07-25 20:08+0200\n"
+"Last-Translator: Rodrigo <rodrigo_blu at yahoo.com.br>\n"
+"Language-Team: Portuguese <https://hosted.weblate.org/projects/sdaps/master/"
+"pt/>\n"
+"Language: pt\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 1.10-dev\n"
+
+#: ../sdaps/script.py:41
+msgid "SDAPS -- Paper based survey tool."
+msgstr "SDAPS -- Ferramenta de questionários baseados em papel."
+
+#: ../sdaps/script.py:45
+msgid "project directory|The SDAPS project."
+msgstr "diretório do projeto|Projeto SDAPS."
+
+#: ../sdaps/script.py:46
+msgid "command list|Commands:"
+msgstr "lista de comandos|Comandos:"
+
+#: ../sdaps/add/__init__.py:55
+#, python-format
+msgid ""
+"Invalid input file %s. You need to specify a (multipage) monochrome TIFF as "
+"input."
+msgstr ""
+"Arquivo de entrada inválido %s. Você precisa especificar uma imagem TIFF "
+"(multipáginas) monocromática como entrada."
+
+#: ../sdaps/add/__init__.py:67
+#, python-format
+msgid ""
+"Not adding %s because it has a wrong page count (needs to be a mulitple of "
+"%i)."
+msgstr ""
+"Não adicionando %s porque ele tem um número de páginas errado (deve ser um "
+"múltiplo de %i)."
+
+#: ../sdaps/boxgallery/__init__.py:108
+#, python-format
+msgid "Rendering boxgallery for metric \"%s\"."
+msgstr "Renderizando caixas pela métrica \"%s\"."
+
+#: ../sdaps/log.py:37
+msgid "Warning: "
+msgstr "Atenção: "
+
+#: ../sdaps/log.py:41
+msgid "Error: "
+msgstr "Erro: "
+
+#: ../sdaps/cmdline/add.py:31
+msgid "Add scanned questionnaires to the survey."
+msgstr "Adicionar questionários digitalizados na pesquisa."
+
+#: ../sdaps/cmdline/add.py:32
+msgid ""
+"This command is used to add scanned images to the survey.\n"
+"    The image data needs to be a (multipage) 300dpi monochrome TIFF file. "
+"You\n"
+"    may choose not to copy the data into the project directory. In that "
+"case\n"
+"    the data will be referenced using a relative path."
+msgstr ""
+"Este comando é usado para adicionar imagens digitalizadas na pesquisa.\n"
+"     A imagem precisa ser em formato TIFF (multipáginas) monocromático de "
+"300dpi.\n"
+"     Você pode optar por não copiar os dados para o diretório do projeto. "
+"Nesse\n"
+"     caso os dados serão referenciados usando um caminho relativo."
+
+#: ../sdaps/cmdline/add.py:38
+msgid "Convert given files and add the result."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:43 ../sdaps/cmdline/convert.py:39
+msgid ""
+"Do a 3D-transformation after finding the corner marks. If the\n"
+"        corner marks are not found then the image will be added as-is."
+msgstr ""
+"Faz uma transformação 3D depois de encontrar as marcas de canto.\n"
+"        Se as marcas não forem encontradas, a imagem é adicionada como é."
+
+#: ../sdaps/cmdline/add.py:49
+msgid ""
+"Force adding the images even if the page count is wrong (only use if you "
+"know what you are doing)."
+msgstr ""
+"Força a adição das imagens, mesmo que a contagem de páginas esteja errada "
+"(só use se você sabe o que está fazendo)."
+
+#: ../sdaps/cmdline/add.py:53
+msgid "Copy the files into the directory (default)."
+msgstr "Copie os arquivos para o diretório (padrão)."
+
+#: ../sdaps/cmdline/add.py:58
+msgid "Do not copy the files into the directory."
+msgstr "Não copie os arquivos para o diretório."
+
+#: ../sdaps/cmdline/add.py:62
+#, fuzzy
+msgid ""
+"Images contain a duplex scan of a simplex questionnaire (default: simplex "
+"scan)."
+msgstr ""
+"As imagens contêm uma digitalização duplex de um questionário simplex "
+"(padrão: digitalização simplex)."
+
+#: ../sdaps/cmdline/add.py:68 ../sdaps/cmdline/convert.py:49
+msgid "A number of TIFF image files."
+msgstr "Um número de arquivos de imagem TIFF."
+
+#: ../sdaps/cmdline/add.py:94
+msgid "The --no-copy option is not compatible with --convert!"
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:103
+msgid "Converting input files into a single temporary file."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:115
+msgid ""
+"The page count of the created temporary file does not work with this survey."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:120
+msgid "Running the conversion failed."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:125
+#, python-format
+msgid "Processing %s"
+msgstr "Processando %s"
+
+#: ../sdaps/cmdline/add.py:129
+msgid "Done"
+msgstr "Concluído"
+
+#: ../sdaps/cmdline/annotate.py:28
+msgid "Annotate the questionnaire and show the recognized positions."
+msgstr "Anotar o questionário e mostrar as posições reconhecidas."
+
+#: ../sdaps/cmdline/annotate.py:29
+msgid ""
+"This command is mainly a debug utility. It creates an\n"
+"    annotated version of the questionnaire, with the information that SDAPS\n"
+"    knows about it overlayed on top."
+msgstr ""
+"Este comando é principalmente um utilitário de depuração. Ele cria uma\n"
+"    versão anotada do questionário, com as informações que o SDAPS\n"
+"    sabe sobre ele sobrepostas na parte superior."
+
+#: ../sdaps/cmdline/boxgallery.py:31
+msgid "Create PDFs with boxes sorted by the detection heuristics."
+msgstr "Cria PDFs com caixas ordenadas por heurísticas de detecção."
+
+#: ../sdaps/cmdline/boxgallery.py:32
+msgid ""
+"SDAPS uses multiple heuristics to detect determine the\n"
+"    state of checkboxes. There is a list for each heuristic giving the "
+"expected\n"
+"    state and the quality of the value (see defs.py). Using this command a "
+"PDF\n"
+"    will be created for each of the heuristics so that one can adjust the\n"
+"    values."
+msgstr ""
+"O SDAPS usa várias heurísticas para determinar o estado das caixas.\n"
+"    Há uma lista para cada heurística dando o estado esperado e a qualidade\n"
+"    do valor (ver defs.py). Usando este comando um PDF será criado para "
+"cada\n"
+"    uma das heurísticas, para que se possa ajustar os valores."
+
+#: ../sdaps/cmdline/boxgallery.py:40
+msgid ""
+"Reruns part of the recognition process and retrieves debug images from this "
+"step."
+msgstr ""
+"Executa o reconhecimento novamente e armazena imagens adicionais para "
+"depuração a partir deste passo."
+
+#: ../sdaps/cmdline/convert.py:30
+msgid "Convert a set of images to the correct image format."
+msgstr "Converte um conjunto de imagens para o formato de imagem correto."
+
+#: ../sdaps/cmdline/convert.py:31
+msgid ""
+"This command can be used if you scanned files in a different\n"
+"        mode than the expected monochrome TIFF file. All the given files "
+"will\n"
+"        be loaded, converted to monochrome and stored into a multipage 1bpp\n"
+"        TIFF file. Optionally you can select that a 3D transformation "
+"should\n"
+"        be performed, using this it may be possible to work with photos of\n"
+"        questionnaires instead of scans."
+msgstr ""
+"Esse comando pode ser usado se você tiver arquivos digitalizados em formato "
+"diferente\n"
+"\tao TIFF monocromático esperado. Todos os arquivos serão carregados,\n"
+"\tconvertidos em monocromático e armazenados em um arquivo TIFF multipágina "
+"de 1bpp.\n"
+"\tOpcionalmente, você pode selecionar que se realize uma transformação 3D,\n"
+"\tcom isto pode ser possível trabalhar com fotos dos questionários, ao "
+"invés\n"
+"\tda digitalização convencional."
+
+#: ../sdaps/cmdline/convert.py:45
+msgid "The location of the output file. Must be given."
+msgstr "A localização do arquivo de saída. Deve ser fornecida."
+
+#: ../sdaps/cmdline/convert.py:58
+msgid "No output filename specified!"
+msgstr "Nome do arquivo de saída não especificado!"
+
+#: ../sdaps/cmdline/cover.py:27
+msgid "Create a cover for the questionnaires."
+msgstr "Criar uma página de rosto para os questionários."
+
+#: ../sdaps/cmdline/cover.py:28
+msgid ""
+"This command creates a cover page for questionnaires. All\n"
+"    the metadata of the survey will be printed on the page."
+msgstr ""
+"Este comando cria uma página de rosto para os questionários.\n"
+"    Todos os metadados da pesquisa serão impressos nela."
+
+#: ../sdaps/cmdline/cover.py:31
+#, python-format
+msgid "Filename to store the data to (default: cover_%%i.pdf)"
+msgstr "Nome de arquivo para armazenar os dados (padrão: cover_%%i.pdf)"
+
+#: ../sdaps/cmdline/csvdata.py:34
+msgid "Import or export data to/from CSV files."
+msgstr "Importar ou exportar dados de/para arquivos CSV."
+
+#: ../sdaps/cmdline/csvdata.py:35
+msgid ""
+"Import or export data to/from a CSV file. The first line\n"
+"    is a header which defines questionnaire_id and global_id, and a column\n"
+"    for each checkbox and textfield. Note that the import is currently very\n"
+"    limited, as you need to specifiy the questionnaire ID to select the "
+"sheet\n"
+"    which should be updated."
+msgstr ""
+"Importar ou exportar dados de/para um arquivo CSV. A primeira linha\n"
+"     é um cabeçalho, que define questionnaire_id, global_id e uma coluna\n"
+"     para cada caixa de verificação e campo de texto. Note que a importação "
+"é\n"
+"     atualmente muito limitada, já que você precisa especificar o "
+"identificador\n"
+"     do questionário para selecionar a folha que deverá ser atualizada."
+
+#: ../sdaps/cmdline/csvdata.py:44
+msgid "Export data to CSV file."
+msgstr "Exportar dados para arquivo CSV."
+
+#: ../sdaps/cmdline/csvdata.py:46
+#, fuzzy, python-format
+msgid "Filename to store the data to (default: data_%%i.csv)"
+msgstr "Nome de arquivo para armazenar os dados (padrão: ids_%%i)"
+
+#: ../sdaps/cmdline/csvdata.py:48
+msgid "The delimiter used in the CSV file (default ',')"
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:52 ../sdaps/cmdline/report.py:31
+#: ../sdaps/cmdline/reporttex.py:52
+msgid "Filter to only export a partial dataset."
+msgstr "Filtro para exportar apenas um conjunto de dados parcial."
+
+#: ../sdaps/cmdline/csvdata.py:54
+#, fuzzy
+msgid "Export images of freeform fields."
+msgstr "Formato curto (sem campos de texto de forma livre)."
+
+#: ../sdaps/cmdline/csvdata.py:60
+#, fuzzy
+msgid "Export an image for each question that includes all boxes."
+msgstr "Exportação e importação de IDs de questionário."
+
+#: ../sdaps/cmdline/csvdata.py:66
+msgid "Export the recognition quality for each checkbox."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:74
+msgid "Import data to from a CSV file."
+msgstr "Importar dados de um arquivo CSV."
+
+#: ../sdaps/cmdline/csvdata.py:76
+msgid "The file to import."
+msgstr "O arquivo a ser importado."
+
+#: ../sdaps/cmdline/gui.py:28
+msgid "Launch a gui. You can view and alter the (recognized) answers with it."
+msgstr ""
+"Abrir uma interface gráfica (GUI). Você pode visualizar e alterar as "
+"respostas (reconhecidas) nela."
+
+#: ../sdaps/cmdline/gui.py:29
+msgid ""
+"This command launches a graphical user interface that can\n"
+"    be used to correct answer. You need to run \"recognize\" before using "
+"it.\n"
+"    "
+msgstr ""
+"Este comando lança uma interface gráfica (GUI) que pode ser utilizada para\n"
+"     corrigir respostas. Você precisa executar \"recognize\" antes de usá-"
+"lo.\n"
+"    "
+
+#: ../sdaps/cmdline/gui.py:34
+msgid "Filter to only show a partial dataset."
+msgstr "Filtro para mostrar apenas um conjunto de dados parcial."
+
+#: ../sdaps/cmdline/ids.py:29
+msgid "Export and import questionnaire IDs."
+msgstr "Exportação e importação de IDs de questionário."
+
+#: ../sdaps/cmdline/ids.py:30
+msgid ""
+"This command can be used to import and export questionnaire\n"
+"    IDs. It only makes sense in projects where such an ID is printed on the\n"
+"    questionnaire. Note that you can also add IDs by using the stamp "
+"command,\n"
+"    which will give you the PDF at the same time."
+msgstr ""
+"Esse comando pode ser usado para importar e exportar IDs de questionário.\n"
+"     Ele só faz sentido em projetos onde tal identificação é impressa no\n"
+"     questionário. Note que você também pode adicionar IDs usando o comando\n"
+"     \"stamp\", que também lhe dará o arquivo PDF."
+
+#: ../sdaps/cmdline/ids.py:35
+#, python-format
+msgid "Filename to store the data to (default: ids_%%i)"
+msgstr "Nome de arquivo para armazenar os dados (padrão: ids_%%i)"
+
+#: ../sdaps/cmdline/ids.py:38
+msgid "Add IDs to the internal list from the specified file."
+msgstr "Adicionar IDs à lista interna do arquivo especificado."
+
+#: ../sdaps/cmdline/info.py:28
+msgid "Display and modify metadata of project."
+msgstr "Exibir e modificar os metadados do projeto."
+
+#: ../sdaps/cmdline/info.py:29
+msgid ""
+"This command lets you modify the metadata of the SDAPS\n"
+"    project. You can modify, add and remove arbitrary keys that will be "
+"printed\n"
+"    on the report. The only key that always exist is \"title\".\n"
+"    If no key is given then a list of defined keys is printed."
+msgstr ""
+"Este comando permite que você modifique os metadados do projeto SDAPS.\n"
+"     Você pode modificar, adicionar e remover chaves arbitrárias que serão "
+"impressas\n"
+"     no relatório. A única chave que sempre existe é \"title\".\n"
+"     Se nenhuma chave for informada, uma lista de chaves definidas é "
+"impressa."
+
+#: ../sdaps/cmdline/info.py:36
+msgid "The key to display or modify."
+msgstr "A chave para exibir ou modificar."
+
+#: ../sdaps/cmdline/info.py:40
+msgid "Set the given key to this value."
+msgstr "Novo valor para a chave."
+
+#: ../sdaps/cmdline/info.py:44
+msgid "Delete the key and value pair."
+msgstr "Exclua o par de chave e valor."
+
+#: ../sdaps/cmdline/info.py:68
+msgid "Existing fields:\n"
+msgstr "Campos existentes:\n"
+
+#: ../sdaps/cmdline/recognize.py:28
+msgid "Run the optical mark recognition."
+msgstr "Executar o reconhecimento óptico de marcas."
+
+#: ../sdaps/cmdline/recognize.py:29
+msgid ""
+"Iterates over all images and runs the optical mark\n"
+"    recognition. It will reevaluate sheets even if \"recognize\" has "
+"already\n"
+"    run or manual changes were made."
+msgstr ""
+"Itera sobre todas as imagens e executa o reconhecimento óptico de marcas.\n"
+"     Ele irá reavaliar as folhas mesmo se o comando \"recognize\" for\n"
+"     executado previamente ou alterações manuais tenham sido feitas."
+
+#: ../sdaps/cmdline/recognize.py:34
+msgid ""
+"Only identify the page properties, but don't recognize the checkbox states."
+msgstr "Analisa apenas as propriedades da página, mas não as respostas."
+
+#: ../sdaps/cmdline/recognize.py:39
+msgid ""
+"Rerun the recognition for all pages. The default is to skip all pages that "
+"were recognized or verified already."
+msgstr ""
+"Execute novamente o reconhecimento para todas as páginas. O padrão é pular "
+"todas as páginas que já foram reconhecidas ou verificadas."
+
+#: ../sdaps/cmdline/reorder.py:26
+msgid "Reorder pages according to questionnaire ID."
+msgstr "Reordenar páginas de acordo com o ID do questionário."
+
+#: ../sdaps/cmdline/reorder.py:27
+msgid ""
+"This command reorders all pages according to the already\n"
+"    recognized questionnaire ID. To use it add all the files to the "
+"project,\n"
+"    then run a partial recognition using \"recognize --identify\". After "
+"this\n"
+"    you have to run this command to reorder the data for the real "
+"recognition.\n"
+"    "
+msgstr ""
+"Este comando reordena as páginas pelo ID do questionário já reconhecido\n"
+"     Para usá-lo, adicione todos os arquivos ao projeto, depois\n"
+"     execute o reconhecimento parcial com \"recognize --identify\".\n"
+"     Logo após, você precisa executar este comando para reordenar\n"
+"     os dados para o real reconhecimento.\n"
+"    "
+
+#: ../sdaps/cmdline/report.py:26
+msgid "Create a PDF report."
+msgstr "Criar um relatório PDF."
+
+#: ../sdaps/cmdline/report.py:27
+msgid ""
+"This command creates a PDF report using reportlab that\n"
+"    contains statistics and if selected the freeform fields."
+msgstr ""
+"Este comando cria um relatório em PDF usando reportlab que\n"
+"     contém estatísticas e os campos de forma livre, se selecionados."
+
+#: ../sdaps/cmdline/report.py:33
+msgid "Create a filtered report for every checkbox."
+msgstr "Criar um relatório filtrado para cada caixa."
+
+#: ../sdaps/cmdline/report.py:36
+msgid "Short format (without freeform text fields)."
+msgstr "Formato curto (sem campos de texto de forma livre)."
+
+#: ../sdaps/cmdline/report.py:41
+msgid "Detailed output. (default)"
+msgstr "Saída detalhada. (padrão)"
+
+#: ../sdaps/cmdline/report.py:47 ../sdaps/cmdline/reporttex.py:30
+msgid ""
+"Do not include original images in the report. This is useful if there are "
+"privacy concerns."
+msgstr ""
+"Não incluir imagens originais no relatório. Isso é útil se houver "
+"preocupações com a privacidade."
+
+#: ../sdaps/cmdline/report.py:52 ../sdaps/cmdline/reporttex.py:35
+msgid "Do not use substitutions instead of images."
+msgstr "Não use substituições em vez de imagens."
+
+#: ../sdaps/cmdline/report.py:58 ../sdaps/cmdline/reporttex.py:46
+msgid "The paper size used for the output (default: locale dependent)"
+msgstr ""
+"O tamanho do papel usado para a saída (padrão: dependente da localidade)"
+
+#: ../sdaps/cmdline/report.py:61 ../sdaps/cmdline/reporttex.py:49
+#, python-format
+msgid "Filename to store the data to (default: report_%%i.pdf)"
+msgstr "Nome de arquivo para armazenar os dados (padrão: report_%%i.pdf)"
+
+#: ../sdaps/cmdline/reporttex.py:26
+msgid "Create a PDF report using LaTeX."
+msgstr "Criar um relatório em PDF usando LaTeX."
+
+#: ../sdaps/cmdline/reporttex.py:27
+msgid ""
+"This command creates a PDF report using LaTeX that\n"
+"    contains statistics and freeform fields."
+msgstr ""
+"Este comando cria um relatório em PDF usando LaTeX que\n"
+"     contém estatísticas e campos de forma livre."
+
+#: ../sdaps/cmdline/reporttex.py:41
+msgid "Save the generated TeX files instead of the final PDF."
+msgstr "Salve os arquivos TeX gerados em vez do PDF final."
+
+#: ../sdaps/cmdline/setup.py:28
+msgid "Create a new survey using an ODT document."
+msgstr "Criar uma nova pesquisa usando um documento ODT."
+
+#: ../sdaps/cmdline/setup.py:29
+msgid ""
+"Create a new surevey from an ODT document. The PDF export\n"
+"    of the file needs to be specified at the same time. SDAPS will import\n"
+"    metadata (properties) and the title from the ODT document."
+msgstr ""
+"Cria uma nova pesquisa de um documento ODT. O arquivo exportado em PDF\n"
+"     precisa ser especificado ao mesmo tempo. O SDAPS importará os\n"
+"     metadados (propriedades) e o título do documento ODT."
+
+#: ../sdaps/cmdline/setup.py:34
+msgid "The ODT Document"
+msgstr "O documento ODT"
+
+#: ../sdaps/cmdline/setup.py:36
+msgid "PDF export of the ODT document"
+msgstr "Arquivo PDF exportado a partir do documento ODT"
+
+#: ../sdaps/cmdline/setup.py:39 ../sdaps/cmdline/setuptex.py:39
+msgid "Additional questions that are not part of the questionnaire."
+msgstr "Outras perguntas que não fazem parte do questionário."
+
+#: ../sdaps/cmdline/setup.py:48
+msgid "Enable printing of the survey ID (default)."
+msgstr "Ativar a impressão do ID da pesquisa (padrão)."
+
+#: ../sdaps/cmdline/setup.py:52
+msgid "Disable printing of the survey ID."
+msgstr "Desativar a impressão do ID da pesquisa."
+
+#: ../sdaps/cmdline/setup.py:57
+msgid "Enable printing of the questionnaire ID."
+msgstr "Ativar a impressão do ID do questionário."
+
+#: ../sdaps/cmdline/setup.py:61
+msgid "Disable printing of the questionnaire ID (default)."
+msgstr "Desativar a impressão do ID do questionário (padrão)."
+
+#: ../sdaps/cmdline/setup.py:65
+msgid ""
+"Set an additional global ID for tracking. This can can only be used in the "
+"\"code128\" style."
+msgstr ""
+"Definir um ID Global adicional para monitoramento. Isto só pode ser "
+"utilizado no estilo \"code128\"."
+
+#: ../sdaps/cmdline/setup.py:70
+msgid ""
+"The stamping style to use. Should be either \"classic\" or \"code128\". Use "
+"\"code128\" for more features."
+msgstr ""
+"O estilo que é usado para estampar. Deve ser \"classic\" ou \"code128\". Use "
+"\"code128\" para mais recursos."
+
+#: ../sdaps/cmdline/setup.py:76
+msgid "The mode to use when detecting checkboxes."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:82
+msgid ""
+"Use duplex mode (ie. only print the IDs on the back side). Requires a "
+"mulitple of two pages."
+msgstr ""
+"Use o modo duplex (ou seja, só imprimir as identificações na parte de trás). "
+"Requer um múltiplo de duas páginas."
+
+#: ../sdaps/cmdline/setup.py:87
+msgid ""
+"Use simplex mode. IDs are printed on each page. You need a simplex scan "
+"currently!"
+msgstr ""
+"Use o modo simplex. IDs são impressos em cada página. Você precisa de uma "
+"verificação simplex atualmente!"
+
+#: ../sdaps/cmdline/setuptex.py:27
+msgid "Create a new survey using a LaTeX document."
+msgstr "Criar uma nova pesquisa através de um documento LaTeX."
+
+#: ../sdaps/cmdline/setuptex.py:28
+msgid ""
+"Create a new survey from a LaTeX document. You need to\n"
+"    be using the SDAPS class. All the metadata and options for the project\n"
+"    can be set inside the LaTeX document."
+msgstr ""
+"Criar uma nova pesquisa de um documento LaTeX. Você deve\n"
+"     utilizar a classe SDAPS. Todos os metadados e opções\n"
+"     para o projeto podem ser definidos no documento LaTeX."
+
+#: ../sdaps/cmdline/setuptex.py:33
+msgid "The LaTeX Document"
+msgstr "O documento LaTeX"
+
+#: ../sdaps/cmdline/setuptex.py:35
+msgid ""
+"Additional files that are required by the LaTeX document and need to be "
+"copied into the project directory."
+msgstr ""
+"Arquivos adicionais são necessários para o documento LaTeX e precisam ser "
+"copiados para o diretório do projeto."
+
+#: ../sdaps/cmdline/stamp.py:26
+msgid "Add marks for automatic processing."
+msgstr "Adicionar marcas para processamento automático."
+
+#: ../sdaps/cmdline/stamp.py:27
+msgid ""
+"This command creates the printable document. Depending on\n"
+"    the projects setting you are required to specifiy a source for "
+"questionnaire\n"
+"    IDs."
+msgstr ""
+"Este comando cria o documento para impressão. Dependendo das configurações\n"
+"      do projeto, você deve especificar uma fonte para os IDs de "
+"questionário."
+
+#: ../sdaps/cmdline/stamp.py:33
+msgid ""
+"If using questionnaire IDs, create N questionnaires with randomized IDs."
+msgstr ""
+"Se estiver usando IDs de questionário, cria N questionários com IDs "
+"aleatórios."
+
+#: ../sdaps/cmdline/stamp.py:36
+msgid ""
+"If using questionnaire IDs, create questionnaires from the IDs read from the "
+"specified file."
+msgstr ""
+"Se estiver usando IDs de questionário, cria questionários com os IDs lidos "
+"do arquivo especificado."
+
+#: ../sdaps/cmdline/stamp.py:39
+msgid "If using questionnaire IDs, create questionnaires for all stored IDs."
+msgstr ""
+"Se estiver usando IDs de questionário, cria questionários para todos os IDs "
+"armazenados."
+
+#: ../sdaps/cmdline/stamp.py:42
+#, python-format
+msgid "Filename to store the data to (default: stamp_%%i.pdf)"
+msgstr "Nome de arquivo para armazenar os dados (padrão: stamp_%%i.pdf)"
+
+#: ../sdaps/convert/__init__.py:35
+#, python-format
+msgid "Could not apply 3D-transformation to image '%s', page %i!"
+msgstr "Não foi possível aplicar transformação 3D na imagem '%s', página %i!"
+
+#: ../sdaps/cover/__init__.py:40
+msgid "sdaps questionnaire"
+msgstr "questionário sdaps"
+
+#: ../sdaps/gui/__init__.py:57
+msgid ""
+"The survey does not have any images! Please add images (and run recognize) "
+"before using the GUI."
+msgstr ""
+"A pesquisa não possui imagens! Por favor, adicione imagens e execute "
+"\"recognize\" antes de usar a interface gráfica (GUI)."
+
+#: ../sdaps/gui/__init__.py:202
+msgid "Page|Invalid"
+msgstr "Página|Inválida"
+
+#: ../sdaps/gui/__init__.py:205
+#, python-format
+msgid "Page %i"
+msgid_plural "Page %i"
+msgstr[0] "Página %i"
+msgstr[1] "Página %i"
+
+#: ../sdaps/gui/__init__.py:247
+msgid "Copyright © 2007-2014 The SDAPS Authors"
+msgstr "Copyright © 2007-2014 Os autores do SDAPS"
+
+#: ../sdaps/gui/__init__.py:249
+msgid "Scripts for data acquisition with paper based surveys"
+msgstr "SDAPS - Scripts para aquisição de dados de pesquisas baseadas em papel"
+
+#: ../sdaps/gui/__init__.py:250
+msgid "http://sdaps.org"
+msgstr "http://sdaps.org"
+
+#: ../sdaps/gui/__init__.py:251
+msgid "translator-credits"
+msgstr "Rodrigo Antonio Müller"
+
+#: ../sdaps/gui/__init__.py:296
+#, python-format
+msgid " of %i"
+msgstr " de %i"
+
+#: ../sdaps/gui/__init__.py:297
+#, python-format
+msgid "Recognition Quality: %.2f"
+msgstr "Qualidade de reconhecimento: %.2f"
+
+#: ../sdaps/gui/__init__.py:313
+msgid ""
+"You have reached the first page of the survey. Would you like to go to the "
+"last page?"
+msgstr ""
+"Você chegou na primeira página da pesquisa. Deseja ir para a última página?"
+
+#: ../sdaps/gui/__init__.py:315
+msgid "Go to last page"
+msgstr "Ir para a última página"
+
+#: ../sdaps/gui/__init__.py:337
+msgid ""
+"You have reached the last page of the survey. Would you like to go to the "
+"first page?"
+msgstr ""
+"Você chegou na última página da pesquisa. Deseja ir para a primeira página?"
+
+#: ../sdaps/gui/__init__.py:339
+msgid "Go to first page"
+msgstr "Ir para a primeira página"
+
+#: ../sdaps/gui/__init__.py:447
+msgid "Close without saving"
+msgstr "Fechar sem salvar"
+
+#: ../sdaps/gui/__init__.py:451
+msgid ""
+"<b>Save the project before closing?</b>\n"
+"\n"
+"If you do not save you may loose data."
+msgstr ""
+"<b>Deseja salvar o projeto antes de fechar?</b>\n"
+"\n"
+"Se não salvar, poderá perder dados."
+
+#: ../sdaps/gui/main_window.ui.h:1
+msgid "Forward"
+msgstr "Próximo"
+
+#: ../sdaps/gui/main_window.ui.h:2
+msgid "Previous"
+msgstr "Anterior"
+
+#: ../sdaps/gui/main_window.ui.h:3
+msgid "Zoom In"
+msgstr "Ampliar"
+
+#: ../sdaps/gui/main_window.ui.h:4
+msgid "Zoom Out"
+msgstr "Reduzir"
+
+#: ../sdaps/gui/main_window.ui.h:5
+msgid "SDAPS"
+msgstr "SDAPS"
+
+#: ../sdaps/gui/main_window.ui.h:6
+msgid "_File"
+msgstr "_Arquivo"
+
+#: ../sdaps/gui/main_window.ui.h:7
+msgid "_View"
+msgstr "_Exibir"
+
+#: ../sdaps/gui/main_window.ui.h:8
+msgid "_Help"
+msgstr "_Ajuda"
+
+#: ../sdaps/gui/main_window.ui.h:9
+msgid "Page Rotated"
+msgstr "Página Girada"
+
+#: ../sdaps/gui/main_window.ui.h:10
+msgid "Sort by Quality"
+msgstr "Ordenar pela Qualidade"
+
+#: ../sdaps/gui/main_window.ui.h:11
+msgid "label"
+msgstr "Rótulo"
+
+#: ../sdaps/gui/widget_buddies.py:56
+msgid "<b>Global Properties</b>"
+msgstr "<b>Propriedades Globais</b>"
+
+#: ../sdaps/gui/widget_buddies.py:60
+msgid "Sheet valid"
+msgstr "Folha válida"
+
+#: ../sdaps/gui/widget_buddies.py:61
+msgid "Verified"
+msgstr "Verificado"
+
+#: ../sdaps/gui/widget_buddies.py:62
+msgid "Empty"
+msgstr "Vazio"
+
+#: ../sdaps/gui/widget_buddies.py:74 ../sdaps/gui/widget_buddies.py:98
+msgid "<b>Questionnaire ID: </b>"
+msgstr "<b>ID do Questionário: </b>"
+
+#: ../sdaps/image/__init__.py:47
+msgid ""
+"It appears you have not build the C extension. Please run \"./setup.py build"
+"\" in the toplevel directory."
+msgstr ""
+"Parece que você não construiu a extensão C. Por favor, execute \"./setup.py "
+"build\" no diretório principal."
+
+#: ../sdaps/utils/opencv.py:31
+msgid "Cannot convert PDF files as poppler is not installed or usable!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:253
+#, python-format
+msgid "%i sheet"
+msgid_plural "%i sheets"
+msgstr[0] "%i folha"
+msgstr[1] "%i folhas"
+
+#: ../sdaps/model/survey.py:264
+#, python-format
+msgid "%f seconds per sheet"
+msgstr "%f segundos por folha"
+
+#: ../sdaps/model/survey.py:290
+msgid ""
+"A questionnaire that is printed in duplex needs an even amount of pages!"
+msgstr ""
+"Um questionário impresso em duplex necessita de uma quantidade par de "
+"páginas!"
+
+#: ../sdaps/model/survey.py:294
+msgid ""
+"The 'classic' style only supports a maximum of six pages! Use the 'code128' "
+"style if you require more pages."
+msgstr ""
+"O estilo 'classic' suporta apenas seis páginas no máximo! Use o estilo "
+"'code128' se você precisa de mais páginas."
+
+#: ../sdaps/model/survey.py:307
+msgid "IDs need to be integers in \"classic\" style!"
+msgstr "IDs precisam ser números inteiros no estilo \"clássico\"!"
+
+#: ../sdaps/model/survey.py:313
+#, python-format
+msgid "Invalid character %s in questionnaire ID \"%s\" in \"code128\" style!"
+msgstr ""
+"Caractere inválido %s no ID de questionário \"%s\" com estilo \"code128\"!"
+
+#: ../sdaps/model/survey.py:317
+msgid ""
+"SDAPS cannot draw a questionnaire ID with the \"custom\" style. Do this "
+"yourself somehow!"
+msgstr ""
+"SDAPS não pode desenhar um ID de questionário com o estilo \"custom\". Faça "
+"isto de alguma forma!"
+
+#: ../sdaps/model/survey.py:337
+#, python-format
+msgid "Running upgrade routines for file format version %i"
+msgstr ""
+"Executando rotinas de atualização para a versão de formato de arquivo %i"
+
+#. in simplex mode every page will have a matrix; it might be a None
+#. matrix though
+#: ../sdaps/recognize/buddies.py:72
+#, python-format
+msgid "%s, %i: Matrix not recognized."
+msgstr "%s, %i: Matriz não reconhecida."
+
+#: ../sdaps/recognize/buddies.py:81
+#, python-format
+msgid "%s, %i: Rotation not found."
+msgstr "%s, %i: Rotação não detectada."
+
+#. Copy the rotation over (if required) and print warning if the rotation is unknown
+#: ../sdaps/recognize/buddies.py:85
+#, python-format
+msgid "Neither %s, %i or %s, %i has a known rotation!"
+msgstr "Nem %s, %i ou %s, %i tem uma rotação conhecida!"
+
+#: ../sdaps/recognize/buddies.py:97
+#, python-format
+msgid "%s, %i: Matrix not recognized (again)."
+msgstr "%s, %i: Matriz não reconhecida (novamente)."
+
+#: ../sdaps/recognize/buddies.py:111
+#, python-format
+msgid "%s, %i: Could not get page number."
+msgstr "%s, %i: Não foi possível obter o número da página."
+
+#. Whoa, that should not happen.
+#: ../sdaps/recognize/buddies.py:131
+#, python-format
+msgid "Neither %s, %i or %s, %i has a known page number!"
+msgstr "Nem %s, %i ou %s, %i tem um número de página conhecido!"
+
+#. We don't touch the ignore flag in this case
+#. Simply print a message as this should *never* happen
+#: ../sdaps/recognize/buddies.py:142
+#, python-format
+msgid ""
+"Got a simplex document where two adjacent pages had a known page number. "
+"This should never happen as even simplex scans are converted to duplex by "
+"inserting dummy pages. Maybe you did a simplex scan but added it in duplex "
+"mode? The pages in question are %s, %i and %s, %i."
+msgstr ""
+"Tenho um documento simplex, onde duas páginas adjacentes tem um número de "
+"página conhecido. Isso nunca deveria acontecer, pois mesmo as varreduras "
+"simplex são convertidas para duplex, com a inserção de páginas falsas. Será "
+"que você fez uma varredura simplex, mas a adicionou em modo duplex? As "
+"páginas em questão são %s, %i e %s, %i."
+
+#: ../sdaps/recognize/buddies.py:159
+#, python-format
+msgid "Images %s, %i and %s, %i do not have consecutive page numbers!"
+msgstr "Imagens %s, %i e %s, %i não tem números de página consecutivos!"
+
+#: ../sdaps/recognize/buddies.py:175
+#, python-format
+msgid "No page number for page %s, %i exists."
+msgstr "Não existe número de página para a página %s, %i existe."
+
+#: ../sdaps/recognize/buddies.py:180
+#, python-format
+msgid "Page number for page %s, %i already used by another image."
+msgstr ""
+"O número de página para a página %s, %i já é utilizado por outra imagem."
+
+#: ../sdaps/recognize/buddies.py:186
+#, python-format
+msgid "Page number %i for page %s, %i is out of range."
+msgstr "O número de página %i para a página %s, %i está fora do intervalo."
+
+#: ../sdaps/recognize/buddies.py:203
+#, python-format
+msgid "%s, %i: Could not read survey ID, but should be able to."
+msgstr ""
+"%s, %i: Não foi possível ler o ID da pesquisa, mas deve ser capaz de fazer."
+
+#: ../sdaps/recognize/buddies.py:207
+#, python-format
+msgid "Could not read survey ID of either %s, %i or %s, %i!"
+msgstr "Não foi possível ler o ID da pesquisa de %s, %i ou %s, %i!"
+
+#. Broken survey ID ...
+#: ../sdaps/recognize/buddies.py:214
+#, python-format
+msgid "Got a wrong survey ID (%s, %i)! It is %s, but should be %i."
+msgstr "Tem um ID de pesquisa errado (%s, %i)! É %s, mas deve ser %i."
+
+#: ../sdaps/recognize/buddies.py:236
+#, python-format
+msgid "%s, %i: Could not read questionnaire ID, but should be able to."
+msgstr ""
+"%s, %i: Não foi possível ler o ID do questionário, mas deve ser capaz de "
+"fazer."
+
+#: ../sdaps/recognize/buddies.py:240
+#, fuzzy, python-format
+msgid "Could not read questionnaire ID of either %s, %i or %s, %i!"
+msgstr "Não foi possível ler o ID de questionário de %s, %i ou %s, %i!"
+
+#: ../sdaps/recognize/buddies.py:267
+msgid ""
+"Got different IDs on different pages for at least one sheet! Do *NOT* try to "
+"use filters with this survey! You have to run a \"reorder\" step for this to "
+"work properly!"
+msgstr ""
+"Tem diferentes IDs em páginas diferentes para pelo menos uma folha! *NÃO* "
+"tente usar filtros com esta pesquisa! Você precisa executar o passo \"reorder"
+"\" para que isso funcione corretamente!"
+
+#: ../sdaps/recognize/buddies.py:323
+msgid "No style buddy loaded. This needs to be done for the \"custom\" style!"
+msgstr ""
+"Não há definição de estilo carregada. Isto precisa ser feito para o estilo "
+"\"custom\"!"
+
+#: ../sdaps/report/answers.py:185
+#, python-format
+msgid "Answers: %i"
+msgstr "Respostas: %i"
+
+#: ../sdaps/report/answers.py:187
+#, python-format
+msgid "Mean: %.2f"
+msgstr "Média: %.2f"
+
+#: ../sdaps/report/answers.py:189
+#, python-format
+msgid "Standard Deviation: %.2f"
+msgstr "Desvio Padrão: %.2f"
+
+#: ../sdaps/report/__init__.py:75 ../sdaps/reporttex/__init__.py:140
+msgid "Turned in Questionnaires"
+msgstr "Quantidade de questionários"
+
+#: ../sdaps/report/__init__.py:92 ../sdaps/reporttex/__init__.py:139
+msgid "sdaps report"
+msgstr "Relatório SDAPS"
+
+#: ../sdaps/setup/buddies.py:61
+#, python-format
+msgid "Head %(l0)i got no title."
+msgstr "O cabeçalho %(l0)i não tem título."
+
+#: ../sdaps/setup/buddies.py:74
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got no question."
+msgstr "%(class)s %(l0)i.%(l1)i não possui qualquer questão."
+
+#: ../sdaps/setup/buddies.py:120
+#, python-format
+msgid "Error in question \"%s\""
+msgstr "Erro na questão \"%s\""
+
+#: ../sdaps/setup/buddies.py:123
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got no boxes."
+msgstr "%(class)s %(l0)i.%(l1)i não possui caixas."
+
+#: ../sdaps/setup/buddies.py:148
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got not exactly two answers."
+msgstr "%(class)s %(l0)i.%(l1)i não tem exatamente duas respostas."
+
+#: ../sdaps/setup/buddies.py:170
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got not exactly one box."
+msgstr "%(class)s %(l0)i.%(l1)i não tem exatamente uma caixa."
+
+#: ../sdaps/setupodt/__init__.py:45 ../sdaps/setuptex/__init__.py:68
+msgid "The survey directory already exists."
+msgstr "O diretório da pesquisa já existe."
+
+#: ../sdaps/setupodt/__init__.py:50
+#, python-format
+msgid ""
+"Unknown file type (%s). questionnaire_odt should be application/vnd.oasis."
+"opendocument.text."
+msgstr ""
+"Tipo de arquivo desconhecido (%s). O questionnaire_odt deve ser do tipo "
+"application/vnd.oasis.opendocument.text."
+
+#: ../sdaps/setupodt/__init__.py:55
+#, python-format
+msgid "Unknown file type (%s). questionnaire_pdf should be application/pdf."
+msgstr ""
+"Tipo de arquivo desconhecido (%s). O questionnaire_pdf deve ser do tipo "
+"application/pdf."
+
+#: ../sdaps/setupodt/__init__.py:61 ../sdaps/setuptex/__init__.py:79
+#, python-format
+msgid "Unknown file type (%s). additionalqobjects should be text/plain."
+msgstr ""
+"Tipo de arquivo desconhecido (%s). O additionalqobjects deve ser do tipo "
+"text/plain."
+
+#: ../sdaps/setupodt/__init__.py:90
+msgid "Caught an Exception while parsing the ODT file. The current state is:"
+msgstr "Houve uma exceção ao analisar o arquivo ODT. O estado atual é:"
+
+#: ../sdaps/setupodt/__init__.py:93
+msgid ""
+"If the dependencies for the \"annotate\" command are installed, then an "
+"annotated version will be created next to the original PDF file."
+msgstr ""
+"Se as dependências para o comando \"annotate\" estão instaladas, uma versão "
+"anotada será criada junto ao arquivo PDF original."
+
+#: ../sdaps/setupodt/__init__.py:123 ../sdaps/setuptex/__init__.py:147
+msgid ""
+"Some combination of options and project properties do not work. Aborted "
+"Setup."
+msgstr ""
+"Uma combinação de opções e propriedades do projeto não funcionam. Instalação "
+"abortada."
+
+#: ../sdaps/setupodt/boxesparser.py:121 ../sdaps/setupodt/boxesparser.py:190
+#, python-format
+msgid ""
+"Warning: Ignoring a box (page: %i, x: %.1f, y: %.1f, width: %.1f, height: "
+"%.1f)."
+msgstr ""
+"Aviso: Ignorando uma caixa (página: %i, x: %.1f, y: %.1f, largura: %.1f, "
+"altura: %.1f)."
+
+#: ../sdaps/stamp/__init__.py:38
+msgid ""
+"You may not specify the number of sheets for this survey. All questionnaires "
+"will be identical as the survey has been configured to not use questionnaire "
+"IDs for each sheet."
+msgstr ""
+"Você não pode especificar o número de folhas para esta pesquisa. Todos os "
+"questionários serão idênticos já que a pesquisa foi configurada para não "
+"usar IDs de questionário em cada folha."
+
+#: ../sdaps/stamp/__init__.py:76
+msgid ""
+"This survey has been configured to use questionnaire IDs. Each questionnaire "
+"will be unique. You need to use on of the options to add new IDs or use the "
+"existing ones."
+msgstr ""
+"Esta pesquisa foi configurada para usar IDs de questionário. Cada "
+"questionário será único. Por isso, uma das opções deve ser usada, para "
+"adicionar novos IDs ou usar já armazenados."
+
+#: ../sdaps/stamp/generic.py:37
+#, python-format
+msgid "Survey-ID: %i"
+msgstr "ID da Pesquisa: %i"
+
+#: ../sdaps/stamp/generic.py:51
+#, python-format
+msgid "Questionnaire-ID: %i"
+msgstr "ID do Questionário: %i"
+
+#: ../sdaps/stamp/generic.py:336
+msgid ""
+"You need to have either pdftk or pyPdf installed. pdftk is the faster method."
+msgstr ""
+"Você precisa ter o pdftk ou o pyPdf instalado. pdftk é o método mais rápido."
+
+#. bottomup = False =>(0, 0) is the upper left corner
+#: ../sdaps/stamp/generic.py:352
+#, python-format
+msgid "Creating stamp PDF for %i sheet"
+msgid_plural "Creating stamp PDF for %i sheets"
+msgstr[0] "Criando estampa PDF para %i folha"
+msgstr[1] "Criando estampa PDF para %i folhas"
+
+#: ../sdaps/stamp/generic.py:410 ../sdaps/stamp/generic.py:543
+#, python-format
+msgid "%i sheet; %f seconds per sheet"
+msgid_plural "%i sheet; %f seconds per sheet"
+msgstr[0] "%i folha; %f segundos por folha"
+msgstr[1] "%i folhas; %f segundos por folha"
+
+#. Merge using pdftk
+#: ../sdaps/stamp/generic.py:419
+msgid "Stamping using pdftk"
+msgstr "Estampando com pdftk"
+
+#. Shortcut if we only have one sheet.
+#. In this case form data in the PDF will *not* break, in
+#. the other code path it *will* break.
+#: ../sdaps/stamp/generic.py:426
+msgid "pdftk: Overlaying the original PDF with the markings."
+msgstr "pdftk: Sobrepondo o PDF original com as marcas."
+
+#: ../sdaps/stamp/generic.py:435
+#, python-format
+msgid "pdftk: Splitting out page %d of each sheet."
+msgid_plural "pdftk: Splitting out page %d of each sheet."
+msgstr[0] "pdftk: Extraindo a página %d de cada folha."
+msgstr[1] "pdftk: Extraindo a página %d de cada folha."
+
+#: ../sdaps/stamp/generic.py:449
+msgid "pdftk: Splitting the questionnaire for watermarking."
+msgstr "pdftk: Dividindo o questionário para marca d'água."
+
+#: ../sdaps/stamp/generic.py:460 ../sdaps/stamp/generic.py:469
+#, python-format
+msgid "pdftk: Watermarking page %d of all sheets."
+msgid_plural "pdftk: Watermarking page %d of all sheets."
+msgstr[0] "pdftk: Criando marca d'água na página %d de todas as folhas."
+msgstr[1] "pdftk: Criando marca d'água na página %d de todas as folhas."
+
+#: ../sdaps/stamp/generic.py:492
+msgid "pdftk: Assembling everything into the final PDF."
+msgstr "pdftk: Montando tudo no PDF final."
+
+#: ../sdaps/stamp/generic.py:529
+msgid "Stamping using pyPdf. For faster stamping, install pdftk."
+msgstr "Estampando com pyPdf. Para estampar mais rapidamente, instale o pdftk."
+
+#: ../sdaps/stamp/latex.py:21
+msgid ""
+"There should be no need to stamp a SDAPS Project that uses LaTeX and does "
+"not have different questionnaire IDs printed on each sheet.\n"
+"I am going to do so anyways."
+msgstr ""
+"Não deveria ser necessário estampar um projeto SDAPS que usa LaTeX e não tem "
+"diferentes IDs de questionário impresso em cada folha.\n"
+"Vou fazê-lo de qualquer maneira."
+
+#: ../sdaps/stamp/latex.py:43
+#, python-format
+msgid "Running %s now twice to generate the stamped questionnaire."
+msgstr "Executando %s duas vezes para gerar o questionário estampado."
+
+#: ../sdaps/stamp/latex.py:47 ../sdaps/setuptex/__init__.py:119
+#: ../sdaps/setuptex/__init__.py:158 ../sdaps/reporttex/__init__.py:161
+#, python-format
+msgid "Error running \"%s\" to compile the LaTeX file."
+msgstr "Erro executando \"%s\" para compilar o arquivo LaTeX."
+
+#: ../sdaps/stamp/latex.py:53
+#, python-format
+msgid ""
+"An error occured during creation of the report. Temporary files left in '%s'."
+msgstr ""
+"Ocorreu um erro durante a criação do relatório. Os arquivos temporários "
+"foram deixados em '%s'."
+
+#: ../sdaps/setuptex/__init__.py:73
+#, python-format
+msgid "Unknown file type (%s). questionnaire_tex should be of type text/x-tex."
+msgstr ""
+"Tipo de arquivo desconhecido (%s). O questionnaire_tex deve ser do tipo text/"
+"x-tex."
+
+#: ../sdaps/setuptex/__init__.py:74
+msgid "Will keep going, but expect failure!"
+msgstr "Vai continuar, mas espere por falhas!"
+
+#: ../sdaps/setuptex/__init__.py:115 ../sdaps/setuptex/__init__.py:153
+#, python-format
+msgid "Running %s now twice to generate the questionnaire."
+msgstr "Executando %s duas vezes para gerar o questionário."
+
+#: ../sdaps/setuptex/__init__.py:133
+msgid "Caught an Exception while parsing the SDAPS file. The current state is:"
+msgstr "Houve uma exceção ao analisar o arquivo SDAPS. O estado atual é:"
+
+#: ../sdaps/setuptex/__init__.py:174
+msgid ""
+"An error occured in the setup routine. The survey directory still exists. "
+"You can for example check the questionnaire.log file for LaTeX compile "
+"errors."
+msgstr ""
+"Ocorreu um erro na rotina de instalação. O diretório de pesquisa ainda "
+"existe. Você pode ver o arquivo questionnaire.log para verificar erros de "
+"compilação do LaTeX."
+
+#: ../sdaps/reporttex/__init__.py:107
+msgid "author|Unknown"
+msgstr "autor|Desconhecido"
+
+#: ../sdaps/reporttex/__init__.py:138
+msgid "tex language|english"
+msgstr "brazilian"
+
+#: ../sdaps/reporttex/__init__.py:154
+#, python-format
+msgid "The TeX project with the report data is located at '%s'."
+msgstr "O projeto TeX com os dados do relatório está localizado em '%s'."
+
+#: ../sdaps/reporttex/__init__.py:157
+#, python-format
+msgid "Running %s now twice to generate the report."
+msgstr "Executando %s duas vezes para gerar o relatório."
+
+#: ../sdaps/reporttex/__init__.py:167
+#, python-format
+msgid "An occured during creation of the report. Temporary files left in '%s'."
+msgstr ""
+"Ocorreu um erro durante a criação do relatório. Os arquivos temporários "
+"foram deixados em '%s'."
diff --git a/po/pt_BR.po b/po/pt_BR.po
new file mode 100644
index 0000000..9ea355a
--- /dev/null
+++ b/po/pt_BR.po
@@ -0,0 +1,1258 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: SDAPS 0.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-03-13 19:45+0100\n"
+"PO-Revision-Date: 2015-04-11 22:45+0200\n"
+"Last-Translator: Thiago Casotti <thiago.casotti at uol.com.br>\n"
+"Language-Team: Portuguese (Brazil) "
+"<https://hosted.weblate.org/projects/sdaps/master/pt_BR/>\n"
+"Language: pt_BR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 2.3-dev\n"
+
+#: ../sdaps/script.py:41
+msgid "SDAPS -- Paper based survey tool."
+msgstr "SDAPS -- Ferramenta de questionários baseados em papel."
+
+#: ../sdaps/script.py:45
+msgid "project directory|The SDAPS project."
+msgstr "diretório do projeto|Projeto SDAPS."
+
+#: ../sdaps/script.py:46
+msgid "command list|Commands:"
+msgstr "lista de comandos|Comandos:"
+
+#: ../sdaps/add/__init__.py:55
+#, python-format
+msgid ""
+"Invalid input file %s. You need to specify a (multipage) monochrome TIFF as "
+"input."
+msgstr ""
+"Arquivo de entrada inválido %s. Você precisa especificar uma imagem TIFF "
+"(multipáginas) monocromática como entrada."
+
+#: ../sdaps/add/__init__.py:67
+#, python-format
+msgid ""
+"Not adding %s because it has a wrong page count (needs to be a mulitple of "
+"%i)."
+msgstr ""
+"Não adicionando %s porque ele tem um número de páginas errado (deve ser um "
+"múltiplo de %i)."
+
+#: ../sdaps/boxgallery/__init__.py:108
+#, python-format
+msgid "Rendering boxgallery for metric \"%s\"."
+msgstr "Renderizando caixas pela métrica \"%s\"."
+
+#: ../sdaps/log.py:37
+msgid "Warning: "
+msgstr "Atenção: "
+
+#: ../sdaps/log.py:41
+msgid "Error: "
+msgstr "Erro: "
+
+#: ../sdaps/cmdline/add.py:31
+msgid "Add scanned questionnaires to the survey."
+msgstr "Adicionar questionários digitalizados na pesquisa."
+
+#: ../sdaps/cmdline/add.py:32
+msgid ""
+"This command is used to add scanned images to the survey.\n"
+"    The image data needs to be a (multipage) 300dpi monochrome TIFF file. "
+"You\n"
+"    may choose not to copy the data into the project directory. In that "
+"case\n"
+"    the data will be referenced using a relative path."
+msgstr ""
+"Este comando é usado para adicionar imagens digitalizadas na pesquisa.\n"
+"     A imagem precisa ser em formato TIFF (multipáginas) monocromático de "
+"300dpi.\n"
+"     Você pode optar por não copiar os dados para o diretório do projeto. "
+"Nesse\n"
+"     caso os dados serão referenciados usando um caminho relativo."
+
+#: ../sdaps/cmdline/add.py:38
+msgid "Convert given files and add the result."
+msgstr "Converter os arquivos fornecidos e adicionar o resultado."
+
+#: ../sdaps/cmdline/add.py:43 ../sdaps/cmdline/convert.py:39
+msgid ""
+"Do a 3D-transformation after finding the corner marks. If the\n"
+"        corner marks are not found then the image will be added as-is."
+msgstr ""
+"Faz uma transformação 3D depois de encontrar as marcas de canto.\n"
+"        Se as marcas não forem encontradas, a imagem é adicionada como é."
+
+#: ../sdaps/cmdline/add.py:49
+msgid ""
+"Force adding the images even if the page count is wrong (only use if you "
+"know what you are doing)."
+msgstr ""
+"Força a adição das imagens, mesmo que a contagem de páginas esteja errada "
+"(só use se você sabe o que está fazendo)."
+
+#: ../sdaps/cmdline/add.py:53
+msgid "Copy the files into the directory (default)."
+msgstr "Copie os arquivos para o diretório (padrão)."
+
+#: ../sdaps/cmdline/add.py:58
+msgid "Do not copy the files into the directory."
+msgstr "Não copie os arquivos para o diretório."
+
+#: ../sdaps/cmdline/add.py:62
+msgid ""
+"Images contain a duplex scan of a simplex questionnaire (default: simplex "
+"scan)."
+msgstr ""
+"As imagens contêm uma digitalização duplex de um questionário simplex "
+"(padrão: digitalização simplex)."
+
+#: ../sdaps/cmdline/add.py:68 ../sdaps/cmdline/convert.py:49
+msgid "A number of TIFF image files."
+msgstr "Um número de arquivos de imagem TIFF."
+
+#: ../sdaps/cmdline/add.py:94
+msgid "The --no-copy option is not compatible with --convert!"
+msgstr "A opção --no-copy não é compatível com --convert!"
+
+#: ../sdaps/cmdline/add.py:103
+msgid "Converting input files into a single temporary file."
+msgstr "Converter arquivos de entrada em um diretório temporário."
+
+#: ../sdaps/cmdline/add.py:115
+msgid ""
+"The page count of the created temporary file does not work with this survey."
+msgstr ""
+"A imagem convertida não tem o formato correto. O número de páginas está "
+"errado."
+
+#: ../sdaps/cmdline/add.py:120
+msgid "Running the conversion failed."
+msgstr "A conversão falhou."
+
+#: ../sdaps/cmdline/add.py:125
+#, python-format
+msgid "Processing %s"
+msgstr "Processando %s"
+
+#: ../sdaps/cmdline/add.py:129
+msgid "Done"
+msgstr "Concluído"
+
+#: ../sdaps/cmdline/annotate.py:28
+msgid "Annotate the questionnaire and show the recognized positions."
+msgstr "Anotar o questionário e mostrar as posições reconhecidas."
+
+#: ../sdaps/cmdline/annotate.py:29
+msgid ""
+"This command is mainly a debug utility. It creates an\n"
+"    annotated version of the questionnaire, with the information that SDAPS\n"
+"    knows about it overlayed on top."
+msgstr ""
+"Este comando é principalmente um utilitário de depuração. Ele cria uma\n"
+"    versão anotada do questionário, com as informações que o SDAPS\n"
+"    sabe sobre ele sobrepostas na parte superior."
+
+#: ../sdaps/cmdline/boxgallery.py:31
+msgid "Create PDFs with boxes sorted by the detection heuristics."
+msgstr "Cria PDFs com caixas ordenadas por heurísticas de detecção."
+
+#: ../sdaps/cmdline/boxgallery.py:32
+msgid ""
+"SDAPS uses multiple heuristics to detect determine the\n"
+"    state of checkboxes. There is a list for each heuristic giving the "
+"expected\n"
+"    state and the quality of the value (see defs.py). Using this command a "
+"PDF\n"
+"    will be created for each of the heuristics so that one can adjust the\n"
+"    values."
+msgstr ""
+"O SDAPS usa várias heurísticas para determinar o estado das caixas.\n"
+"    Há uma lista para cada heurística dando o estado esperado e a qualidade\n"
+"    do valor (ver defs.py). Usando este comando um PDF será criado para "
+"cada\n"
+"    uma das heurísticas, para que se possa ajustar os valores."
+
+#: ../sdaps/cmdline/boxgallery.py:40
+msgid ""
+"Reruns part of the recognition process and retrieves debug images from this "
+"step."
+msgstr ""
+"Executa o reconhecimento novamente e armazena imagens adicionais para "
+"depuração a partir deste passo."
+
+#: ../sdaps/cmdline/convert.py:30
+msgid "Convert a set of images to the correct image format."
+msgstr "Converte um conjunto de imagens para o formato de imagem correto."
+
+#: ../sdaps/cmdline/convert.py:31
+msgid ""
+"This command can be used if you scanned files in a different\n"
+"        mode than the expected monochrome TIFF file. All the given files "
+"will\n"
+"        be loaded, converted to monochrome and stored into a multipage 1bpp\n"
+"        TIFF file. Optionally you can select that a 3D transformation "
+"should\n"
+"        be performed, using this it may be possible to work with photos of\n"
+"        questionnaires instead of scans."
+msgstr ""
+"Esse comando pode ser usado se você tiver arquivos digitalizados em formato "
+"diferente\n"
+"\tao TIFF monocromático esperado. Todos os arquivos serão carregados,\n"
+"\tconvertidos em monocromático e armazenados em um arquivo TIFF multipágina "
+"de 1bpp.\n"
+"\tOpcionalmente, você pode selecionar que se realize uma transformação 3D,\n"
+"\tcom isto pode ser possível trabalhar com fotos dos questionários, ao "
+"invés\n"
+"\tda digitalização convencional."
+
+#: ../sdaps/cmdline/convert.py:45
+msgid "The location of the output file. Must be given."
+msgstr "A localização do arquivo de saída. Deve ser fornecida."
+
+#: ../sdaps/cmdline/convert.py:58
+msgid "No output filename specified!"
+msgstr "Nome do arquivo de saída não especificado!"
+
+#: ../sdaps/cmdline/cover.py:27
+msgid "Create a cover for the questionnaires."
+msgstr "Criar uma página de rosto para os questionários."
+
+#: ../sdaps/cmdline/cover.py:28
+msgid ""
+"This command creates a cover page for questionnaires. All\n"
+"    the metadata of the survey will be printed on the page."
+msgstr ""
+"Este comando cria uma página de rosto para os questionários.\n"
+"    Todos os metadados da pesquisa serão impressos nela."
+
+#: ../sdaps/cmdline/cover.py:31
+#, python-format
+msgid "Filename to store the data to (default: cover_%%i.pdf)"
+msgstr "Nome de arquivo para armazenar os dados (padrão: cover_%%i.pdf)"
+
+#: ../sdaps/cmdline/csvdata.py:34
+msgid "Import or export data to/from CSV files."
+msgstr "Importar ou exportar dados de/para arquivos CSV."
+
+#: ../sdaps/cmdline/csvdata.py:35
+msgid ""
+"Import or export data to/from a CSV file. The first line\n"
+"    is a header which defines questionnaire_id and global_id, and a column\n"
+"    for each checkbox and textfield. Note that the import is currently very\n"
+"    limited, as you need to specifiy the questionnaire ID to select the "
+"sheet\n"
+"    which should be updated."
+msgstr ""
+"Importar ou exportar dados de/para um arquivo CSV. A primeira linha\n"
+"     é um cabeçalho, que define questionnaire_id, global_id e uma coluna\n"
+"     para cada caixa de seleção e campo de texto. Note que a importação é\n"
+"     atualmente muito limitada, já que você precisa especificar o "
+"identificador\n"
+"     do questionário para selecionar a folha que deverá ser atualizada."
+
+#: ../sdaps/cmdline/csvdata.py:44
+msgid "Export data to CSV file."
+msgstr "Exportar dados para arquivo CSV."
+
+#: ../sdaps/cmdline/csvdata.py:46
+#, python-format
+msgid "Filename to store the data to (default: data_%%i.csv)"
+msgstr "Nome de arquivo para armazenar os dados (padrão: data_%%i.csv)"
+
+#: ../sdaps/cmdline/csvdata.py:48
+msgid "The delimiter used in the CSV file (default ',')"
+msgstr "O delimitador usado no arquivo CSV (default ',')"
+
+#: ../sdaps/cmdline/csvdata.py:52 ../sdaps/cmdline/report.py:31
+#: ../sdaps/cmdline/reporttex.py:52
+msgid "Filter to only export a partial dataset."
+msgstr "Filtro para exportar apenas um conjunto de dados parcial."
+
+#: ../sdaps/cmdline/csvdata.py:54
+msgid "Export images of freeform fields."
+msgstr "Exportar imagens dos campos de forma livre."
+
+#: ../sdaps/cmdline/csvdata.py:60
+msgid "Export an image for each question that includes all boxes."
+msgstr "Exportar uma imagem para cada pergunta que inclui todas as caixas."
+
+#: ../sdaps/cmdline/csvdata.py:66
+msgid "Export the recognition quality for each checkbox."
+msgstr "Exportar a qualidade de reconhecimento para cada caixa de verificação."
+
+#: ../sdaps/cmdline/csvdata.py:74
+msgid "Import data to from a CSV file."
+msgstr "Importar dados de um arquivo CSV."
+
+#: ../sdaps/cmdline/csvdata.py:76
+msgid "The file to import."
+msgstr "O arquivo a ser importado."
+
+#: ../sdaps/cmdline/gui.py:28
+msgid "Launch a gui. You can view and alter the (recognized) answers with it."
+msgstr ""
+"Abrir uma interface gráfica (GUI). Você pode visualizar e alterar as "
+"respostas (reconhecidas) nela."
+
+#: ../sdaps/cmdline/gui.py:29
+msgid ""
+"This command launches a graphical user interface that can\n"
+"    be used to correct answer. You need to run \"recognize\" before using "
+"it.\n"
+"    "
+msgstr ""
+"Este comando lança uma interface gráfica (GUI) que pode ser utilizada para\n"
+"     corrigir respostas. Você precisa executar \"recognize\" antes de usá-"
+"lo.\n"
+"    "
+
+#: ../sdaps/cmdline/gui.py:34
+msgid "Filter to only show a partial dataset."
+msgstr "Filtro para mostrar apenas um conjunto de dados parcial."
+
+#: ../sdaps/cmdline/ids.py:29
+msgid "Export and import questionnaire IDs."
+msgstr "Exportação e importação de IDs de questionário."
+
+#: ../sdaps/cmdline/ids.py:30
+msgid ""
+"This command can be used to import and export questionnaire\n"
+"    IDs. It only makes sense in projects where such an ID is printed on the\n"
+"    questionnaire. Note that you can also add IDs by using the stamp "
+"command,\n"
+"    which will give you the PDF at the same time."
+msgstr ""
+"Esse comando pode ser usado para importar e exportar IDs de questionário.\n"
+"     Ele só faz sentido em projetos onde tal identificação é impressa no\n"
+"     questionário. Note que você também pode adicionar IDs usando o comando\n"
+"     \"stamp\", que também lhe dará o arquivo PDF."
+
+#: ../sdaps/cmdline/ids.py:35
+#, python-format
+msgid "Filename to store the data to (default: ids_%%i)"
+msgstr "Nome de arquivo para armazenar os dados (padrão: ids_%%i)"
+
+#: ../sdaps/cmdline/ids.py:38
+msgid "Add IDs to the internal list from the specified file."
+msgstr "Adicionar IDs à lista interna do arquivo especificado."
+
+#: ../sdaps/cmdline/info.py:28
+msgid "Display and modify metadata of project."
+msgstr "Exibir e modificar os metadados do projeto."
+
+#: ../sdaps/cmdline/info.py:29
+msgid ""
+"This command lets you modify the metadata of the SDAPS\n"
+"    project. You can modify, add and remove arbitrary keys that will be "
+"printed\n"
+"    on the report. The only key that always exist is \"title\".\n"
+"    If no key is given then a list of defined keys is printed."
+msgstr ""
+"Este comando permite que você modifique os metadados do projeto SDAPS.\n"
+"     Você pode modificar, adicionar e remover chaves arbitrárias que serão "
+"impressas\n"
+"     no relatório. A única chave que sempre existe é \"title\".\n"
+"     Se nenhuma chave for informada, uma lista de chaves definidas é "
+"impressa."
+
+#: ../sdaps/cmdline/info.py:36
+msgid "The key to display or modify."
+msgstr "A chave para exibir ou modificar."
+
+#: ../sdaps/cmdline/info.py:40
+msgid "Set the given key to this value."
+msgstr "Novo valor para a chave."
+
+#: ../sdaps/cmdline/info.py:44
+msgid "Delete the key and value pair."
+msgstr "Exclua o par de chave e valor."
+
+#: ../sdaps/cmdline/info.py:68
+msgid "Existing fields:\n"
+msgstr "Campos existentes:\n"
+
+#: ../sdaps/cmdline/recognize.py:28
+msgid "Run the optical mark recognition."
+msgstr "Executar o reconhecimento óptico de marcas."
+
+#: ../sdaps/cmdline/recognize.py:29
+msgid ""
+"Iterates over all images and runs the optical mark\n"
+"    recognition. It will reevaluate sheets even if \"recognize\" has "
+"already\n"
+"    run or manual changes were made."
+msgstr ""
+"Itera sobre todas as imagens e executa o reconhecimento óptico de marcas.\n"
+"     Ele irá reavaliar as folhas mesmo se o comando \"recognize\" for\n"
+"     executado previamente ou alterações manuais tenham sido feitas."
+
+#: ../sdaps/cmdline/recognize.py:34
+msgid ""
+"Only identify the page properties, but don't recognize the checkbox states."
+msgstr "Analisa apenas as propriedades da página, mas não as respostas."
+
+#: ../sdaps/cmdline/recognize.py:39
+msgid ""
+"Rerun the recognition for all pages. The default is to skip all pages that "
+"were recognized or verified already."
+msgstr ""
+"Execute novamente o reconhecimento para todas as páginas. O padrão é pular "
+"todas as páginas que já foram reconhecidas ou verificadas."
+
+#: ../sdaps/cmdline/reorder.py:26
+msgid "Reorder pages according to questionnaire ID."
+msgstr "Reordenar páginas de acordo com o ID do questionário."
+
+#: ../sdaps/cmdline/reorder.py:27
+msgid ""
+"This command reorders all pages according to the already\n"
+"    recognized questionnaire ID. To use it add all the files to the "
+"project,\n"
+"    then run a partial recognition using \"recognize --identify\". After "
+"this\n"
+"    you have to run this command to reorder the data for the real "
+"recognition.\n"
+"    "
+msgstr ""
+"Este comando reordena as páginas pelo ID do questionário já reconhecido\n"
+"     Para usá-lo, adicione todos os arquivos ao projeto, depois\n"
+"     execute o reconhecimento parcial com \"recognize --identify\".\n"
+"     Logo após, você precisa executar este comando para reordenar\n"
+"     os dados para o real reconhecimento.\n"
+"    "
+
+#: ../sdaps/cmdline/report.py:26
+msgid "Create a PDF report."
+msgstr "Criar um relatório PDF."
+
+#: ../sdaps/cmdline/report.py:27
+msgid ""
+"This command creates a PDF report using reportlab that\n"
+"    contains statistics and if selected the freeform fields."
+msgstr ""
+"Este comando cria um relatório em PDF usando reportlab que\n"
+"     contém estatísticas e os campos de forma livre, se selecionados."
+
+#: ../sdaps/cmdline/report.py:33
+msgid "Create a filtered report for every checkbox."
+msgstr "Criar um relatório filtrado para cada caixa."
+
+#: ../sdaps/cmdline/report.py:36
+msgid "Short format (without freeform text fields)."
+msgstr "Formato curto (sem campos de texto de forma livre)."
+
+#: ../sdaps/cmdline/report.py:41
+msgid "Detailed output. (default)"
+msgstr "Saída detalhada. (padrão)"
+
+#: ../sdaps/cmdline/report.py:47 ../sdaps/cmdline/reporttex.py:30
+msgid ""
+"Do not include original images in the report. This is useful if there are "
+"privacy concerns."
+msgstr ""
+"Não incluir imagens originais no relatório. Isso é útil se houver "
+"preocupações com a privacidade."
+
+#: ../sdaps/cmdline/report.py:52 ../sdaps/cmdline/reporttex.py:35
+msgid "Do not use substitutions instead of images."
+msgstr "Não use substituições em vez de imagens."
+
+#: ../sdaps/cmdline/report.py:58 ../sdaps/cmdline/reporttex.py:46
+msgid "The paper size used for the output (default: locale dependent)"
+msgstr ""
+"O tamanho do papel usado para a saída (padrão: dependente da localidade)"
+
+#: ../sdaps/cmdline/report.py:61 ../sdaps/cmdline/reporttex.py:49
+#, python-format
+msgid "Filename to store the data to (default: report_%%i.pdf)"
+msgstr "Nome de arquivo para armazenar os dados (padrão: report_%%i.pdf)"
+
+#: ../sdaps/cmdline/reporttex.py:26
+msgid "Create a PDF report using LaTeX."
+msgstr "Criar um relatório em PDF usando LaTeX."
+
+#: ../sdaps/cmdline/reporttex.py:27
+msgid ""
+"This command creates a PDF report using LaTeX that\n"
+"    contains statistics and freeform fields."
+msgstr ""
+"Este comando cria um relatório em PDF usando LaTeX que\n"
+"     contém estatísticas e campos de forma livre."
+
+#: ../sdaps/cmdline/reporttex.py:41
+msgid "Save the generated TeX files instead of the final PDF."
+msgstr "Salve os arquivos TeX gerados em vez do PDF final."
+
+#: ../sdaps/cmdline/setup.py:28
+msgid "Create a new survey using an ODT document."
+msgstr "Criar uma nova pesquisa usando um documento ODT."
+
+#: ../sdaps/cmdline/setup.py:29
+msgid ""
+"Create a new surevey from an ODT document. The PDF export\n"
+"    of the file needs to be specified at the same time. SDAPS will import\n"
+"    metadata (properties) and the title from the ODT document."
+msgstr ""
+"Cria uma nova pesquisa de um documento ODT. O arquivo exportado em PDF\n"
+"     precisa ser especificado ao mesmo tempo. O SDAPS importará os\n"
+"     metadados (propriedades) e o título do documento ODT."
+
+#: ../sdaps/cmdline/setup.py:34
+msgid "The ODT Document"
+msgstr "O documento ODT"
+
+#: ../sdaps/cmdline/setup.py:36
+msgid "PDF export of the ODT document"
+msgstr "Arquivo PDF exportado a partir do documento ODT"
+
+#: ../sdaps/cmdline/setup.py:39 ../sdaps/cmdline/setuptex.py:39
+msgid "Additional questions that are not part of the questionnaire."
+msgstr "Outras perguntas que não fazem parte do questionário."
+
+#: ../sdaps/cmdline/setup.py:48
+msgid "Enable printing of the survey ID (default)."
+msgstr "Ativar a impressão do ID da pesquisa (padrão)."
+
+#: ../sdaps/cmdline/setup.py:52
+msgid "Disable printing of the survey ID."
+msgstr "Desativar a impressão do ID da pesquisa."
+
+#: ../sdaps/cmdline/setup.py:57
+msgid "Enable printing of the questionnaire ID."
+msgstr "Ativar a impressão do ID do questionário."
+
+#: ../sdaps/cmdline/setup.py:61
+msgid "Disable printing of the questionnaire ID (default)."
+msgstr "Desativar a impressão do ID do questionário (padrão)."
+
+#: ../sdaps/cmdline/setup.py:65
+msgid ""
+"Set an additional global ID for tracking. This can can only be used in the "
+"\"code128\" style."
+msgstr ""
+"Definir um ID Global adicional para monitoramento. Isto só pode ser "
+"utilizado no estilo \"code128\"."
+
+#: ../sdaps/cmdline/setup.py:70
+msgid ""
+"The stamping style to use. Should be either \"classic\" or \"code128\". Use "
+"\"code128\" for more features."
+msgstr ""
+"O estilo que é usado para estampar. Deve ser \"classic\" ou \"code128\". Use "
+"\"code128\" para mais recursos."
+
+#: ../sdaps/cmdline/setup.py:76
+msgid "The mode to use when detecting checkboxes."
+msgstr "Modo de detecção das caixas de seleção."
+
+#: ../sdaps/cmdline/setup.py:82
+msgid ""
+"Use duplex mode (ie. only print the IDs on the back side). Requires a "
+"mulitple of two pages."
+msgstr ""
+"Use o modo duplex (ou seja, só imprimir as identificações na parte de trás). "
+"Requer um múltiplo de duas páginas."
+
+#: ../sdaps/cmdline/setup.py:87
+msgid ""
+"Use simplex mode. IDs are printed on each page. You need a simplex scan "
+"currently!"
+msgstr ""
+"Use o modo simplex. IDs são impressos em cada página. Você precisa de uma "
+"verificação simplex atualmente!"
+
+#: ../sdaps/cmdline/setuptex.py:27
+msgid "Create a new survey using a LaTeX document."
+msgstr "Criar uma nova pesquisa através de um documento LaTeX."
+
+#: ../sdaps/cmdline/setuptex.py:28
+msgid ""
+"Create a new survey from a LaTeX document. You need to\n"
+"    be using the SDAPS class. All the metadata and options for the project\n"
+"    can be set inside the LaTeX document."
+msgstr ""
+"Criar uma nova pesquisa de um documento LaTeX. Você deve\n"
+"     utilizar a classe SDAPS. Todos os metadados e opções\n"
+"     para o projeto podem ser definidos no documento LaTeX."
+
+#: ../sdaps/cmdline/setuptex.py:33
+msgid "The LaTeX Document"
+msgstr "O documento LaTeX"
+
+#: ../sdaps/cmdline/setuptex.py:35
+msgid ""
+"Additional files that are required by the LaTeX document and need to be "
+"copied into the project directory."
+msgstr ""
+"Arquivos adicionais são necessários para o documento LaTeX e precisam ser "
+"copiados para o diretório do projeto."
+
+#: ../sdaps/cmdline/stamp.py:26
+msgid "Add marks for automatic processing."
+msgstr "Adicionar marcas para processamento automático."
+
+#: ../sdaps/cmdline/stamp.py:27
+msgid ""
+"This command creates the printable document. Depending on\n"
+"    the projects setting you are required to specifiy a source for "
+"questionnaire\n"
+"    IDs."
+msgstr ""
+"Este comando cria o documento para impressão. Dependendo das configurações\n"
+"      do projeto, você deve especificar uma fonte para os IDs de "
+"questionário."
+
+#: ../sdaps/cmdline/stamp.py:33
+msgid ""
+"If using questionnaire IDs, create N questionnaires with randomized IDs."
+msgstr ""
+"Se estiver usando IDs de questionário, cria N questionários com IDs "
+"aleatórios."
+
+#: ../sdaps/cmdline/stamp.py:36
+msgid ""
+"If using questionnaire IDs, create questionnaires from the IDs read from the "
+"specified file."
+msgstr ""
+"Se estiver usando IDs de questionário, cria questionários com os IDs lidos "
+"do arquivo especificado."
+
+#: ../sdaps/cmdline/stamp.py:39
+msgid "If using questionnaire IDs, create questionnaires for all stored IDs."
+msgstr ""
+"Se estiver usando IDs de questionário, cria questionários para todos os IDs "
+"armazenados."
+
+#: ../sdaps/cmdline/stamp.py:42
+#, python-format
+msgid "Filename to store the data to (default: stamp_%%i.pdf)"
+msgstr "Nome de arquivo para armazenar os dados (padrão: stamp_%%i.pdf)"
+
+#: ../sdaps/convert/__init__.py:35
+#, python-format
+msgid "Could not apply 3D-transformation to image '%s', page %i!"
+msgstr "Não foi possível aplicar transformação 3D na imagem '%s', página %i!"
+
+#: ../sdaps/cover/__init__.py:40
+msgid "sdaps questionnaire"
+msgstr "questionário sdaps"
+
+#: ../sdaps/gui/__init__.py:57
+msgid ""
+"The survey does not have any images! Please add images (and run recognize) "
+"before using the GUI."
+msgstr ""
+"A pesquisa não possui imagens! Por favor, adicione imagens e execute "
+"\"recognize\" antes de usar a interface gráfica (GUI)."
+
+#: ../sdaps/gui/__init__.py:202
+msgid "Page|Invalid"
+msgstr "Página|Inválida"
+
+#: ../sdaps/gui/__init__.py:205
+#, python-format
+msgid "Page %i"
+msgid_plural "Page %i"
+msgstr[0] "Página %i"
+msgstr[1] "Página %i"
+
+#: ../sdaps/gui/__init__.py:247
+msgid "Copyright © 2007-2014 The SDAPS Authors"
+msgstr "Copyright © 2007-2014 Os autores do SDAPS"
+
+#: ../sdaps/gui/__init__.py:249
+msgid "Scripts for data acquisition with paper based surveys"
+msgstr "SDAPS - Scripts para aquisição de dados de pesquisas baseadas em papel"
+
+#: ../sdaps/gui/__init__.py:250
+msgid "http://sdaps.org"
+msgstr "http://sdaps.org"
+
+#: ../sdaps/gui/__init__.py:251
+msgid "translator-credits"
+msgstr "Rodrigo Antonio Müller"
+
+#: ../sdaps/gui/__init__.py:296
+#, python-format
+msgid " of %i"
+msgstr " de %i"
+
+#: ../sdaps/gui/__init__.py:297
+#, python-format
+msgid "Recognition Quality: %.2f"
+msgstr "Qualidade de reconhecimento: %.2f"
+
+#: ../sdaps/gui/__init__.py:313
+msgid ""
+"You have reached the first page of the survey. Would you like to go to the "
+"last page?"
+msgstr ""
+"Você chegou na primeira página da pesquisa. Deseja ir para a última página?"
+
+#: ../sdaps/gui/__init__.py:315
+msgid "Go to last page"
+msgstr "Ir para a última página"
+
+#: ../sdaps/gui/__init__.py:337
+msgid ""
+"You have reached the last page of the survey. Would you like to go to the "
+"first page?"
+msgstr ""
+"Você chegou na última página da pesquisa. Deseja ir para a primeira página?"
+
+#: ../sdaps/gui/__init__.py:339
+msgid "Go to first page"
+msgstr "Ir para a primeira página"
+
+#: ../sdaps/gui/__init__.py:447
+msgid "Close without saving"
+msgstr "Fechar sem salvar"
+
+#: ../sdaps/gui/__init__.py:451
+msgid ""
+"<b>Save the project before closing?</b>\n"
+"\n"
+"If you do not save you may loose data."
+msgstr ""
+"<b>Deseja salvar o projeto antes de fechar?</b>\n"
+"\n"
+"Se não salvar, poderá perder dados."
+
+#: ../sdaps/gui/main_window.ui.h:1
+msgid "Forward"
+msgstr "Próximo"
+
+#: ../sdaps/gui/main_window.ui.h:2
+msgid "Previous"
+msgstr "Anterior"
+
+#: ../sdaps/gui/main_window.ui.h:3
+msgid "Zoom In"
+msgstr "Ampliar"
+
+#: ../sdaps/gui/main_window.ui.h:4
+msgid "Zoom Out"
+msgstr "Reduzir"
+
+#: ../sdaps/gui/main_window.ui.h:5
+msgid "SDAPS"
+msgstr "SDAPS"
+
+#: ../sdaps/gui/main_window.ui.h:6
+msgid "_File"
+msgstr "_Arquivo"
+
+#: ../sdaps/gui/main_window.ui.h:7
+msgid "_View"
+msgstr "_Exibir"
+
+#: ../sdaps/gui/main_window.ui.h:8
+msgid "_Help"
+msgstr "_Ajuda"
+
+#: ../sdaps/gui/main_window.ui.h:9
+msgid "Page Rotated"
+msgstr "Página Girada"
+
+#: ../sdaps/gui/main_window.ui.h:10
+msgid "Sort by Quality"
+msgstr "Ordenar pela Qualidade"
+
+#: ../sdaps/gui/main_window.ui.h:11
+msgid "label"
+msgstr "Rótulo"
+
+#: ../sdaps/gui/widget_buddies.py:56
+msgid "<b>Global Properties</b>"
+msgstr "<b>Propriedades Globais</b>"
+
+#: ../sdaps/gui/widget_buddies.py:60
+msgid "Sheet valid"
+msgstr "Folha válida"
+
+#: ../sdaps/gui/widget_buddies.py:61
+msgid "Verified"
+msgstr "Verificado"
+
+#: ../sdaps/gui/widget_buddies.py:62
+msgid "Empty"
+msgstr "Vazio"
+
+#: ../sdaps/gui/widget_buddies.py:74 ../sdaps/gui/widget_buddies.py:98
+msgid "<b>Questionnaire ID: </b>"
+msgstr "<b>ID do Questionário: </b>"
+
+#: ../sdaps/image/__init__.py:47
+msgid ""
+"It appears you have not build the C extension. Please run \"./setup.py build"
+"\" in the toplevel directory."
+msgstr ""
+"Parece que você não construiu a extensão C. Por favor, execute \"./setup.py "
+"build\" no diretório principal."
+
+#: ../sdaps/utils/opencv.py:31
+msgid "Cannot convert PDF files as poppler is not installed or usable!"
+msgstr ""
+"Não é possível converter arquivos PDF, pois o poppler não está instalado ou "
+"utilizável!"
+
+#: ../sdaps/model/survey.py:253
+#, python-format
+msgid "%i sheet"
+msgid_plural "%i sheets"
+msgstr[0] "%i folha"
+msgstr[1] "%i folhas"
+
+#: ../sdaps/model/survey.py:264
+#, python-format
+msgid "%f seconds per sheet"
+msgstr "%f segundos por folha"
+
+#: ../sdaps/model/survey.py:290
+msgid ""
+"A questionnaire that is printed in duplex needs an even amount of pages!"
+msgstr ""
+"Um questionário impresso em duplex necessita de uma quantidade par de "
+"páginas!"
+
+#: ../sdaps/model/survey.py:294
+msgid ""
+"The 'classic' style only supports a maximum of six pages! Use the 'code128' "
+"style if you require more pages."
+msgstr ""
+"O estilo 'classic' suporta apenas seis páginas no máximo! Use o estilo "
+"'code128' se você precisa de mais páginas."
+
+#: ../sdaps/model/survey.py:307
+msgid "IDs need to be integers in \"classic\" style!"
+msgstr "IDs precisam ser números inteiros no estilo \"clássico\"!"
+
+#: ../sdaps/model/survey.py:313
+#, python-format
+msgid "Invalid character %s in questionnaire ID \"%s\" in \"code128\" style!"
+msgstr ""
+"Caractere inválido %s no ID de questionário \"%s\" com estilo \"code128\"!"
+
+#: ../sdaps/model/survey.py:317
+msgid ""
+"SDAPS cannot draw a questionnaire ID with the \"custom\" style. Do this "
+"yourself somehow!"
+msgstr ""
+"SDAPS não pode desenhar um ID de questionário com o estilo \"custom\". Faça "
+"isto de alguma forma!"
+
+#: ../sdaps/model/survey.py:337
+#, python-format
+msgid "Running upgrade routines for file format version %i"
+msgstr ""
+"Executando rotinas de atualização para a versão de formato de arquivo %i"
+
+#. in simplex mode every page will have a matrix; it might be a None
+#. matrix though
+#: ../sdaps/recognize/buddies.py:72
+#, python-format
+msgid "%s, %i: Matrix not recognized."
+msgstr "%s, %i: Matriz não reconhecida."
+
+#: ../sdaps/recognize/buddies.py:81
+#, python-format
+msgid "%s, %i: Rotation not found."
+msgstr "%s, %i: Rotação não detectada."
+
+#. Copy the rotation over (if required) and print warning if the rotation is unknown
+#: ../sdaps/recognize/buddies.py:85
+#, python-format
+msgid "Neither %s, %i or %s, %i has a known rotation!"
+msgstr "Nem %s, %i ou %s, %i tem uma rotação conhecida!"
+
+#: ../sdaps/recognize/buddies.py:97
+#, python-format
+msgid "%s, %i: Matrix not recognized (again)."
+msgstr "%s, %i: Matriz não reconhecida (novamente)."
+
+#: ../sdaps/recognize/buddies.py:111
+#, python-format
+msgid "%s, %i: Could not get page number."
+msgstr "%s, %i: Não foi possível obter o número da página."
+
+#. Whoa, that should not happen.
+#: ../sdaps/recognize/buddies.py:131
+#, python-format
+msgid "Neither %s, %i or %s, %i has a known page number!"
+msgstr "Nem %s, %i ou %s, %i tem um número de página conhecido!"
+
+#. We don't touch the ignore flag in this case
+#. Simply print a message as this should *never* happen
+#: ../sdaps/recognize/buddies.py:142
+#, python-format
+msgid ""
+"Got a simplex document where two adjacent pages had a known page number. "
+"This should never happen as even simplex scans are converted to duplex by "
+"inserting dummy pages. Maybe you did a simplex scan but added it in duplex "
+"mode? The pages in question are %s, %i and %s, %i."
+msgstr ""
+"Tenho um documento simplex, onde duas páginas adjacentes tem um número de "
+"página conhecido. Isso nunca deveria acontecer, pois mesmo as varreduras "
+"simplex são convertidas para duplex, com a inserção de páginas falsas. Será "
+"que você fez uma varredura simplex, mas a adicionou em modo duplex? As "
+"páginas em questão são %s, %i e %s, %i."
+
+#: ../sdaps/recognize/buddies.py:159
+#, python-format
+msgid "Images %s, %i and %s, %i do not have consecutive page numbers!"
+msgstr "Imagens %s, %i e %s, %i não tem números de página consecutivos!"
+
+#: ../sdaps/recognize/buddies.py:175
+#, python-format
+msgid "No page number for page %s, %i exists."
+msgstr "Não existe número de página para a página %s, %i existe."
+
+#: ../sdaps/recognize/buddies.py:180
+#, python-format
+msgid "Page number for page %s, %i already used by another image."
+msgstr ""
+"O número de página para a página %s, %i já é utilizado por outra imagem."
+
+#: ../sdaps/recognize/buddies.py:186
+#, python-format
+msgid "Page number %i for page %s, %i is out of range."
+msgstr "O número de página %i para a página %s, %i está fora do intervalo."
+
+#: ../sdaps/recognize/buddies.py:203
+#, python-format
+msgid "%s, %i: Could not read survey ID, but should be able to."
+msgstr ""
+"%s, %i: Não foi possível ler o ID da pesquisa, mas deve ser capaz de fazer."
+
+#: ../sdaps/recognize/buddies.py:207
+#, python-format
+msgid "Could not read survey ID of either %s, %i or %s, %i!"
+msgstr "Não foi possível ler o ID da pesquisa de %s, %i ou %s, %i!"
+
+#. Broken survey ID ...
+#: ../sdaps/recognize/buddies.py:214
+#, python-format
+msgid "Got a wrong survey ID (%s, %i)! It is %s, but should be %i."
+msgstr "Tem um ID de pesquisa errado (%s, %i)! É %s, mas deve ser %i."
+
+#: ../sdaps/recognize/buddies.py:236
+#, python-format
+msgid "%s, %i: Could not read questionnaire ID, but should be able to."
+msgstr ""
+"%s, %i: Não foi possível ler o ID do questionário, mas deve ser capaz de "
+"fazer."
+
+#: ../sdaps/recognize/buddies.py:240
+#, python-format
+msgid "Could not read questionnaire ID of either %s, %i or %s, %i!"
+msgstr "Não foi possível ler o ID de questionário de %s, %i ou %s, %i!"
+
+#: ../sdaps/recognize/buddies.py:267
+msgid ""
+"Got different IDs on different pages for at least one sheet! Do *NOT* try to "
+"use filters with this survey! You have to run a \"reorder\" step for this to "
+"work properly!"
+msgstr ""
+"Tem diferentes IDs em páginas diferentes para pelo menos uma folha! *NÃO* "
+"tente usar filtros com esta pesquisa! Você precisa executar o passo \"reorder"
+"\" para que isso funcione corretamente!"
+
+#: ../sdaps/recognize/buddies.py:323
+msgid "No style buddy loaded. This needs to be done for the \"custom\" style!"
+msgstr ""
+"Não há definição de estilo carregada. Isto precisa ser feito para o estilo "
+"\"custom\"!"
+
+#: ../sdaps/report/answers.py:185
+#, python-format
+msgid "Answers: %i"
+msgstr "Respostas: %i"
+
+#: ../sdaps/report/answers.py:187
+#, python-format
+msgid "Mean: %.2f"
+msgstr "Média: %.2f"
+
+#: ../sdaps/report/answers.py:189
+#, python-format
+msgid "Standard Deviation: %.2f"
+msgstr "Desvio Padrão: %.2f"
+
+#: ../sdaps/report/__init__.py:75 ../sdaps/reporttex/__init__.py:140
+msgid "Turned in Questionnaires"
+msgstr "Quantidade de questionários"
+
+#: ../sdaps/report/__init__.py:92 ../sdaps/reporttex/__init__.py:139
+msgid "sdaps report"
+msgstr "Relatório SDAPS"
+
+#: ../sdaps/setup/buddies.py:61
+#, python-format
+msgid "Head %(l0)i got no title."
+msgstr "O cabeçalho %(l0)i não tem título."
+
+#: ../sdaps/setup/buddies.py:74
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got no question."
+msgstr "%(class)s %(l0)i.%(l1)i não possui qualquer questão."
+
+#: ../sdaps/setup/buddies.py:120
+#, python-format
+msgid "Error in question \"%s\""
+msgstr "Erro na questão \"%s\""
+
+#: ../sdaps/setup/buddies.py:123
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got no boxes."
+msgstr "%(class)s %(l0)i.%(l1)i não possui caixas."
+
+#: ../sdaps/setup/buddies.py:148
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got not exactly two answers."
+msgstr "%(class)s %(l0)i.%(l1)i não tem exatamente duas respostas."
+
+#: ../sdaps/setup/buddies.py:170
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got not exactly one box."
+msgstr "%(class)s %(l0)i.%(l1)i não tem exatamente uma caixa."
+
+#: ../sdaps/setupodt/__init__.py:45 ../sdaps/setuptex/__init__.py:68
+msgid "The survey directory already exists."
+msgstr "O diretório da pesquisa já existe."
+
+#: ../sdaps/setupodt/__init__.py:50
+#, python-format
+msgid ""
+"Unknown file type (%s). questionnaire_odt should be application/vnd.oasis."
+"opendocument.text."
+msgstr ""
+"Tipo de arquivo desconhecido (%s). O questionnaire_odt deve ser do tipo "
+"application/vnd.oasis.opendocument.text."
+
+#: ../sdaps/setupodt/__init__.py:55
+#, python-format
+msgid "Unknown file type (%s). questionnaire_pdf should be application/pdf."
+msgstr ""
+"Tipo de arquivo desconhecido (%s). O questionnaire_pdf deve ser do tipo "
+"application/pdf."
+
+#: ../sdaps/setupodt/__init__.py:61 ../sdaps/setuptex/__init__.py:79
+#, python-format
+msgid "Unknown file type (%s). additionalqobjects should be text/plain."
+msgstr ""
+"Tipo de arquivo desconhecido (%s). O additionalqobjects deve ser do tipo "
+"text/plain."
+
+#: ../sdaps/setupodt/__init__.py:90
+msgid "Caught an Exception while parsing the ODT file. The current state is:"
+msgstr "Houve uma exceção ao analisar o arquivo ODT. O estado atual é:"
+
+#: ../sdaps/setupodt/__init__.py:93
+msgid ""
+"If the dependencies for the \"annotate\" command are installed, then an "
+"annotated version will be created next to the original PDF file."
+msgstr ""
+"Se as dependências para o comando \"annotate\" estão instaladas, uma versão "
+"anotada será criada junto ao arquivo PDF original."
+
+#: ../sdaps/setupodt/__init__.py:123 ../sdaps/setuptex/__init__.py:147
+msgid ""
+"Some combination of options and project properties do not work. Aborted "
+"Setup."
+msgstr ""
+"Uma combinação de opções e propriedades do projeto não funcionam. Instalação "
+"abortada."
+
+#: ../sdaps/setupodt/boxesparser.py:121 ../sdaps/setupodt/boxesparser.py:190
+#, python-format
+msgid ""
+"Warning: Ignoring a box (page: %i, x: %.1f, y: %.1f, width: %.1f, height: "
+"%.1f)."
+msgstr ""
+"Aviso: Ignorando uma caixa (página: %i, x: %.1f, y: %.1f, largura: %.1f, "
+"altura: %.1f)."
+
+#: ../sdaps/stamp/__init__.py:38
+msgid ""
+"You may not specify the number of sheets for this survey. All questionnaires "
+"will be identical as the survey has been configured to not use questionnaire "
+"IDs for each sheet."
+msgstr ""
+"Você não pode especificar o número de folhas para esta pesquisa. Todos os "
+"questionários serão idênticos já que a pesquisa foi configurada para não "
+"usar IDs de questionário em cada folha."
+
+#: ../sdaps/stamp/__init__.py:76
+msgid ""
+"This survey has been configured to use questionnaire IDs. Each questionnaire "
+"will be unique. You need to use on of the options to add new IDs or use the "
+"existing ones."
+msgstr ""
+"Esta pesquisa foi configurada para usar IDs de questionário. Cada "
+"questionário será único. Por isso, uma das opções deve ser usada, para "
+"adicionar novos IDs ou usar já armazenados."
+
+#: ../sdaps/stamp/generic.py:37
+#, python-format
+msgid "Survey-ID: %i"
+msgstr "ID da Pesquisa: %i"
+
+#: ../sdaps/stamp/generic.py:51
+#, python-format
+msgid "Questionnaire-ID: %i"
+msgstr "ID do Questionário: %i"
+
+#: ../sdaps/stamp/generic.py:336
+msgid ""
+"You need to have either pdftk or pyPdf installed. pdftk is the faster method."
+msgstr ""
+"Você precisa ter o pdftk ou o pyPdf instalado. pdftk é o método mais rápido."
+
+#. bottomup = False =>(0, 0) is the upper left corner
+#: ../sdaps/stamp/generic.py:352
+#, python-format
+msgid "Creating stamp PDF for %i sheet"
+msgid_plural "Creating stamp PDF for %i sheets"
+msgstr[0] "Criando estampa PDF para %i folha"
+msgstr[1] "Criando estampa PDF para %i folhas"
+
+#: ../sdaps/stamp/generic.py:410 ../sdaps/stamp/generic.py:543
+#, python-format
+msgid "%i sheet; %f seconds per sheet"
+msgid_plural "%i sheet; %f seconds per sheet"
+msgstr[0] "%i folha; %f segundos por folha"
+msgstr[1] "%i folhas; %f segundos por folha"
+
+#. Merge using pdftk
+#: ../sdaps/stamp/generic.py:419
+msgid "Stamping using pdftk"
+msgstr "Estampando com pdftk"
+
+#. Shortcut if we only have one sheet.
+#. In this case form data in the PDF will *not* break, in
+#. the other code path it *will* break.
+#: ../sdaps/stamp/generic.py:426
+msgid "pdftk: Overlaying the original PDF with the markings."
+msgstr "pdftk: Sobrepondo o PDF original com as marcas."
+
+#: ../sdaps/stamp/generic.py:435
+#, python-format
+msgid "pdftk: Splitting out page %d of each sheet."
+msgid_plural "pdftk: Splitting out page %d of each sheet."
+msgstr[0] "pdftk: Extraindo a página %d de cada folha."
+msgstr[1] "pdftk: Extraindo a página %d de cada folha."
+
+#: ../sdaps/stamp/generic.py:449
+msgid "pdftk: Splitting the questionnaire for watermarking."
+msgstr "pdftk: Dividindo o questionário para marca d'água."
+
+#: ../sdaps/stamp/generic.py:460 ../sdaps/stamp/generic.py:469
+#, python-format
+msgid "pdftk: Watermarking page %d of all sheets."
+msgid_plural "pdftk: Watermarking page %d of all sheets."
+msgstr[0] "pdftk: Criando marca d'água na página %d de todas as folhas."
+msgstr[1] "pdftk: Criando marca d'água na página %d de todas as folhas."
+
+#: ../sdaps/stamp/generic.py:492
+msgid "pdftk: Assembling everything into the final PDF."
+msgstr "pdftk: Montando tudo no PDF final."
+
+#: ../sdaps/stamp/generic.py:529
+msgid "Stamping using pyPdf. For faster stamping, install pdftk."
+msgstr "Estampando com pyPdf. Para estampar mais rapidamente, instale o pdftk."
+
+#: ../sdaps/stamp/latex.py:21
+msgid ""
+"There should be no need to stamp a SDAPS Project that uses LaTeX and does "
+"not have different questionnaire IDs printed on each sheet.\n"
+"I am going to do so anyways."
+msgstr ""
+"Não deveria ser necessário estampar um projeto SDAPS que usa LaTeX e não tem "
+"diferentes IDs de questionário impresso em cada folha.\n"
+"Vou fazê-lo de qualquer maneira."
+
+#: ../sdaps/stamp/latex.py:43
+#, python-format
+msgid "Running %s now twice to generate the stamped questionnaire."
+msgstr "Executando %s duas vezes para gerar o questionário estampado."
+
+#: ../sdaps/stamp/latex.py:47 ../sdaps/setuptex/__init__.py:119
+#: ../sdaps/setuptex/__init__.py:158 ../sdaps/reporttex/__init__.py:161
+#, python-format
+msgid "Error running \"%s\" to compile the LaTeX file."
+msgstr "Erro executando \"%s\" para compilar o arquivo LaTeX."
+
+#: ../sdaps/stamp/latex.py:53
+#, python-format
+msgid ""
+"An error occured during creation of the report. Temporary files left in '%s'."
+msgstr ""
+"Ocorreu um erro durante a criação do relatório. Os arquivos temporários "
+"foram deixados em '%s'."
+
+#: ../sdaps/setuptex/__init__.py:73
+#, python-format
+msgid "Unknown file type (%s). questionnaire_tex should be of type text/x-tex."
+msgstr ""
+"Tipo de arquivo desconhecido (%s). O questionnaire_tex deve ser do tipo text/"
+"x-tex."
+
+#: ../sdaps/setuptex/__init__.py:74
+msgid "Will keep going, but expect failure!"
+msgstr "Vai continuar, mas espere por falhas!"
+
+#: ../sdaps/setuptex/__init__.py:115 ../sdaps/setuptex/__init__.py:153
+#, python-format
+msgid "Running %s now twice to generate the questionnaire."
+msgstr "Executando %s duas vezes para gerar o questionário."
+
+#: ../sdaps/setuptex/__init__.py:133
+msgid "Caught an Exception while parsing the SDAPS file. The current state is:"
+msgstr "Houve uma exceção ao analisar o arquivo SDAPS. O estado atual é:"
+
+#: ../sdaps/setuptex/__init__.py:174
+msgid ""
+"An error occured in the setup routine. The survey directory still exists. "
+"You can for example check the questionnaire.log file for LaTeX compile "
+"errors."
+msgstr ""
+"Ocorreu um erro na rotina de instalação. O diretório de pesquisa ainda "
+"existe. Você pode ver o arquivo questionnaire.log para verificar erros de "
+"compilação do LaTeX."
+
+#: ../sdaps/reporttex/__init__.py:107
+msgid "author|Unknown"
+msgstr "autor|Desconhecido"
+
+#: ../sdaps/reporttex/__init__.py:138
+msgid "tex language|english"
+msgstr "brazilian"
+
+#: ../sdaps/reporttex/__init__.py:154
+#, python-format
+msgid "The TeX project with the report data is located at '%s'."
+msgstr "O projeto TeX com os dados do relatório está localizado em '%s'."
+
+#: ../sdaps/reporttex/__init__.py:157
+#, python-format
+msgid "Running %s now twice to generate the report."
+msgstr "Executando %s duas vezes para gerar o relatório."
+
+#: ../sdaps/reporttex/__init__.py:167
+#, python-format
+msgid "An occured during creation of the report. Temporary files left in '%s'."
+msgstr ""
+"Ocorreu um erro durante a criação do relatório. Os arquivos temporários "
+"foram deixados em '%s'."
diff --git a/po/sdaps.pot b/po/sdaps.pot
new file mode 100644
index 0000000..489737b
--- /dev/null
+++ b/po/sdaps.pot
@@ -0,0 +1,1089 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-11-19 20:54+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
+
+#: ../sdaps/script.py:41
+msgid "SDAPS -- Paper based survey tool."
+msgstr ""
+
+#: ../sdaps/script.py:45
+msgid "project directory|The SDAPS project."
+msgstr ""
+
+#: ../sdaps/script.py:46
+msgid "command list|Commands:"
+msgstr ""
+
+#: ../sdaps/add/__init__.py:55
+#, python-format
+msgid ""
+"Invalid input file %s. You need to specify a (multipage) monochrome TIFF as "
+"input."
+msgstr ""
+
+#: ../sdaps/add/__init__.py:67
+#, python-format
+msgid ""
+"Not adding %s because it has a wrong page count (needs to be a mulitple of "
+"%i)."
+msgstr ""
+
+#: ../sdaps/boxgallery/__init__.py:108
+#, python-format
+msgid "Rendering boxgallery for metric \"%s\"."
+msgstr ""
+
+#: ../sdaps/log.py:37
+msgid "Warning: "
+msgstr ""
+
+#: ../sdaps/log.py:41
+msgid "Error: "
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:31
+msgid "Add scanned questionnaires to the survey."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:32
+msgid ""
+"This command is used to add scanned images to the survey.\n"
+"    The image data needs to be a (multipage) 300dpi monochrome TIFF file. "
+"You\n"
+"    may choose not to copy the data into the project directory. In that "
+"case\n"
+"    the data will be referenced using a relative path."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:38
+msgid "Convert given files and add the result."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:43 ../sdaps/cmdline/convert.py:39
+msgid ""
+"Do a 3D-transformation after finding the corner marks. If the\n"
+"        corner marks are not found then the image will be added as-is."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:49
+msgid ""
+"Force adding the images even if the page count is wrong (only use if you "
+"know what you are doing)."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:53
+msgid "Copy the files into the directory (default)."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:58
+msgid "Do not copy the files into the directory."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:62
+msgid ""
+"Images contain a duplex scan of a simplex questionnaire (default: simplex "
+"scan)."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:68 ../sdaps/cmdline/convert.py:49
+msgid "A number of TIFF image files."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:94
+msgid "The --no-copy option is not compatible with --convert!"
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:103
+msgid "Converting input files into a single temporary file."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:115
+msgid ""
+"The page count of the created temporary file does not work with this survey."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:120
+msgid "Running the conversion failed."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:125
+#, python-format
+msgid "Processing %s"
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:129
+msgid "Done"
+msgstr ""
+
+#: ../sdaps/cmdline/annotate.py:28
+msgid "Annotate the questionnaire and show the recognized positions."
+msgstr ""
+
+#: ../sdaps/cmdline/annotate.py:29
+msgid ""
+"This command is mainly a debug utility. It creates an\n"
+"    annotated version of the questionnaire, with the information that SDAPS\n"
+"    knows about it overlayed on top."
+msgstr ""
+
+#: ../sdaps/cmdline/boxgallery.py:31
+msgid "Create PDFs with boxes sorted by the detection heuristics."
+msgstr ""
+
+#: ../sdaps/cmdline/boxgallery.py:32
+msgid ""
+"SDAPS uses multiple heuristics to detect determine the\n"
+"    state of checkboxes. There is a list for each heuristic giving the "
+"expected\n"
+"    state and the quality of the value (see defs.py). Using this command a "
+"PDF\n"
+"    will be created for each of the heuristics so that one can adjust the\n"
+"    values."
+msgstr ""
+
+#: ../sdaps/cmdline/boxgallery.py:40
+msgid ""
+"Reruns part of the recognition process and retrieves debug images from this "
+"step."
+msgstr ""
+
+#: ../sdaps/cmdline/convert.py:30
+msgid "Convert a set of images to the correct image format."
+msgstr ""
+
+#: ../sdaps/cmdline/convert.py:31
+msgid ""
+"This command can be used if you scanned files in a different\n"
+"        mode than the expected monochrome TIFF file. All the given files "
+"will\n"
+"        be loaded, converted to monochrome and stored into a multipage 1bpp\n"
+"        TIFF file. Optionally you can select that a 3D transformation "
+"should\n"
+"        be performed, using this it may be possible to work with photos of\n"
+"        questionnaires instead of scans."
+msgstr ""
+
+#: ../sdaps/cmdline/convert.py:45
+msgid "The location of the output file. Must be given."
+msgstr ""
+
+#: ../sdaps/cmdline/convert.py:58
+msgid "No output filename specified!"
+msgstr ""
+
+#: ../sdaps/cmdline/cover.py:27
+msgid "Create a cover for the questionnaires."
+msgstr ""
+
+#: ../sdaps/cmdline/cover.py:28
+msgid ""
+"This command creates a cover page for questionnaires. All\n"
+"    the metadata of the survey will be printed on the page."
+msgstr ""
+
+#: ../sdaps/cmdline/cover.py:31
+#, python-format
+msgid "Filename to store the data to (default: cover_%%i.pdf)"
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:34
+msgid "Import or export data to/from CSV files."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:35
+msgid ""
+"Import or export data to/from a CSV file. The first line\n"
+"    is a header which defines questionnaire_id and global_id, and a column\n"
+"    for each checkbox and textfield. Note that the import is currently very\n"
+"    limited, as you need to specifiy the questionnaire ID to select the "
+"sheet\n"
+"    which should be updated."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:44
+msgid "Export data to CSV file."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:46
+#, python-format
+msgid "Filename to store the data to (default: data_%%i.csv)"
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:48
+msgid "The delimiter used in the CSV file (default ',')"
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:52 ../sdaps/cmdline/report.py:31
+#: ../sdaps/cmdline/reporttex.py:52
+msgid "Filter to only export a partial dataset."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:54
+msgid "Export images of freeform fields."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:60
+msgid "Export an image for each question that includes all boxes."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:66
+msgid "Export the recognition quality for each checkbox."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:74
+msgid "Import data to from a CSV file."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:76
+msgid "The file to import."
+msgstr ""
+
+#: ../sdaps/cmdline/gui.py:28
+msgid "Launch a gui. You can view and alter the (recognized) answers with it."
+msgstr ""
+
+#: ../sdaps/cmdline/gui.py:29
+msgid ""
+"This command launches a graphical user interface that can\n"
+"    be used to correct answer. You need to run \"recognize\" before using "
+"it.\n"
+"    "
+msgstr ""
+
+#: ../sdaps/cmdline/gui.py:34
+msgid "Filter to only show a partial dataset."
+msgstr ""
+
+#: ../sdaps/cmdline/ids.py:29
+msgid "Export and import questionnaire IDs."
+msgstr ""
+
+#: ../sdaps/cmdline/ids.py:30
+msgid ""
+"This command can be used to import and export questionnaire\n"
+"    IDs. It only makes sense in projects where such an ID is printed on the\n"
+"    questionnaire. Note that you can also add IDs by using the stamp "
+"command,\n"
+"    which will give you the PDF at the same time."
+msgstr ""
+
+#: ../sdaps/cmdline/ids.py:35
+#, python-format
+msgid "Filename to store the data to (default: ids_%%i)"
+msgstr ""
+
+#: ../sdaps/cmdline/ids.py:38
+msgid "Add IDs to the internal list from the specified file."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:28
+msgid "Display and modify metadata of project."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:29
+msgid ""
+"This command lets you modify the metadata of the SDAPS\n"
+"    project. You can modify, add and remove arbitrary keys that will be "
+"printed\n"
+"    on the report. The only key that always exist is \"title\".\n"
+"    If no key is given then a list of defined keys is printed."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:36
+msgid "The key to display or modify."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:40
+msgid "Set the given key to this value."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:44
+msgid "Delete the key and value pair."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:68
+msgid "Existing fields:\n"
+msgstr ""
+
+#: ../sdaps/cmdline/recognize.py:28
+msgid "Run the optical mark recognition."
+msgstr ""
+
+#: ../sdaps/cmdline/recognize.py:29
+msgid ""
+"Iterates over all images and runs the optical mark\n"
+"    recognition. It will reevaluate sheets even if \"recognize\" has "
+"already\n"
+"    run or manual changes were made."
+msgstr ""
+
+#: ../sdaps/cmdline/recognize.py:34
+msgid ""
+"Only identify the page properties, but don't recognize the checkbox states."
+msgstr ""
+
+#: ../sdaps/cmdline/recognize.py:39
+msgid ""
+"Rerun the recognition for all pages. The default is to skip all pages that "
+"were recognized or verified already."
+msgstr ""
+
+#: ../sdaps/cmdline/reorder.py:26
+msgid "Reorder pages according to questionnaire ID."
+msgstr ""
+
+#: ../sdaps/cmdline/reorder.py:27
+msgid ""
+"This command reorders all pages according to the already\n"
+"    recognized questionnaire ID. To use it add all the files to the "
+"project,\n"
+"    then run a partial recognition using \"recognize --identify\". After "
+"this\n"
+"    you have to run this command to reorder the data for the real "
+"recognition.\n"
+"    "
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:26
+msgid "Create a PDF report."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:27
+msgid ""
+"This command creates a PDF report using reportlab that\n"
+"    contains statistics and if selected the freeform fields."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:33
+msgid "Create a filtered report for every checkbox."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:36
+msgid "Short format (without freeform text fields)."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:41
+msgid "Detailed output. (default)"
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:47 ../sdaps/cmdline/reporttex.py:30
+msgid ""
+"Do not include original images in the report. This is useful if there are "
+"privacy concerns."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:52 ../sdaps/cmdline/reporttex.py:35
+msgid "Do not use substitutions instead of images."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:58 ../sdaps/cmdline/reporttex.py:46
+msgid "The paper size used for the output (default: locale dependent)"
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:61 ../sdaps/cmdline/reporttex.py:49
+#, python-format
+msgid "Filename to store the data to (default: report_%%i.pdf)"
+msgstr ""
+
+#: ../sdaps/cmdline/reporttex.py:26
+msgid "Create a PDF report using LaTeX."
+msgstr ""
+
+#: ../sdaps/cmdline/reporttex.py:27
+msgid ""
+"This command creates a PDF report using LaTeX that\n"
+"    contains statistics and freeform fields."
+msgstr ""
+
+#: ../sdaps/cmdline/reporttex.py:41
+msgid "Save the generated TeX files instead of the final PDF."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:28
+msgid "Create a new survey using an ODT document."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:29
+msgid ""
+"Create a new surevey from an ODT document. The PDF export\n"
+"    of the file needs to be specified at the same time. SDAPS will import\n"
+"    metadata (properties) and the title from the ODT document."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:34
+msgid "The ODT Document"
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:36
+msgid "PDF export of the ODT document"
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:39 ../sdaps/cmdline/setuptex.py:39
+msgid "Additional questions that are not part of the questionnaire."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:48
+msgid "Enable printing of the survey ID (default)."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:52
+msgid "Disable printing of the survey ID."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:57
+msgid "Enable printing of the questionnaire ID."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:61
+msgid "Disable printing of the questionnaire ID (default)."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:65
+msgid ""
+"Set an additional global ID for tracking. This can can only be used in the "
+"\"code128\" style."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:70
+msgid ""
+"The stamping style to use. Should be either \"classic\" or \"code128\". Use "
+"\"code128\" for more features."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:76
+msgid "The mode to use when detecting checkboxes."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:82
+msgid ""
+"Use duplex mode (ie. only print the IDs on the back side). Requires a "
+"mulitple of two pages."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:87
+msgid ""
+"Use simplex mode. IDs are printed on each page. You need a simplex scan "
+"currently!"
+msgstr ""
+
+#: ../sdaps/cmdline/setuptex.py:27
+msgid "Create a new survey using a LaTeX document."
+msgstr ""
+
+#: ../sdaps/cmdline/setuptex.py:28
+msgid ""
+"Create a new survey from a LaTeX document. You need to\n"
+"    be using the SDAPS class. All the metadata and options for the project\n"
+"    can be set inside the LaTeX document."
+msgstr ""
+
+#: ../sdaps/cmdline/setuptex.py:33
+msgid "The LaTeX Document"
+msgstr ""
+
+#: ../sdaps/cmdline/setuptex.py:35
+msgid ""
+"Additional files that are required by the LaTeX document and need to be "
+"copied into the project directory."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:26
+msgid "Add marks for automatic processing."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:27
+msgid ""
+"This command creates the printable document. Depending on\n"
+"    the projects setting you are required to specifiy a source for "
+"questionnaire\n"
+"    IDs."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:33
+msgid ""
+"If using questionnaire IDs, create N questionnaires with randomized IDs."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:36
+msgid ""
+"If using questionnaire IDs, create questionnaires from the IDs read from the "
+"specified file."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:39
+msgid "If using questionnaire IDs, create questionnaires for all stored IDs."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:42
+#, python-format
+msgid "Filename to store the data to (default: stamp_%%i.pdf)"
+msgstr ""
+
+#: ../sdaps/convert/__init__.py:37
+#, python-format
+msgid "Could not apply 3D-transformation to image '%s', page %i!"
+msgstr ""
+
+#: ../sdaps/cover/__init__.py:40
+msgid "sdaps questionnaire"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:57
+msgid ""
+"The survey does not have any images! Please add images (and run recognize) "
+"before using the GUI."
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:202
+msgid "Page|Invalid"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:205
+#, python-format
+msgid "Page %i"
+msgid_plural "Page %i"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/gui/__init__.py:247
+msgid "Copyright © 2007-2014 The SDAPS Authors"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:249
+msgid "Scripts for data acquisition with paper based surveys"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:250
+msgid "http://sdaps.org"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:251
+msgid "translator-credits"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:296
+#, python-format
+msgid " of %i"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:297
+#, python-format
+msgid "Recognition Quality: %.2f"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:313
+msgid ""
+"You have reached the first page of the survey. Would you like to go to the "
+"last page?"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:315
+msgid "Go to last page"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:337
+msgid ""
+"You have reached the last page of the survey. Would you like to go to the "
+"first page?"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:339
+msgid "Go to first page"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:447
+msgid "Close without saving"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:451
+msgid ""
+"<b>Save the project before closing?</b>\n"
+"\n"
+"If you do not save you may loose data."
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:1
+msgid "Forward"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:2
+msgid "Previous"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:3
+msgid "Zoom In"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:4
+msgid "Zoom Out"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:5
+msgid "SDAPS"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:6
+msgid "_File"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:7
+msgid "_View"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:8
+msgid "_Help"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:9
+msgid "Page Rotated"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:10
+msgid "Sort by Quality"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:11
+msgid "label"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:56
+msgid "<b>Global Properties</b>"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:60
+msgid "Sheet valid"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:61
+msgid "Verified"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:62
+msgid "Empty"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:74 ../sdaps/gui/widget_buddies.py:98
+msgid "<b>Questionnaire ID: </b>"
+msgstr ""
+
+#: ../sdaps/image/__init__.py:47
+msgid ""
+"It appears you have not build the C extension. Please run \"./setup.py build"
+"\" in the toplevel directory."
+msgstr ""
+
+#: ../sdaps/utils/opencv.py:31
+msgid "Cannot convert PDF files as poppler is not installed or usable!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:271
+#, python-format
+msgid "%i sheet"
+msgid_plural "%i sheets"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/model/survey.py:282
+#, python-format
+msgid "%f seconds per sheet"
+msgstr ""
+
+#: ../sdaps/model/survey.py:315
+msgid ""
+"A questionnaire that is printed in duplex needs an even amount of pages!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:319
+msgid ""
+"The 'classic' style only supports a maximum of six pages! Use the 'code128' "
+"style if you require more pages."
+msgstr ""
+
+#: ../sdaps/model/survey.py:332
+msgid "IDs need to be integers in \"classic\" style!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:338
+#, python-format
+msgid "Invalid character %s in questionnaire ID \"%s\" in \"code128\" style!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:342
+msgid ""
+"SDAPS cannot draw a questionnaire ID with the \"custom\" style. Do this "
+"yourself somehow!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:362
+#, python-format
+msgid "Running upgrade routines for file format version %i"
+msgstr ""
+
+#. in simplex mode every page will have a matrix; it might be a None
+#. matrix though
+#: ../sdaps/recognize/buddies.py:72
+#, python-format
+msgid "%s, %i: Matrix not recognized."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:81
+#, python-format
+msgid "%s, %i: Rotation not found."
+msgstr ""
+
+#. Copy the rotation over (if required) and print warning if the rotation is unknown
+#: ../sdaps/recognize/buddies.py:85
+#, python-format
+msgid "Neither %s, %i or %s, %i has a known rotation!"
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:97
+#, python-format
+msgid "%s, %i: Matrix not recognized (again)."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:111
+#, python-format
+msgid "%s, %i: Could not get page number."
+msgstr ""
+
+#. Whoa, that should not happen.
+#: ../sdaps/recognize/buddies.py:131
+#, python-format
+msgid "Neither %s, %i or %s, %i has a known page number!"
+msgstr ""
+
+#. We don't touch the ignore flag in this case
+#. Simply print a message as this should *never* happen
+#: ../sdaps/recognize/buddies.py:142
+#, python-format
+msgid ""
+"Got a simplex document where two adjacent pages had a known page number. "
+"This should never happen as even simplex scans are converted to duplex by "
+"inserting dummy pages. Maybe you did a simplex scan but added it in duplex "
+"mode? The pages in question are %s, %i and %s, %i."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:159
+#, python-format
+msgid "Images %s, %i and %s, %i do not have consecutive page numbers!"
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:175
+#, python-format
+msgid "No page number for page %s, %i exists."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:180
+#, python-format
+msgid "Page number for page %s, %i already used by another image."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:186
+#, python-format
+msgid "Page number %i for page %s, %i is out of range."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:203
+#, python-format
+msgid "%s, %i: Could not read survey ID, but should be able to."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:207
+#, python-format
+msgid "Could not read survey ID of either %s, %i or %s, %i!"
+msgstr ""
+
+#. Broken survey ID ...
+#: ../sdaps/recognize/buddies.py:214
+#, python-format
+msgid "Got a wrong survey ID (%s, %i)! It is %s, but should be %i."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:236
+#, python-format
+msgid "%s, %i: Could not read questionnaire ID, but should be able to."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:242
+#, python-format
+msgid "Could not read questionnaire ID of either %s, %i or %s, %i!"
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:272
+msgid ""
+"Got different IDs on different pages for at least one sheet! Do *NOT* try to "
+"use filters with this survey! You have to run a \"reorder\" step for this to "
+"work properly!"
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:328
+msgid "No style buddy loaded. This needs to be done for the \"custom\" style!"
+msgstr ""
+
+#: ../sdaps/report/answers.py:185
+#, python-format
+msgid "Answers: %i"
+msgstr ""
+
+#: ../sdaps/report/answers.py:187
+#, python-format
+msgid "Mean: %.2f"
+msgstr ""
+
+#: ../sdaps/report/answers.py:189
+#, python-format
+msgid "Standard Deviation: %.2f"
+msgstr ""
+
+#: ../sdaps/report/__init__.py:75 ../sdaps/reporttex/__init__.py:140
+msgid "Turned in Questionnaires"
+msgstr ""
+
+#: ../sdaps/report/__init__.py:92 ../sdaps/reporttex/__init__.py:139
+msgid "sdaps report"
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:61
+#, python-format
+msgid "Head %(l0)i got no title."
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:74
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got no question."
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:120
+#, python-format
+msgid "Error in question \"%s\""
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:123
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got no boxes."
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:148
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got not exactly two answers."
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:170
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got not exactly one box."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:45 ../sdaps/setuptex/__init__.py:68
+msgid "The survey directory already exists."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:50
+#, python-format
+msgid ""
+"Unknown file type (%s). questionnaire_odt should be application/vnd.oasis."
+"opendocument.text."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:55
+#, python-format
+msgid "Unknown file type (%s). questionnaire_pdf should be application/pdf."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:61 ../sdaps/setuptex/__init__.py:79
+#, python-format
+msgid "Unknown file type (%s). additionalqobjects should be text/plain."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:90
+msgid "Caught an Exception while parsing the ODT file. The current state is:"
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:93
+msgid ""
+"If the dependencies for the \"annotate\" command are installed, then an "
+"annotated version will be created next to the original PDF file."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:123 ../sdaps/setuptex/__init__.py:147
+msgid ""
+"Some combination of options and project properties do not work. Aborted "
+"Setup."
+msgstr ""
+
+#: ../sdaps/setupodt/boxesparser.py:96 ../sdaps/setupodt/boxesparser.py:168
+#, python-format
+msgid ""
+"Warning: Ignoring a box (page: %i, x: %.1f, y: %.1f, width: %.1f, height: "
+"%.1f)."
+msgstr ""
+
+#: ../sdaps/stamp/__init__.py:38
+msgid ""
+"You may not specify the number of sheets for this survey. All questionnaires "
+"will be identical as the survey has been configured to not use questionnaire "
+"IDs for each sheet."
+msgstr ""
+
+#: ../sdaps/stamp/__init__.py:76
+msgid ""
+"This survey has been configured to use questionnaire IDs. Each questionnaire "
+"will be unique. You need to use on of the options to add new IDs or use the "
+"existing ones."
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:37
+#, python-format
+msgid "Survey-ID: %i"
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:51
+#, python-format
+msgid "Questionnaire-ID: %i"
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:336
+msgid ""
+"You need to have either pdftk or pyPdf installed. pdftk is the faster method."
+msgstr ""
+
+#. bottomup = False =>(0, 0) is the upper left corner
+#: ../sdaps/stamp/generic.py:352
+#, python-format
+msgid "Creating stamp PDF for %i sheet"
+msgid_plural "Creating stamp PDF for %i sheets"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/stamp/generic.py:410 ../sdaps/stamp/generic.py:543
+#, python-format
+msgid "%i sheet; %f seconds per sheet"
+msgid_plural "%i sheet; %f seconds per sheet"
+msgstr[0] ""
+msgstr[1] ""
+
+#. Merge using pdftk
+#: ../sdaps/stamp/generic.py:419
+msgid "Stamping using pdftk"
+msgstr ""
+
+#. Shortcut if we only have one sheet.
+#. In this case form data in the PDF will *not* break, in
+#. the other code path it *will* break.
+#: ../sdaps/stamp/generic.py:426
+msgid "pdftk: Overlaying the original PDF with the markings."
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:435
+#, python-format
+msgid "pdftk: Splitting out page %d of each sheet."
+msgid_plural "pdftk: Splitting out page %d of each sheet."
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/stamp/generic.py:449
+msgid "pdftk: Splitting the questionnaire for watermarking."
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:460 ../sdaps/stamp/generic.py:469
+#, python-format
+msgid "pdftk: Watermarking page %d of all sheets."
+msgid_plural "pdftk: Watermarking page %d of all sheets."
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/stamp/generic.py:492
+msgid "pdftk: Assembling everything into the final PDF."
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:529
+msgid "Stamping using pyPdf. For faster stamping, install pdftk."
+msgstr ""
+
+#: ../sdaps/stamp/latex.py:21
+msgid ""
+"There should be no need to stamp a SDAPS Project that uses LaTeX and does "
+"not have different questionnaire IDs printed on each sheet.\n"
+"I am going to do so anyways."
+msgstr ""
+
+#: ../sdaps/stamp/latex.py:43
+#, python-format
+msgid "Running %s now twice to generate the stamped questionnaire."
+msgstr ""
+
+#: ../sdaps/stamp/latex.py:47 ../sdaps/setuptex/__init__.py:119
+#: ../sdaps/setuptex/__init__.py:158 ../sdaps/reporttex/__init__.py:161
+#, python-format
+msgid "Error running \"%s\" to compile the LaTeX file."
+msgstr ""
+
+#: ../sdaps/stamp/latex.py:53
+#, python-format
+msgid ""
+"An error occured during creation of the report. Temporary files left in '%s'."
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:73
+#, python-format
+msgid "Unknown file type (%s). questionnaire_tex should be of type text/x-tex."
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:74
+msgid "Will keep going, but expect failure!"
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:115 ../sdaps/setuptex/__init__.py:153
+#, python-format
+msgid "Running %s now twice to generate the questionnaire."
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:133
+msgid "Caught an Exception while parsing the SDAPS file. The current state is:"
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:174
+msgid ""
+"An error occured in the setup routine. The survey directory still exists. "
+"You can for example check the questionnaire.log file for LaTeX compile "
+"errors."
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:107
+msgid "author|Unknown"
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:138
+msgid "tex language|english"
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:154
+#, python-format
+msgid "The TeX project with the report data is located at '%s'."
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:157
+#, python-format
+msgid "Running %s now twice to generate the report."
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:167
+#, python-format
+msgid "An occured during creation of the report. Temporary files left in '%s'."
+msgstr ""
diff --git a/po/sv.po b/po/sv.po
new file mode 100644
index 0000000..b81bc9c
--- /dev/null
+++ b/po/sv.po
@@ -0,0 +1,1089 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-03-13 19:45+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"Language: sv\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: ../sdaps/script.py:41
+msgid "SDAPS -- Paper based survey tool."
+msgstr ""
+
+#: ../sdaps/script.py:45
+msgid "project directory|The SDAPS project."
+msgstr ""
+
+#: ../sdaps/script.py:46
+msgid "command list|Commands:"
+msgstr ""
+
+#: ../sdaps/add/__init__.py:55
+#, python-format
+msgid ""
+"Invalid input file %s. You need to specify a (multipage) monochrome TIFF as "
+"input."
+msgstr ""
+
+#: ../sdaps/add/__init__.py:67
+#, python-format
+msgid ""
+"Not adding %s because it has a wrong page count (needs to be a mulitple of "
+"%i)."
+msgstr ""
+
+#: ../sdaps/boxgallery/__init__.py:108
+#, python-format
+msgid "Rendering boxgallery for metric \"%s\"."
+msgstr ""
+
+#: ../sdaps/log.py:37
+msgid "Warning: "
+msgstr ""
+
+#: ../sdaps/log.py:41
+msgid "Error: "
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:31
+msgid "Add scanned questionnaires to the survey."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:32
+msgid ""
+"This command is used to add scanned images to the survey.\n"
+"    The image data needs to be a (multipage) 300dpi monochrome TIFF file. "
+"You\n"
+"    may choose not to copy the data into the project directory. In that "
+"case\n"
+"    the data will be referenced using a relative path."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:38
+msgid "Convert given files and add the result."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:43 ../sdaps/cmdline/convert.py:39
+msgid ""
+"Do a 3D-transformation after finding the corner marks. If the\n"
+"        corner marks are not found then the image will be added as-is."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:49
+msgid ""
+"Force adding the images even if the page count is wrong (only use if you "
+"know what you are doing)."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:53
+msgid "Copy the files into the directory (default)."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:58
+msgid "Do not copy the files into the directory."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:62
+msgid ""
+"Images contain a duplex scan of a simplex questionnaire (default: simplex "
+"scan)."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:68 ../sdaps/cmdline/convert.py:49
+msgid "A number of TIFF image files."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:94
+msgid "The --no-copy option is not compatible with --convert!"
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:103
+msgid "Converting input files into a single temporary file."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:115
+msgid ""
+"The page count of the created temporary file does not work with this survey."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:120
+msgid "Running the conversion failed."
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:125
+#, python-format
+msgid "Processing %s"
+msgstr ""
+
+#: ../sdaps/cmdline/add.py:129
+msgid "Done"
+msgstr ""
+
+#: ../sdaps/cmdline/annotate.py:28
+msgid "Annotate the questionnaire and show the recognized positions."
+msgstr ""
+
+#: ../sdaps/cmdline/annotate.py:29
+msgid ""
+"This command is mainly a debug utility. It creates an\n"
+"    annotated version of the questionnaire, with the information that SDAPS\n"
+"    knows about it overlayed on top."
+msgstr ""
+
+#: ../sdaps/cmdline/boxgallery.py:31
+msgid "Create PDFs with boxes sorted by the detection heuristics."
+msgstr ""
+
+#: ../sdaps/cmdline/boxgallery.py:32
+msgid ""
+"SDAPS uses multiple heuristics to detect determine the\n"
+"    state of checkboxes. There is a list for each heuristic giving the "
+"expected\n"
+"    state and the quality of the value (see defs.py). Using this command a "
+"PDF\n"
+"    will be created for each of the heuristics so that one can adjust the\n"
+"    values."
+msgstr ""
+
+#: ../sdaps/cmdline/boxgallery.py:40
+msgid ""
+"Reruns part of the recognition process and retrieves debug images from this "
+"step."
+msgstr ""
+
+#: ../sdaps/cmdline/convert.py:30
+msgid "Convert a set of images to the correct image format."
+msgstr ""
+
+#: ../sdaps/cmdline/convert.py:31
+msgid ""
+"This command can be used if you scanned files in a different\n"
+"        mode than the expected monochrome TIFF file. All the given files "
+"will\n"
+"        be loaded, converted to monochrome and stored into a multipage 1bpp\n"
+"        TIFF file. Optionally you can select that a 3D transformation "
+"should\n"
+"        be performed, using this it may be possible to work with photos of\n"
+"        questionnaires instead of scans."
+msgstr ""
+
+#: ../sdaps/cmdline/convert.py:45
+msgid "The location of the output file. Must be given."
+msgstr ""
+
+#: ../sdaps/cmdline/convert.py:58
+msgid "No output filename specified!"
+msgstr ""
+
+#: ../sdaps/cmdline/cover.py:27
+msgid "Create a cover for the questionnaires."
+msgstr ""
+
+#: ../sdaps/cmdline/cover.py:28
+msgid ""
+"This command creates a cover page for questionnaires. All\n"
+"    the metadata of the survey will be printed on the page."
+msgstr ""
+
+#: ../sdaps/cmdline/cover.py:31
+#, python-format
+msgid "Filename to store the data to (default: cover_%%i.pdf)"
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:34
+msgid "Import or export data to/from CSV files."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:35
+msgid ""
+"Import or export data to/from a CSV file. The first line\n"
+"    is a header which defines questionnaire_id and global_id, and a column\n"
+"    for each checkbox and textfield. Note that the import is currently very\n"
+"    limited, as you need to specifiy the questionnaire ID to select the "
+"sheet\n"
+"    which should be updated."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:44
+msgid "Export data to CSV file."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:46
+#, python-format
+msgid "Filename to store the data to (default: data_%%i.csv)"
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:48
+msgid "The delimiter used in the CSV file (default ',')"
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:52 ../sdaps/cmdline/report.py:31
+#: ../sdaps/cmdline/reporttex.py:52
+msgid "Filter to only export a partial dataset."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:54
+msgid "Export images of freeform fields."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:60
+msgid "Export an image for each question that includes all boxes."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:66
+msgid "Export the recognition quality for each checkbox."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:74
+msgid "Import data to from a CSV file."
+msgstr ""
+
+#: ../sdaps/cmdline/csvdata.py:76
+msgid "The file to import."
+msgstr ""
+
+#: ../sdaps/cmdline/gui.py:28
+msgid "Launch a gui. You can view and alter the (recognized) answers with it."
+msgstr ""
+
+#: ../sdaps/cmdline/gui.py:29
+msgid ""
+"This command launches a graphical user interface that can\n"
+"    be used to correct answer. You need to run \"recognize\" before using "
+"it.\n"
+"    "
+msgstr ""
+
+#: ../sdaps/cmdline/gui.py:34
+msgid "Filter to only show a partial dataset."
+msgstr ""
+
+#: ../sdaps/cmdline/ids.py:29
+msgid "Export and import questionnaire IDs."
+msgstr ""
+
+#: ../sdaps/cmdline/ids.py:30
+msgid ""
+"This command can be used to import and export questionnaire\n"
+"    IDs. It only makes sense in projects where such an ID is printed on the\n"
+"    questionnaire. Note that you can also add IDs by using the stamp "
+"command,\n"
+"    which will give you the PDF at the same time."
+msgstr ""
+
+#: ../sdaps/cmdline/ids.py:35
+#, python-format
+msgid "Filename to store the data to (default: ids_%%i)"
+msgstr ""
+
+#: ../sdaps/cmdline/ids.py:38
+msgid "Add IDs to the internal list from the specified file."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:28
+msgid "Display and modify metadata of project."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:29
+msgid ""
+"This command lets you modify the metadata of the SDAPS\n"
+"    project. You can modify, add and remove arbitrary keys that will be "
+"printed\n"
+"    on the report. The only key that always exist is \"title\".\n"
+"    If no key is given then a list of defined keys is printed."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:36
+msgid "The key to display or modify."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:40
+msgid "Set the given key to this value."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:44
+msgid "Delete the key and value pair."
+msgstr ""
+
+#: ../sdaps/cmdline/info.py:68
+msgid "Existing fields:\n"
+msgstr ""
+
+#: ../sdaps/cmdline/recognize.py:28
+msgid "Run the optical mark recognition."
+msgstr ""
+
+#: ../sdaps/cmdline/recognize.py:29
+msgid ""
+"Iterates over all images and runs the optical mark\n"
+"    recognition. It will reevaluate sheets even if \"recognize\" has "
+"already\n"
+"    run or manual changes were made."
+msgstr ""
+
+#: ../sdaps/cmdline/recognize.py:34
+msgid ""
+"Only identify the page properties, but don't recognize the checkbox states."
+msgstr ""
+
+#: ../sdaps/cmdline/recognize.py:39
+msgid ""
+"Rerun the recognition for all pages. The default is to skip all pages that "
+"were recognized or verified already."
+msgstr ""
+
+#: ../sdaps/cmdline/reorder.py:26
+msgid "Reorder pages according to questionnaire ID."
+msgstr ""
+
+#: ../sdaps/cmdline/reorder.py:27
+msgid ""
+"This command reorders all pages according to the already\n"
+"    recognized questionnaire ID. To use it add all the files to the "
+"project,\n"
+"    then run a partial recognition using \"recognize --identify\". After "
+"this\n"
+"    you have to run this command to reorder the data for the real "
+"recognition.\n"
+"    "
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:26
+msgid "Create a PDF report."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:27
+msgid ""
+"This command creates a PDF report using reportlab that\n"
+"    contains statistics and if selected the freeform fields."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:33
+msgid "Create a filtered report for every checkbox."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:36
+msgid "Short format (without freeform text fields)."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:41
+msgid "Detailed output. (default)"
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:47 ../sdaps/cmdline/reporttex.py:30
+msgid ""
+"Do not include original images in the report. This is useful if there are "
+"privacy concerns."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:52 ../sdaps/cmdline/reporttex.py:35
+msgid "Do not use substitutions instead of images."
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:58 ../sdaps/cmdline/reporttex.py:46
+msgid "The paper size used for the output (default: locale dependent)"
+msgstr ""
+
+#: ../sdaps/cmdline/report.py:61 ../sdaps/cmdline/reporttex.py:49
+#, python-format
+msgid "Filename to store the data to (default: report_%%i.pdf)"
+msgstr ""
+
+#: ../sdaps/cmdline/reporttex.py:26
+msgid "Create a PDF report using LaTeX."
+msgstr ""
+
+#: ../sdaps/cmdline/reporttex.py:27
+msgid ""
+"This command creates a PDF report using LaTeX that\n"
+"    contains statistics and freeform fields."
+msgstr ""
+
+#: ../sdaps/cmdline/reporttex.py:41
+msgid "Save the generated TeX files instead of the final PDF."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:28
+msgid "Create a new survey using an ODT document."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:29
+msgid ""
+"Create a new surevey from an ODT document. The PDF export\n"
+"    of the file needs to be specified at the same time. SDAPS will import\n"
+"    metadata (properties) and the title from the ODT document."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:34
+msgid "The ODT Document"
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:36
+msgid "PDF export of the ODT document"
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:39 ../sdaps/cmdline/setuptex.py:39
+msgid "Additional questions that are not part of the questionnaire."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:48
+msgid "Enable printing of the survey ID (default)."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:52
+msgid "Disable printing of the survey ID."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:57
+msgid "Enable printing of the questionnaire ID."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:61
+msgid "Disable printing of the questionnaire ID (default)."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:65
+msgid ""
+"Set an additional global ID for tracking. This can can only be used in the "
+"\"code128\" style."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:70
+msgid ""
+"The stamping style to use. Should be either \"classic\" or \"code128\". Use "
+"\"code128\" for more features."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:76
+msgid "The mode to use when detecting checkboxes."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:82
+msgid ""
+"Use duplex mode (ie. only print the IDs on the back side). Requires a "
+"mulitple of two pages."
+msgstr ""
+
+#: ../sdaps/cmdline/setup.py:87
+msgid ""
+"Use simplex mode. IDs are printed on each page. You need a simplex scan "
+"currently!"
+msgstr ""
+
+#: ../sdaps/cmdline/setuptex.py:27
+msgid "Create a new survey using a LaTeX document."
+msgstr ""
+
+#: ../sdaps/cmdline/setuptex.py:28
+msgid ""
+"Create a new survey from a LaTeX document. You need to\n"
+"    be using the SDAPS class. All the metadata and options for the project\n"
+"    can be set inside the LaTeX document."
+msgstr ""
+
+#: ../sdaps/cmdline/setuptex.py:33
+msgid "The LaTeX Document"
+msgstr ""
+
+#: ../sdaps/cmdline/setuptex.py:35
+msgid ""
+"Additional files that are required by the LaTeX document and need to be "
+"copied into the project directory."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:26
+msgid "Add marks for automatic processing."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:27
+msgid ""
+"This command creates the printable document. Depending on\n"
+"    the projects setting you are required to specifiy a source for "
+"questionnaire\n"
+"    IDs."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:33
+msgid ""
+"If using questionnaire IDs, create N questionnaires with randomized IDs."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:36
+msgid ""
+"If using questionnaire IDs, create questionnaires from the IDs read from the "
+"specified file."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:39
+msgid "If using questionnaire IDs, create questionnaires for all stored IDs."
+msgstr ""
+
+#: ../sdaps/cmdline/stamp.py:42
+#, python-format
+msgid "Filename to store the data to (default: stamp_%%i.pdf)"
+msgstr ""
+
+#: ../sdaps/convert/__init__.py:35
+#, python-format
+msgid "Could not apply 3D-transformation to image '%s', page %i!"
+msgstr ""
+
+#: ../sdaps/cover/__init__.py:40
+msgid "sdaps questionnaire"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:57
+msgid ""
+"The survey does not have any images! Please add images (and run recognize) "
+"before using the GUI."
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:202
+msgid "Page|Invalid"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:205
+#, python-format
+msgid "Page %i"
+msgid_plural "Page %i"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/gui/__init__.py:247
+msgid "Copyright © 2007-2014 The SDAPS Authors"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:249
+msgid "Scripts for data acquisition with paper based surveys"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:250
+msgid "http://sdaps.org"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:251
+msgid "translator-credits"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:296
+#, python-format
+msgid " of %i"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:297
+#, python-format
+msgid "Recognition Quality: %.2f"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:313
+msgid ""
+"You have reached the first page of the survey. Would you like to go to the "
+"last page?"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:315
+msgid "Go to last page"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:337
+msgid ""
+"You have reached the last page of the survey. Would you like to go to the "
+"first page?"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:339
+msgid "Go to first page"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:447
+msgid "Close without saving"
+msgstr ""
+
+#: ../sdaps/gui/__init__.py:451
+msgid ""
+"<b>Save the project before closing?</b>\n"
+"\n"
+"If you do not save you may loose data."
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:1
+msgid "Forward"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:2
+msgid "Previous"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:3
+msgid "Zoom In"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:4
+msgid "Zoom Out"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:5
+msgid "SDAPS"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:6
+msgid "_File"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:7
+msgid "_View"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:8
+msgid "_Help"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:9
+msgid "Page Rotated"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:10
+msgid "Sort by Quality"
+msgstr ""
+
+#: ../sdaps/gui/main_window.ui.h:11
+msgid "label"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:56
+msgid "<b>Global Properties</b>"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:60
+msgid "Sheet valid"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:61
+msgid "Verified"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:62
+msgid "Empty"
+msgstr ""
+
+#: ../sdaps/gui/widget_buddies.py:74 ../sdaps/gui/widget_buddies.py:98
+msgid "<b>Questionnaire ID: </b>"
+msgstr ""
+
+#: ../sdaps/image/__init__.py:47
+msgid ""
+"It appears you have not build the C extension. Please run \"./setup.py build"
+"\" in the toplevel directory."
+msgstr ""
+
+#: ../sdaps/utils/opencv.py:31
+msgid "Cannot convert PDF files as poppler is not installed or usable!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:253
+#, python-format
+msgid "%i sheet"
+msgid_plural "%i sheets"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/model/survey.py:264
+#, python-format
+msgid "%f seconds per sheet"
+msgstr ""
+
+#: ../sdaps/model/survey.py:290
+msgid ""
+"A questionnaire that is printed in duplex needs an even amount of pages!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:294
+msgid ""
+"The 'classic' style only supports a maximum of six pages! Use the 'code128' "
+"style if you require more pages."
+msgstr ""
+
+#: ../sdaps/model/survey.py:307
+msgid "IDs need to be integers in \"classic\" style!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:313
+#, python-format
+msgid "Invalid character %s in questionnaire ID \"%s\" in \"code128\" style!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:317
+msgid ""
+"SDAPS cannot draw a questionnaire ID with the \"custom\" style. Do this "
+"yourself somehow!"
+msgstr ""
+
+#: ../sdaps/model/survey.py:337
+#, python-format
+msgid "Running upgrade routines for file format version %i"
+msgstr ""
+
+#. in simplex mode every page will have a matrix; it might be a None
+#. matrix though
+#: ../sdaps/recognize/buddies.py:72
+#, python-format
+msgid "%s, %i: Matrix not recognized."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:81
+#, python-format
+msgid "%s, %i: Rotation not found."
+msgstr ""
+
+#. Copy the rotation over (if required) and print warning if the rotation is unknown
+#: ../sdaps/recognize/buddies.py:85
+#, python-format
+msgid "Neither %s, %i or %s, %i has a known rotation!"
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:97
+#, python-format
+msgid "%s, %i: Matrix not recognized (again)."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:111
+#, python-format
+msgid "%s, %i: Could not get page number."
+msgstr ""
+
+#. Whoa, that should not happen.
+#: ../sdaps/recognize/buddies.py:131
+#, python-format
+msgid "Neither %s, %i or %s, %i has a known page number!"
+msgstr ""
+
+#. We don't touch the ignore flag in this case
+#. Simply print a message as this should *never* happen
+#: ../sdaps/recognize/buddies.py:142
+#, python-format
+msgid ""
+"Got a simplex document where two adjacent pages had a known page number. "
+"This should never happen as even simplex scans are converted to duplex by "
+"inserting dummy pages. Maybe you did a simplex scan but added it in duplex "
+"mode? The pages in question are %s, %i and %s, %i."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:159
+#, python-format
+msgid "Images %s, %i and %s, %i do not have consecutive page numbers!"
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:175
+#, python-format
+msgid "No page number for page %s, %i exists."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:180
+#, python-format
+msgid "Page number for page %s, %i already used by another image."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:186
+#, python-format
+msgid "Page number %i for page %s, %i is out of range."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:203
+#, python-format
+msgid "%s, %i: Could not read survey ID, but should be able to."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:207
+#, python-format
+msgid "Could not read survey ID of either %s, %i or %s, %i!"
+msgstr ""
+
+#. Broken survey ID ...
+#: ../sdaps/recognize/buddies.py:214
+#, python-format
+msgid "Got a wrong survey ID (%s, %i)! It is %s, but should be %i."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:236
+#, python-format
+msgid "%s, %i: Could not read questionnaire ID, but should be able to."
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:240
+#, python-format
+msgid "Could not read questionnaire ID of either %s, %i or %s, %i!"
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:267
+msgid ""
+"Got different IDs on different pages for at least one sheet! Do *NOT* try to "
+"use filters with this survey! You have to run a \"reorder\" step for this to "
+"work properly!"
+msgstr ""
+
+#: ../sdaps/recognize/buddies.py:323
+msgid "No style buddy loaded. This needs to be done for the \"custom\" style!"
+msgstr ""
+
+#: ../sdaps/report/answers.py:185
+#, python-format
+msgid "Answers: %i"
+msgstr ""
+
+#: ../sdaps/report/answers.py:187
+#, python-format
+msgid "Mean: %.2f"
+msgstr ""
+
+#: ../sdaps/report/answers.py:189
+#, python-format
+msgid "Standard Deviation: %.2f"
+msgstr ""
+
+#: ../sdaps/report/__init__.py:75 ../sdaps/reporttex/__init__.py:140
+msgid "Turned in Questionnaires"
+msgstr ""
+
+#: ../sdaps/report/__init__.py:92 ../sdaps/reporttex/__init__.py:139
+msgid "sdaps report"
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:61
+#, python-format
+msgid "Head %(l0)i got no title."
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:74
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got no question."
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:120
+#, python-format
+msgid "Error in question \"%s\""
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:123
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got no boxes."
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:148
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got not exactly two answers."
+msgstr ""
+
+#: ../sdaps/setup/buddies.py:170
+#, python-format
+msgid "%(class)s %(l0)i.%(l1)i got not exactly one box."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:45 ../sdaps/setuptex/__init__.py:68
+msgid "The survey directory already exists."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:50
+#, python-format
+msgid ""
+"Unknown file type (%s). questionnaire_odt should be application/vnd.oasis."
+"opendocument.text."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:55
+#, python-format
+msgid "Unknown file type (%s). questionnaire_pdf should be application/pdf."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:61 ../sdaps/setuptex/__init__.py:79
+#, python-format
+msgid "Unknown file type (%s). additionalqobjects should be text/plain."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:90
+msgid "Caught an Exception while parsing the ODT file. The current state is:"
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:93
+msgid ""
+"If the dependencies for the \"annotate\" command are installed, then an "
+"annotated version will be created next to the original PDF file."
+msgstr ""
+
+#: ../sdaps/setupodt/__init__.py:123 ../sdaps/setuptex/__init__.py:147
+msgid ""
+"Some combination of options and project properties do not work. Aborted "
+"Setup."
+msgstr ""
+
+#: ../sdaps/setupodt/boxesparser.py:121 ../sdaps/setupodt/boxesparser.py:190
+#, python-format
+msgid ""
+"Warning: Ignoring a box (page: %i, x: %.1f, y: %.1f, width: %.1f, height: "
+"%.1f)."
+msgstr ""
+
+#: ../sdaps/stamp/__init__.py:38
+msgid ""
+"You may not specify the number of sheets for this survey. All questionnaires "
+"will be identical as the survey has been configured to not use questionnaire "
+"IDs for each sheet."
+msgstr ""
+
+#: ../sdaps/stamp/__init__.py:76
+msgid ""
+"This survey has been configured to use questionnaire IDs. Each questionnaire "
+"will be unique. You need to use on of the options to add new IDs or use the "
+"existing ones."
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:37
+#, python-format
+msgid "Survey-ID: %i"
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:51
+#, python-format
+msgid "Questionnaire-ID: %i"
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:336
+msgid ""
+"You need to have either pdftk or pyPdf installed. pdftk is the faster method."
+msgstr ""
+
+#. bottomup = False =>(0, 0) is the upper left corner
+#: ../sdaps/stamp/generic.py:352
+#, python-format
+msgid "Creating stamp PDF for %i sheet"
+msgid_plural "Creating stamp PDF for %i sheets"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/stamp/generic.py:410 ../sdaps/stamp/generic.py:543
+#, python-format
+msgid "%i sheet; %f seconds per sheet"
+msgid_plural "%i sheet; %f seconds per sheet"
+msgstr[0] ""
+msgstr[1] ""
+
+#. Merge using pdftk
+#: ../sdaps/stamp/generic.py:419
+msgid "Stamping using pdftk"
+msgstr ""
+
+#. Shortcut if we only have one sheet.
+#. In this case form data in the PDF will *not* break, in
+#. the other code path it *will* break.
+#: ../sdaps/stamp/generic.py:426
+msgid "pdftk: Overlaying the original PDF with the markings."
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:435
+#, python-format
+msgid "pdftk: Splitting out page %d of each sheet."
+msgid_plural "pdftk: Splitting out page %d of each sheet."
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/stamp/generic.py:449
+msgid "pdftk: Splitting the questionnaire for watermarking."
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:460 ../sdaps/stamp/generic.py:469
+#, python-format
+msgid "pdftk: Watermarking page %d of all sheets."
+msgid_plural "pdftk: Watermarking page %d of all sheets."
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../sdaps/stamp/generic.py:492
+msgid "pdftk: Assembling everything into the final PDF."
+msgstr ""
+
+#: ../sdaps/stamp/generic.py:529
+msgid "Stamping using pyPdf. For faster stamping, install pdftk."
+msgstr ""
+
+#: ../sdaps/stamp/latex.py:21
+msgid ""
+"There should be no need to stamp a SDAPS Project that uses LaTeX and does "
+"not have different questionnaire IDs printed on each sheet.\n"
+"I am going to do so anyways."
+msgstr ""
+
+#: ../sdaps/stamp/latex.py:43
+#, python-format
+msgid "Running %s now twice to generate the stamped questionnaire."
+msgstr ""
+
+#: ../sdaps/stamp/latex.py:47 ../sdaps/setuptex/__init__.py:119
+#: ../sdaps/setuptex/__init__.py:158 ../sdaps/reporttex/__init__.py:161
+#, python-format
+msgid "Error running \"%s\" to compile the LaTeX file."
+msgstr ""
+
+#: ../sdaps/stamp/latex.py:53
+#, python-format
+msgid ""
+"An error occured during creation of the report. Temporary files left in '%s'."
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:73
+#, python-format
+msgid "Unknown file type (%s). questionnaire_tex should be of type text/x-tex."
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:74
+msgid "Will keep going, but expect failure!"
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:115 ../sdaps/setuptex/__init__.py:153
+#, python-format
+msgid "Running %s now twice to generate the questionnaire."
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:133
+msgid "Caught an Exception while parsing the SDAPS file. The current state is:"
+msgstr ""
+
+#: ../sdaps/setuptex/__init__.py:174
+msgid ""
+"An error occured in the setup routine. The survey directory still exists. "
+"You can for example check the questionnaire.log file for LaTeX compile "
+"errors."
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:107
+msgid "author|Unknown"
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:138
+msgid "tex language|english"
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:154
+#, python-format
+msgid "The TeX project with the report data is located at '%s'."
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:157
+#, python-format
+msgid "Running %s now twice to generate the report."
+msgstr ""
+
+#: ../sdaps/reporttex/__init__.py:167
+#, python-format
+msgid "An occured during creation of the report. Temporary files left in '%s'."
+msgstr ""
diff --git a/sdaps.py b/sdaps.py
new file mode 100755
index 0000000..46c5cbc
--- /dev/null
+++ b/sdaps.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright (C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright (C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import sys
+
+import sdaps
+sys.exit(sdaps.main(local_run = True))
diff --git a/sdaps/__init__.py b/sdaps/__init__.py
new file mode 100644
index 0000000..067bbc0
--- /dev/null
+++ b/sdaps/__init__.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+u"""
+sdaps has a modular design. It ships a core python module called "model" which
+is responsible for storing and basic modification of the data. When other
+modules like "recognize" are loaded they '''extend''' the original model.
+
+As an example the "recognize" module contains everything required to analyize
+the scanned data and find checkmarks. However, it will in addition load the
+"image", "matrix" and "surface" modules. These three modules are responsible
+for loading and caching the image data and doing some pre-processing of the
+image data(transformation matrix calculation).
+
+Please have a look at the documentation of the "model" package.
+"""
+
+import sys
+
+import paths
+import script
+import os
+import argparse
+
+from utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+
+def init(local_run=False):
+    paths.init(local_run, __path__[0])
+
+def main(local_run=False):
+    u"""The main SDAPS interface routine. It initilizes all modules, parses
+    the command line and passes control over to the selected function."""
+    init(local_run)
+
+    import log
+    log.activate_redirects()
+
+    import cmdline
+
+    cmdline = script.parser.parse_args()
+    cmdline = vars(cmdline)
+
+    log.interactive('-'*78 + '\n')
+    log.interactive(('- SDAPS -- %s' % cmdline['_name']) + '\n')
+    log.interactive('-'*78 + '\n')
+
+    return cmdline['_func'](cmdline)
+
+
+
+# Guess whether documentation is generated, if it is
+# setup for local run.
+if 'sphinx' in sys.argv[0]:
+    paths.init(True, os.path.join(sys.path[0], 'sdaps'))
+
diff --git a/sdaps/add/__init__.py b/sdaps/add/__init__.py
new file mode 100644
index 0000000..3248fa5
--- /dev/null
+++ b/sdaps/add/__init__.py
@@ -0,0 +1,117 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import os
+
+from sdaps import model
+from sdaps import image
+import shutil
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+def _insert_dummy_pages(survey, duplex_scan):
+    # Insert dummy pages if the survey is duplex and the duplex option was not
+    # passed
+    if survey.defs.duplex:
+        # One image per questionnaire page in duplex mode
+        image_count_factor = 1
+        # No dummy pages in duplex mode
+        insert_dummy_pages = False
+    else:
+        # Two images per questionnaire page in duplex mode
+        image_count_factor = 2
+
+        # In simplex mode insertion of dummy pages depends on the command line
+        # optoin (default is True)
+        if duplex_scan:
+            insert_dummy_pages = False
+        else:
+            insert_dummy_pages = True
+
+    return insert_dummy_pages, image_count_factor
+
+def check_image(survey, file, duplex_scan=False, force=False, message=False):
+
+    insert_dummy_pages, image_count_factor = _insert_dummy_pages(survey, duplex_scan)
+
+    if not image.check_tiff_monochrome(file):
+        if message:
+            print _('Invalid input file %s. You need to specify a (multipage) monochrome TIFF as input.') % (file,)
+        return False
+
+    num_pages = image.get_tiff_page_count(file)
+
+    c = survey.questionnaire.page_count
+    if not insert_dummy_pages:
+        c = c * image_count_factor
+
+    # This test is on the image count that needs to come from the file
+    if num_pages % c != 0 and not force:
+        if message:
+            print _('Not adding %s because it has a wrong page count (needs to be a mulitple of %i).') % (file, c)
+        return False
+
+    return True
+
+def add_image(survey, file, duplex_scan=False, force=False, copy=True):
+
+    insert_dummy_pages, image_count_factor = _insert_dummy_pages(survey, duplex_scan)
+
+    if not check_image(survey, file, duplex_scan, force, message=True):
+        return
+
+    num_pages = image.get_tiff_page_count(file)
+
+    c = survey.questionnaire.page_count
+    if not insert_dummy_pages:
+        c = c * image_count_factor
+
+    if insert_dummy_pages:
+        c = c * image_count_factor
+
+    if copy:
+        tiff = survey.new_path('%i.tif')
+        shutil.copyfile(file, tiff)
+    else:
+        tiff = file
+
+    if copy:
+        tiff = os.path.basename(tiff)
+    else:
+        tiff = os.path.relpath(os.path.abspath(tiff), survey.survey_dir)
+
+    pages = range(num_pages)
+    while len(pages) > 0:
+        sheet = model.sheet.Sheet()
+        survey.add_sheet(sheet)
+        while len(pages) > 0 and len(sheet.images) < c:
+            img = model.sheet.Image()
+            sheet.add_image(img)
+            img.filename = tiff
+            img.tiff_page = pages.pop(0)
+
+            # And a dummy page if required
+            if insert_dummy_pages:
+                img = model.sheet.Image()
+                sheet.add_image(img)
+
+                img.filename = "DUMMY"
+                img.tiff_page = -1
+                img.ignored = True
+
diff --git a/sdaps/annotate/__init__.py b/sdaps/annotate/__init__.py
new file mode 100644
index 0000000..6a21f7f
--- /dev/null
+++ b/sdaps/annotate/__init__.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2012, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+from gi.repository import Poppler
+import cairo
+import buddies
+import os.path
+
+def annotate(survey, infile=None, outfile=None):
+    if infile is None:
+        infile = 'file://' + os.path.abspath(survey.path('questionnaire.pdf'))
+    else:
+        infile = 'file://' + os.path.abspath(infile)
+
+    if outfile is None:
+        outfile = survey.path('annotated_questionnaire.pdf')
+
+    pdf = Poppler.Document.new_from_file(infile, None)
+
+    width, height = pdf.get_page(0).get_size()
+
+    output = cairo.PDFSurface(outfile, 2*width, 2*height)
+
+    cr = cairo.Context(output)
+
+    for p in xrange(survey.questionnaire.page_count):
+        pdf.get_page(p).render_for_printing(cr)
+
+        cr.save()
+        # Use mm space in here.
+        cr.scale(72.0 / 25.4, 72.0 / 25.4)
+
+        layout_info = dict()
+        layout_info['twidth'] = width / 72.0 * 25.4 - 20 # mm
+        layout_info['xshift'] = width / 72.0 * 25.4 + 10 # mm
+        layout_info['ypos'] = 15 # mm
+        layout_info['font'] = "Sans 3"
+        layout_info['boxfont'] = "Sans 2"
+
+        # And, render the questions on top (1 based page numbers here)
+        survey.questionnaire.annotate.draw(cr, p + 1, layout_info)
+
+        cr.restore()
+
+        cr.show_page()
+
+
diff --git a/sdaps/annotate/buddies.py b/sdaps/annotate/buddies.py
new file mode 100644
index 0000000..43afedb
--- /dev/null
+++ b/sdaps/annotate/buddies.py
@@ -0,0 +1,259 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, 2013, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+# This file duplicates some drawing code from the gui module.
+
+from gi.repository import Pango
+from gi.repository import PangoCairo
+import cairo
+import math
+
+from sdaps import model
+from sdaps import defs
+
+colorcycle_pos = 0
+colorcycle = [(1, 0, 0), (0.5, 0.5, 0), (0, 0.5, 0.5), (0.5, 0, 0.5)]
+
+LINE_WIDTH = 25.4/72
+MIN_FREETEXT_SIZE = 4.0
+
+def cycle_color():
+    global colorcycle_pos
+    colorcycle_pos = (colorcycle_pos + 1) % len(colorcycle)
+
+def set_rgb_from_color_cycle(cr):
+    global colorcycle_pos, colorcycle
+    cr.set_source_rgba(*(colorcycle[colorcycle_pos] + (0.5,)))
+
+def inner_box(cr, x, y, width, height):
+    line_width = cr.get_line_width()
+
+    cr.rectangle(x + line_width / 2.0, y + line_width / 2.0,
+                 width - line_width, height - line_width)
+
+def inner_ellipse(cr, x, y, width, height):
+    cr.save()
+
+    cr.translate(x + width / 2.0, y + height / 2.0)
+
+    line_width = cr.get_line_width()
+
+    cr.scale((width - line_width) / 2.0, (height - line_width) / 2.0)
+    cr.arc(0, 0, 1.0, 0, 2*math.pi)
+
+    # Restore old matrix (without removing the current path)
+    cr.restore()
+
+def create_layout(cr, text, layout_info, indent=0):
+    layout = PangoCairo.create_layout(cr)
+    text = text.encode('utf-8')
+    layout.set_text(text, len(text))
+    # Dont recreate the description all the time?
+    font = Pango.FontDescription(layout_info['font'])
+    layout.set_font_description(font)
+    layout.set_width(layout_info['twidth'] * Pango.SCALE)
+    layout.set_wrap(Pango.WrapMode.WORD_CHAR)
+
+    layout.indent = indent
+
+    return layout
+
+def show_layout(cr, layout, layout_info, skip=1.25):
+    x, y = layout_info['xshift'] + layout.indent, layout_info['ypos'] + skip
+    cr.move_to(x, y)
+
+    cr.set_source_rgb(0, 0, 0)
+    PangoCairo.show_layout(cr, layout)
+
+    layout_info['ypos'] = layout_info['ypos'] + layout.get_pixel_size()[1] + skip
+
+    # Return the position of the baseline
+    y += layout.get_baseline() / Pango.SCALE
+
+    return x, y
+
+class Questionnaire(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'annotate'
+    obj_class = model.questionnaire.Questionnaire
+
+    def draw(self, cr, page_number, layout_info):
+        cr.set_source_rgba(0.0, 0.0, 1.0, 0.5)
+        cr.set_line_width(LINE_WIDTH)
+
+        # Draw corner marks.
+        cr.move_to(defs.corner_mark_left + defs.corner_mark_length, defs.corner_mark_top)
+        cr.line_to(defs.corner_mark_left, defs.corner_mark_top)
+        cr.line_to(defs.corner_mark_left, defs.corner_mark_top+defs.corner_mark_length)
+
+        cr.move_to(self.obj.survey.defs.paper_width - defs.corner_mark_right - defs.corner_mark_length, defs.corner_mark_top)
+        cr.line_to(self.obj.survey.defs.paper_width - defs.corner_mark_right, defs.corner_mark_top)
+        cr.line_to(self.obj.survey.defs.paper_width - defs.corner_mark_right, defs.corner_mark_top + defs.corner_mark_length)
+
+        cr.move_to(defs.corner_mark_left + defs.corner_mark_length, self.obj.survey.defs.paper_height - defs.corner_mark_bottom)
+        cr.line_to(defs.corner_mark_left, self.obj.survey.defs.paper_height - defs.corner_mark_top)
+        cr.line_to(defs.corner_mark_left, self.obj.survey.defs.paper_height - defs.corner_mark_top - defs.corner_mark_length)
+
+        cr.move_to(self.obj.survey.defs.paper_width - defs.corner_mark_right - defs.corner_mark_length, self.obj.survey.defs.paper_height - defs.corner_mark_top)
+        cr.line_to(self.obj.survey.defs.paper_width - defs.corner_mark_right, self.obj.survey.defs.paper_height - defs.corner_mark_top)
+        cr.line_to(self.obj.survey.defs.paper_width - defs.corner_mark_right, self.obj.survey.defs.paper_height - defs.corner_mark_top - defs.corner_mark_length)
+
+        cr.stroke()
+
+        for qobject in self.obj.qobjects:
+            cycle_color()
+            qobject.annotate.draw(cr, page_number, layout_info)
+
+
+class QObject(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'annotate'
+    obj_class = model.questionnaire.QObject
+
+    def draw(self, cr, page_number, layout_info):
+        pass
+
+
+class Head(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'annotate'
+    obj_class = model.questionnaire.Head
+
+    def draw(self, cr, page_number, layout_info):
+        # There is no page number for head objects.
+        layout = create_layout(cr, self.obj.id_str() + " " + self.obj.title, layout_info)
+        xpos, ypos = show_layout(cr, layout, layout_info)
+
+
+class Question(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'annotate'
+    obj_class = model.questionnaire.Question
+
+    def draw(self, cr, page_number, layout_info):
+        # Does the sheet contain this question?
+        if page_number == self.obj.page_number:
+            layout = create_layout(cr, self.obj.id_str() + " " + self.obj.question, layout_info, 3)
+            xpos, ypos = show_layout(cr, layout, layout_info, 3)
+
+            # iterate over boxes
+            for box in self.obj.boxes:
+                box.annotate.draw(cr, layout_info)
+
+class Mark(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'annotate'
+    obj_class = model.questionnaire.Mark
+
+    def draw(self, cr, page_number, layout_info):
+        # Does the sheet contain this question?
+        if page_number == self.obj.page_number:
+            layout = create_layout(cr, self.obj.id_str() + " " + self.obj.question, layout_info, 3)
+            xpos, ypos = show_layout(cr, layout, layout_info, 3)
+
+            # Lower range
+            layout = create_layout(cr, self.obj.answers[0], layout_info, 10)
+            xpos, ypos = show_layout(cr, layout, layout_info, 3)
+
+            cr.move_to(self.obj.boxes[0].x, self.obj.boxes[0].y)
+            cr.line_to(xpos, ypos)
+            cr.set_line_width(LINE_WIDTH)
+            set_rgb_from_color_cycle(cr)
+            cr.stroke()
+
+            # Upper range
+            layout = create_layout(cr, self.obj.answers[1], layout_info, 10)
+            xpos, ypos = show_layout(cr, layout, layout_info, 3)
+
+            cr.move_to(self.obj.boxes[-1].x, self.obj.boxes[-1].y)
+            cr.line_to(xpos, ypos)
+            cr.set_line_width(LINE_WIDTH)
+            set_rgb_from_color_cycle(cr)
+            cr.stroke()
+
+            for box in self.obj.boxes:
+                box.annotate.draw(cr, layout_info, nostring=True)
+
+class Box(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'annotate'
+    obj_class = model.questionnaire.Checkbox
+
+    def draw_box(self, cr):
+        inner_box(cr, self.obj.x, self.obj.y, self.obj.width, self.obj.height)
+        cr.stroke()
+
+    def draw(self, cr, layout_info, nostring=False):
+        if nostring is False:
+            layout = create_layout(cr, self.obj.id_str() + " " + self.obj.text, layout_info, 6)
+            xpos, ypos = show_layout(cr, layout, layout_info)
+
+            cr.move_to(self.obj.x, self.obj.y)
+            cr.line_to(xpos, ypos)
+            cr.set_line_width(LINE_WIDTH)
+            set_rgb_from_color_cycle(cr)
+            cr.stroke()
+
+        cr.set_fill_rule(cairo.FILL_RULE_EVEN_ODD)
+
+        cr.set_source_rgba(0.0, 0.0, 1.0, 0.5)
+        cr.set_line_width(LINE_WIDTH)
+
+        self.draw_box(cr)
+
+        text = str(self.obj.id[-1])
+        layout = PangoCairo.create_layout(cr)
+        layout.set_text(text, len(text))
+        # Dont recreate the description all the time?
+        font = Pango.FontDescription(layout_info['boxfont'])
+        layout.set_font_description(font)
+
+        cr.move_to(self.obj.x + LINE_WIDTH, self.obj.y + LINE_WIDTH)
+        PangoCairo.show_layout(cr, layout)
+        cr.new_path()
+
+
+class Checkbox(Box):
+
+    __metaclass__ = model.buddy.Register
+    name = 'annotate'
+    obj_class = model.questionnaire.Checkbox
+
+    def draw_box(self, cr):
+        if self.obj.form == "box":
+            inner_box(cr, self.obj.x, self.obj.y, self.obj.width, self.obj.height)
+            cr.stroke()
+        elif self.obj.form == "ellipse":
+            inner_ellipse(cr, self.obj.x, self.obj.y, self.obj.width, self.obj.height)
+            cr.stroke()
+
+
+
+class Textbox(Box):
+
+    __metaclass__ = model.buddy.Register
+    name = 'annotate'
+    obj_class = model.questionnaire.Textbox
+
+
diff --git a/sdaps/boxgallery/__init__.py b/sdaps/boxgallery/__init__.py
new file mode 100644
index 0000000..8ef406f
--- /dev/null
+++ b/sdaps/boxgallery/__init__.py
@@ -0,0 +1,130 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import cairo
+from gi.repository import Pango
+from gi.repository import PangoCairo
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+import copy
+from . import buddies
+
+
+def paint_box(cr, mm_to_pt, x, y, box, key):
+    cr.save()
+    cr.set_matrix(mm_to_pt)
+    cr.translate(x, y)
+
+    cr.scale(25.4 / 300.0, 25.4 / 300.0)
+
+    cr.set_source_rgb(0, 0, 0)
+    cr.mask_surface(box[2][0], 0, 0)
+
+    if box[3] and key in box[3] and box[3][key] is not None:
+        cr.set_source_surface(box[3][key][0], box[3][key][1] - box[2][1], box[3][key][2] - box[2][2])
+        cr.paint()
+
+    cr.restore()
+
+    cr.save()
+
+    tmp_x, tmp_y = mm_to_pt.transform_point(x, y)
+    tmp_width, tmp_height = mm_to_pt.transform_distance(8, 13)
+
+    tmp_x, tmp_y = mm_to_pt.transform_point(x, y + 8.0)
+    # Print bold if detected ON
+    value = box[1][key] if key in box[1] else -1
+    if box[0]:
+        t = "<b>%.2f</b>" % value
+    else:
+        t = "%.2f" % value
+
+    cr.move_to(tmp_x, tmp_y)
+
+    layout = PangoCairo.create_layout(cr)
+    layout.set_markup(t, -1)
+    font = Pango.FontDescription("serif 6")
+    layout.set_font_description(font)
+
+    PangoCairo.show_layout(cr, layout)
+
+    cr.restore()
+
+
+def fill_page(cr, mm_to_pt, checkboxes, key):
+    y = 15
+    y_step = 13
+    x_step = 10
+    x_max = 210 - 15 - 8
+    y_max = 297 - 15 - 8
+
+    while y < y_max:
+        x = 15
+        while x < x_max:
+
+            if len(checkboxes) == 0:
+                return
+            box = checkboxes.pop(0)
+
+            paint_box(cr, mm_to_pt, x, y, box, key)
+
+            x += x_step
+        y += y_step
+
+
+def boxgallery(survey, debugrecognition):
+    # Enable debug image creation in the C module
+    if debugrecognition:
+        from sdaps import image
+        image.enable_debug_surface_creation(True)
+
+    survey.questionnaire.boxgallery.init(debugrecognition)
+    survey.iterate_progressbar(survey.questionnaire.boxgallery.get_checkbox_images)
+    checkboxes = survey.questionnaire.boxgallery.checkboxes
+    survey.questionnaire.boxgallery.clean()
+
+    keys = set()
+    for checkbox in checkboxes:
+        keys = keys.union(checkbox[1].iterkeys())
+
+    for key in keys:
+        print _("Rendering boxgallery for metric \"%s\"." % key)
+        draw_list = copy.copy(checkboxes)
+        draw_list.sort(key=lambda x: x[1][key] if key in x[1] else 0)
+
+        # Hardcode 300dpi
+        # Hardcode the mm size:
+        # 3.5 + 0.4mm = 3.9mm
+        mm_to_pt = cairo.Matrix(72.0 / 25.4, 0, 0, 72.0 / 25.4, 0, 0)
+
+        page = 1
+        pdf = cairo.PDFSurface(survey.path('boxgallery-%s.pdf' % key), 595, 842)
+        cr = cairo.Context(pdf)
+
+        while len(draw_list) > 0:
+            fill_page(cr, mm_to_pt, draw_list, key)
+            cr.show_page()
+            pdf.flush()
+
+            page += 1
+
+        del pdf
+        del cr
+
diff --git a/sdaps/boxgallery/buddies.py b/sdaps/boxgallery/buddies.py
new file mode 100644
index 0000000..55e3732
--- /dev/null
+++ b/sdaps/boxgallery/buddies.py
@@ -0,0 +1,138 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import cairo
+
+from sdaps import model
+from sdaps import surface
+from sdaps import matrix
+
+
+class Sheet(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'boxgallery'
+    obj_class = model.sheet.Sheet
+
+    def load(self):
+        for image in self.obj.images:
+            if not image.ignored:
+                image.surface.load()
+
+    def clean(self):
+        for image in self.obj.images:
+            if not image.ignored:
+                image.surface.clean()
+
+
+class Questionnaire(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'boxgallery'
+    obj_class = model.questionnaire.Questionnaire
+
+    def init(self, debugrecognition):
+        self.checkboxes = list()
+        self.debugrecognition = debugrecognition
+
+    def clean(self):
+        del self.checkboxes
+
+    def get_checkbox_images(self):
+        if self.obj.survey.sheet.valid:
+            self.obj.survey.sheet.boxgallery.load()
+            for qobject in self.obj.qobjects:
+                self.checkboxes.extend(qobject.boxgallery.get_checkbox_images(self.debugrecognition))
+            self.obj.survey.sheet.boxgallery.clean()
+
+
+class QObject(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'boxgallery'
+    obj_class = model.questionnaire.QObject
+
+    def get_checkbox_images(self, debugrecognition):
+        return []
+
+
+class Question(QObject):
+
+    __metaclass__ = model.buddy.Register
+    name = 'boxgallery'
+    obj_class = model.questionnaire.Question
+
+    def get_checkbox_images(self, debugrecognition):
+        boxes = []
+
+        for box in self.obj.boxes:
+            new_box = box.boxgallery.get_checkbox_image(debugrecognition)
+            if new_box:
+                boxes.append(new_box)
+        return boxes
+
+
+class Box(model.buddy.Buddy):
+    __metaclass__ = model.buddy.Register
+    name = 'boxgallery'
+    obj_class = model.questionnaire.Box
+
+    def get_checkbox_image(self, debugrecognition):
+        return None
+
+
+class Checkbox(model.buddy.Buddy):
+    __metaclass__ = model.buddy.Register
+    name = 'boxgallery'
+    obj_class = model.questionnaire.Checkbox
+
+    def get_checkbox_image(self, debugrecognition):
+        image = self.obj.sheet.get_page_image(self.obj.page_number)
+
+        border = 1.5
+        mm_to_px = image.matrix.mm_to_px()
+
+        px_x, px_y = mm_to_px.transform_point(
+            self.obj.data.x - border, self.obj.data.y - border)
+        px_x, px_y = int(px_x), int(px_y)
+        px_width, px_height = mm_to_px.transform_distance(
+            self.obj.data.width + 2 * border, self.obj.data.height + 2 * border)
+
+        dest = cairo.ImageSurface(
+            cairo.FORMAT_A1, int(px_width), int(px_height))
+        src = image.surface.surface
+
+        cr = cairo.Context(dest)
+        cr.set_source_surface(src, -px_x, -px_y)
+        cr.set_operator(cairo.OPERATOR_SOURCE)
+        cr.paint()
+
+        del cr
+        dest.flush()
+
+        debug = {}
+        if debugrecognition:
+            # Run the recognition for this checkbox
+            self.obj.recognize.recognize()
+
+            debug = self.obj.recognize.debug
+
+        return (self.obj.data.state, self.obj.data.metrics, (dest, px_x, px_y), debug)
+
+
+
diff --git a/sdaps/calculate.py b/sdaps/calculate.py
new file mode 100644
index 0000000..1e7c144
--- /dev/null
+++ b/sdaps/calculate.py
@@ -0,0 +1,204 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+u"""This module contains buddy objects to calculate statistics from the data.
+
+It is possible to search for large changes between different filters by calling
+the "reference" function after a calculation. After this the significant boolean
+will be set for new calculations (with different filters) if there is a large
+deviation from the old value."""
+
+import math
+
+from sdaps import model
+
+
+class Questionnaire(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'calculate'
+    obj_class = model.questionnaire.Questionnaire
+
+    def init(self):
+        """Initialize or reset the state of the calculate module."""
+        self.count = 0
+        # iterate over qobjects
+        for qobject in self.obj.qobjects:
+            qobject.calculate.init()
+
+    def read(self):
+        """The function collects the data from a sheet. You should use
+        :py:meth:`Survey.iterate` to call it for each sheet that needs to be
+        counted."""
+        self.count += 1
+        # iterate over qobjects
+        for qobject in self.obj.qobjects:
+            qobject.calculate.read()
+
+    def calculate(self):
+        """Call once after :py:meth:`Questionnaire.calculate.read` to calculate
+        statistical values like the standard deviation."""
+        # iterate over qobjects
+        for qobject in self.obj.qobjects:
+            qobject.calculate.calculate()
+
+    def reference(self):
+        """Can be used to calculate a reference value. You can later check
+        whether there was a significant difference to the previous run. The
+        `significant` property will be set accordingly."""
+        # iterate over qobjects
+        for qobject in self.obj.qobjects:
+            qobject.calculate.reference()
+
+
+class QObject(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'calculate'
+    obj_class = model.questionnaire.QObject
+
+    def init(self):
+        pass
+
+    def read(self):
+        pass
+
+    def calculate(self):
+        pass
+
+    def reference(self):
+        pass
+
+
+class Question(QObject):
+
+    __metaclass__ = model.buddy.Register
+    name = 'calculate'
+    obj_class = model.questionnaire.Question
+
+
+class Choice(Question):
+    """
+    :ivar count: Number of times the question was answered.
+    :ivar values: Dictionary for each box with the ratio the answer was choosen.
+    :ivar significant: Whether there was a significant difference to the reference run.
+    """
+
+
+    __metaclass__ = model.buddy.Register
+    name = 'calculate'
+    obj_class = model.questionnaire.Choice
+
+    def init(self):
+        self.count = 0
+        self.values = {box.value: 0 for box in self.obj.boxes}
+        self.significant = {box.value: 0 for box in self.obj.boxes}
+
+    def read(self):
+        self.count += 1
+        for item in self.obj.get_answer():
+            self.values[item] += 1
+
+    def calculate(self):
+        if self.count:
+            for value in self.values:
+                self.values[value] = self.values[value] / float(self.count)
+                if hasattr(self, 'ref_count'):
+                    self.significant[value] = (
+                        abs(self.values[value] - self.ref_values[value]) > 0.1)
+
+    def reference(self):
+        self.ref_count = self.count
+        self.ref_values = self.values
+
+
+class Mark(Question):
+    """
+    :ivar count: Number of times the question was answered.
+    :ivar values: Dictionary for each box with the ratio the value was choosen.
+    :ivar mean: The average value that was choosen.
+    :ivar standard_deviation: The average value that was choosen.
+    :ivar significant: Whether there was a significant difference to the reference run.
+    """
+
+    __metaclass__ = model.buddy.Register
+    name = 'calculate'
+    obj_class = model.questionnaire.Mark
+
+    def init(self):
+        self.count = 0
+        self.values = {box.value + 1: 0 for box in self.obj.boxes}
+        self.significant = 0
+        self.mean = 0
+        self.standard_deviation = 0
+
+    def read(self):
+        answer = self.obj.get_answer()
+        if answer:
+            self.count += 1
+            self.values[answer] += 1
+
+    def calculate(self):
+        if self.count:
+            for mark in self.values:
+                self.values[mark] = self.values[mark] / float(self.count)
+            self.mean = sum(
+                [mark * value for mark, value in self.values.items()])
+            self.standard_deviation = math.sqrt(sum([
+                value * pow(mark - self.mean, 2)
+                for mark, value in self.values.items()]))
+            if hasattr(self, 'ref_count'):
+                self.significant = abs(self.mean - self.ref_mean) > 0.1
+
+    def reference(self):
+        self.ref_count = self.count
+        self.ref_mean = self.mean
+
+
+class Additional_FilterHistogram(Question):
+
+    __metaclass__ = model.buddy.Register
+    name = 'calculate'
+    obj_class = model.questionnaire.Additional_FilterHistogram
+
+    def init(self):
+        self.count = 0
+        self.values = [0] * len(self.obj.answers)
+        self.significant = [0] * len(self.obj.answers)
+
+    def read(self):
+        self.count += 1
+        for i in range(len(self.obj.answers)):
+            filter = clifilter.clifilter(
+                self.obj.questionnaire.survey, self.obj.filters[i])
+            if filter():
+                self.values[i] += 1
+
+    def calculate(self):
+        if self.count:
+            self.significant = dict()
+            for i in range(len(self.values)):
+                self.values[i] = self.values[i] / float(self.count)
+                if hasattr(self, 'ref_count'):
+                    self.significant[i] = (
+                        abs(self.values[i] - self.ref_values[i]) > 0.1)
+
+    def reference(self):
+        self.ref_count = self.count
+        self.ref_values = self.values
+
diff --git a/sdaps/clifilter.py b/sdaps/clifilter.py
new file mode 100644
index 0000000..d316b8c
--- /dev/null
+++ b/sdaps/clifilter.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+u"""
+This modules contains a helper function to allow writing filter expressions
+on the command line of sdaps. Using this it is for example possible to create
+a report that only contains a subset of all filled out sheets.
+"""
+
+
+class Locals(object):
+
+    def __init__(self, survey):
+        self.survey = survey
+        self.qobjects = dict([
+            (qobject.id_filter(), qobject)
+            for qobject in self.survey.questionnaire.qobjects
+        ])
+
+    def __getitem__(self, key):
+        if key in self.qobjects:
+            return self.qobjects[key].get_answer()
+        elif key in ['survey_id', 'questionnaire_id', 'global_id', 'valid', 'quality', 'recognized', 'verified', 'complete']:
+            return getattr(self.survey.sheet, key)
+        else:
+            raise KeyError
+
+
+def clifilter(survey, expression):
+    if expression is None or expression.strip() == '':
+        return lambda: True
+
+    exp = compile(expression, '<string>', 'eval')
+    globals = __builtins__
+    locals = Locals(survey)
+    return lambda: eval(exp, globals, locals)
+
diff --git a/sdaps/cmdline/__init__.py b/sdaps/cmdline/__init__.py
new file mode 100644
index 0000000..42deadd
--- /dev/null
+++ b/sdaps/cmdline/__init__.py
@@ -0,0 +1,21 @@
+
+# Simply import all submodules
+
+from . import add
+from . import annotate
+from . import boxgallery
+from . import convert
+from . import cover
+from . import csvdata
+from . import gui
+from . import ids
+from . import info
+from . import recognize
+from . import reorder
+from . import report
+from . import reporttex
+from . import setup
+from . import setuptex
+from . import stamp
+
+
diff --git a/sdaps/cmdline/add.py b/sdaps/cmdline/add.py
new file mode 100644
index 0000000..f599cbb
--- /dev/null
+++ b/sdaps/cmdline/add.py
@@ -0,0 +1,140 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import os
+import tempfile
+
+from sdaps import model
+from sdaps import script
+from sdaps import log
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+
+parser = script.subparsers.add_parser("add",
+    help=_("Add scanned questionnaires to the survey."),
+    description=_("""This command is used to add scanned images to the survey.
+    The image data needs to be a (multipage) 300dpi monochrome TIFF file. You
+    may choose not to copy the data into the project directory. In that case
+    the data will be referenced using a relative path."""))
+
+parser.add_argument('--convert',
+    help=_("Convert given files and add the result."),
+    dest="convert",
+    action="store_true",
+    default=False)
+parser.add_argument('--3d-transform',
+    help=_("""Do a 3D-transformation after finding the corner marks. If the
+        corner marks are not found then the image will be added as-is."""),
+    dest="transform",
+    action="store_true",
+    default=False)
+parser.add_argument('--force',
+    help=_("Force adding the images even if the page count is wrong (only use if you know what you are doing)."),
+    action="store_true",
+    default=False)
+parser.add_argument('--copy',
+    help=_("Copy the files into the directory (default)."),
+    dest="copy",
+    action="store_true",
+    default=True)
+parser.add_argument('--no-copy',
+    help=_("Do not copy the files into the directory."),
+    dest="copy",
+    action="store_false")
+parser.add_argument('--duplex',
+    help=_("Images contain a duplex scan of a simplex questionnaire (default: simplex scan)."),
+    dest="duplex",
+    action="store_true",
+    default=False)
+
+parser.add_argument('images',
+    help=_("A number of TIFF image files."),
+    nargs='+')
+
+ at script.connect(parser)
+ at script.logfile
+def add(cmdline):
+    import sys
+    from sdaps.add import add_image, check_image
+    from sdaps import image
+
+    error = False
+    survey = model.survey.Survey.load(cmdline['project'])
+
+    filelist = []
+    deletelist = []
+
+    if not cmdline['convert']:
+        for file in cmdline['images']:
+            filelist.append(file)
+
+            if not check_image(survey, file, cmdline['duplex'], cmdline['force'], message=True):
+                error=True
+        if error:
+            return
+    else:
+        if not cmdline['copy']:
+            log.error(_("The --no-copy option is not compatible with --convert!"))
+            return 1
+
+        try:
+            from sdaps.convert import convert_images
+        except:
+            log.error("Need to convert the images to monochrome TIFF, however the conversion module cannot be imported. You are likely missing the OpenCV dependency.")
+            return 1
+
+        print _("Converting input files into a single temporary file.")
+
+        tmp = tempfile.mktemp(suffix='.tif', prefix='sdaps-convert-')
+        deletelist.append(tmp)
+        filelist.append(tmp)
+
+        # Run conversion
+        # TODO: Allow 3D transformation here!
+        try:
+            convert_images(cmdline['images'], tmp, survey.defs.paper_width, survey.defs.paper_height, cmdline['transform'])
+
+            if not check_image(survey, tmp, cmdline['duplex'], cmdline['force']):
+                log.error(_("The page count of the created temporary file does not work with this survey."))
+                raise AssertionError()
+
+        except Exception, e:
+            log.error(str(e))
+            log.error(_("Running the conversion failed."))
+            error = True
+
+    if not error:
+        for file in filelist:
+            print _('Processing %s') % file
+
+            add_image(survey, file, cmdline['duplex'], cmdline['force'], cmdline['copy'])
+
+            print _('Done')
+
+    for file in deletelist:
+        os.unlink(file)
+
+    if error:
+        return 1
+    else:
+        survey.save()
+        return 0
+
+
diff --git a/sdaps/cmdline/annotate.py b/sdaps/cmdline/annotate.py
new file mode 100644
index 0000000..3da7ad9
--- /dev/null
+++ b/sdaps/cmdline/annotate.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2012, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import os
+
+from sdaps import model
+from sdaps import script
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+
+parser = script.subparsers.add_parser("annotate",
+    help=_("Annotate the questionnaire and show the recognized positions."),
+    description=_("""This command is mainly a debug utility. It creates an
+    annotated version of the questionnaire, with the information that SDAPS
+    knows about it overlayed on top."""))
+
+ at script.connect(parser)
+ at script.logfile
+def annotate(cmdline):
+    from sdaps.annotate import annotate
+
+    survey = model.survey.Survey.load(cmdline['project'])
+
+    return annotate(survey)
+
diff --git a/sdaps/cmdline/boxgallery.py b/sdaps/cmdline/boxgallery.py
new file mode 100644
index 0000000..9bf69e3
--- /dev/null
+++ b/sdaps/cmdline/boxgallery.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+u"""
+This modules contains a script for the user to output all boxes sorted by their
+coverage. This can be used to adjust magic values for checkbox recognition.
+"""
+
+from sdaps import model
+from sdaps import script
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+parser = script.subparsers.add_parser("boxgallery",
+    help=_("Create PDFs with boxes sorted by the detection heuristics."),
+    description=_("""SDAPS uses multiple heuristics to detect determine the
+    state of checkboxes. There is a list for each heuristic giving the expected
+    state and the quality of the value (see defs.py). Using this command a PDF
+    will be created for each of the heuristics so that one can adjust the
+    values."""))
+
+parser.add_argument('--debugrecognition',
+    action="store_true",
+    help=_('Reruns part of the recognition process and retrieves debug images from this step.'),
+    default=False)
+
+ at script.connect(parser)
+ at script.logfile
+def boxgallery(cmdline):
+
+    # We need to load the buddies before the survey is loaded.
+    if cmdline['debugrecognition']:
+        from sdaps.recognize import buddies
+
+    survey = model.survey.Survey.load(cmdline['project'])
+    from sdaps import boxgallery
+    return boxgallery.boxgallery(survey, cmdline['debugrecognition'])
+
+
diff --git a/sdaps/cmdline/convert.py b/sdaps/cmdline/convert.py
new file mode 100644
index 0000000..90e63bd
--- /dev/null
+++ b/sdaps/cmdline/convert.py
@@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2013, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import os
+import sys
+
+from sdaps import model
+from sdaps import script
+from sdaps import log
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+
+parser = script.subparsers.add_parser("convert",
+    help=_("Convert a set of images to the correct image format."),
+    description=_("""This command can be used if you scanned files in a different
+        mode than the expected monochrome TIFF file. All the given files will
+        be loaded, converted to monochrome and stored into a multipage 1bpp
+        TIFF file. Optionally you can select that a 3D transformation should
+        be performed, using this it may be possible to work with photos of
+        questionnaires instead of scans."""))
+
+parser.add_argument('--3d-transform',
+    help=_("""Do a 3D-transformation after finding the corner marks. If the
+        corner marks are not found then the image will be added as-is."""),
+    dest="transform",
+    action="store_true",
+    default=False)
+parser.add_argument('-o', '--output',
+    help=_("The location of the output file. Must be given."),
+    dest="output")
+
+parser.add_argument('images',
+    help=_("A number of TIFF image files."),
+    nargs='+')
+
+ at script.connect(parser)
+ at script.logfile
+def convert(cmdline):
+    from sdaps.convert import convert_images
+
+    if cmdline['output'] is None:
+        log.error(_("No output filename specified!"))
+        sys.exit(1)
+
+    # We need a survey only for the paper size!
+    survey = model.survey.Survey.load(cmdline['project'])
+
+    convert_images(cmdline['images'], cmdline['output'], survey.defs.paper_width, survey.defs.paper_height, cmdline['transform'])
+
+
diff --git a/sdaps/cmdline/cover.py b/sdaps/cmdline/cover.py
new file mode 100644
index 0000000..a8d897f
--- /dev/null
+++ b/sdaps/cmdline/cover.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+from sdaps import model
+from sdaps import script
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+
+parser = script.subparsers.add_parser("cover",
+    help=_("Create a cover for the questionnaires."),
+    description=_("""This command creates a cover page for questionnaires. All
+    the metadata of the survey will be printed on the page."""))
+parser.add_argument('-o', '--output',
+    help=_("Filename to store the data to (default: cover_%%i.pdf)"))
+
+ at script.connect(parser)
+ at script.logfile
+def cover(cmdline):
+    from sdaps import cover
+
+    survey = model.survey.Survey.load(cmdline['project'])
+
+    cover.cover(survey, cmdline['output'])
+
+
diff --git a/sdaps/cmdline/csvdata.py b/sdaps/cmdline/csvdata.py
new file mode 100644
index 0000000..c905af9
--- /dev/null
+++ b/sdaps/cmdline/csvdata.py
@@ -0,0 +1,121 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+u"""
+This module implements a simple export/import to/from CSV files.
+"""
+
+import os
+import sys
+
+from sdaps import model
+from sdaps import script
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+
+parser = script.subparsers.add_parser("csv",
+    help=_("Import or export data to/from CSV files."),
+    description=_("""Import or export data to/from a CSV file. The first line
+    is a header which defines questionnaire_id and global_id, and a column
+    for each checkbox and textfield. Note that the import is currently very
+    limited, as you need to specifiy the questionnaire ID to select the sheet
+    which should be updated."""))
+
+subparser = parser.add_subparsers()
+
+export = subparser.add_parser('export',
+    help=_("Export data to CSV file."))
+export.add_argument('-o', '--output',
+    help=_("Filename to store the data to (default: data_%%i.csv)"))
+export.add_argument('-d', '--delimiter',
+    help=_("The delimiter used in the CSV file (default ',')"),
+    default=',',
+    action='store')
+export.add_argument('-f', '--filter',
+    help=_("Filter to only export a partial dataset."))
+export.add_argument('--images',
+    help=_("Export images of freeform fields."),
+    dest='export_images',
+    action='store_const',
+    const=True,
+    default=False)
+export.add_argument('--question-images',
+    help=_("Export an image for each question that includes all boxes."),
+    dest='export_question_images',
+    action='store_const',
+    const=True,
+    default=False)
+export.add_argument('--quality',
+    help=_("Export the recognition quality for each checkbox."),
+    dest='export_quality',
+    action='store_const',
+    const=True,
+    default=False)
+export.set_defaults(direction='export')
+
+import_ = subparser.add_parser('import',
+    help=_("Import data to from a CSV file."))
+import_.add_argument('file',
+    help=_("The file to import."))
+import_.set_defaults(direction='import')
+
+ at script.connect(parser)
+ at script.logfile
+def csvdata(cmdline):
+    from sdaps import csvdata
+    from sdaps.utils.image import ImageWriter
+
+    survey = model.survey.Survey.load(cmdline['project'])
+
+    if cmdline['direction'] == 'export':
+        if cmdline['output']:
+            if cmdline['output'] == '-':
+                outfd = os.dup(sys.stdout.fileno())
+                outfile = os.fdopen(outfd, 'w')
+            else:
+                filename = cmdline['output']
+                outfile = file(filename, 'w')
+        else:
+            filename = survey.new_path('data_%i.csv')
+            outfile = file(filename, 'w')
+
+        csvoptions = { 'delimiter' : cmdline['delimiter'] }
+
+        if cmdline['export_images'] or cmdline['export_question_images'] and cmdline['output'] != '-':
+            img_path = os.path.dirname(filename)
+            img_prefix = os.path.join(os.path.splitext(os.path.basename(filename))[0], 'img')
+
+            image_writer = ImageWriter(img_path, img_prefix)
+        else:
+            image_writer = None
+
+
+        return csvdata.csvdata_export(survey, outfile, image_writer,
+            filter=cmdline['filter'],
+            export_images=cmdline['export_images'],
+            export_question_images=cmdline['export_question_images'],
+            export_quality=cmdline['export_quality'],
+            csvoptions=csvoptions)
+    elif cmdline['direction'] == 'import':
+        return csvdata.csvdata_import(survey, file(cmdline['file'], 'r'))
+    else:
+        raise AssertionError
+
+
diff --git a/sdaps/cmdline/gui.py b/sdaps/cmdline/gui.py
new file mode 100644
index 0000000..bd7b7bd
--- /dev/null
+++ b/sdaps/cmdline/gui.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+
+from sdaps import model
+from sdaps import script
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+
+parser = script.subparsers.add_parser("gui",
+    help=_("Launch a gui. You can view and alter the (recognized) answers with it."),
+    description=_("""This command launches a graphical user interface that can
+    be used to correct answer. You need to run "recognize" before using it.
+    """))
+
+parser.add_argument('-f', '--filter',
+    help=_("Filter to only show a partial dataset."))
+
+ at script.connect(parser)
+ at script.logfile
+def gui(cmdline):
+    from sdaps import gui
+
+    survey = model.survey.Survey.load(cmdline['project'])
+
+    return gui.gui(survey, cmdline)
+
+
diff --git a/sdaps/cmdline/ids.py b/sdaps/cmdline/ids.py
new file mode 100644
index 0000000..b2d71aa
--- /dev/null
+++ b/sdaps/cmdline/ids.py
@@ -0,0 +1,83 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+from sdaps import model
+from sdaps import script
+
+import os
+import sys
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+parser = script.subparsers.add_parser("ids",
+    help=_("Export and import questionnaire IDs."),
+    description=_("""This command can be used to import and export questionnaire
+    IDs. It only makes sense in projects where such an ID is printed on the
+    questionnaire. Note that you can also add IDs by using the stamp command,
+    which will give you the PDF at the same time."""))
+parser.add_argument('-o', '--output',
+    help=_("Filename to store the data to (default: ids_%%i)"))
+parser.add_argument('-a', '--add',
+    metavar="FILE",
+    help=_("Add IDs to the internal list from the specified file."))
+
+ at script.connect(parser)
+ at script.logfile
+def ids(cmdline):
+    survey = model.survey.Survey.load(cmdline['project'])
+
+    if cmdline['add']:
+        if cmdline['add'] == '-':
+            ids = sys.stdin
+        else:
+            ids = open(cmdline['add'], 'r')
+
+        to_add = []
+        for line in ids.readlines():
+            # Skip empty lines
+            if line == "":
+                continue
+
+            line = line.decode('utf-8')
+            line = line.strip('\r\n')
+            to_add.append(survey.validate_questionnaire_id(line))
+
+        survey.questionnaire_ids += to_add
+        survey.save()
+
+    else:
+        if cmdline['output']:
+            if cmdline['output'] == '-':
+                outfd = os.dup(sys.stdout.fileno())
+                ids = os.fdopen(outfd, 'w')
+            else:
+                filename = cmdline['output']
+                ids = file(filename, 'w')
+        else:
+            filename = survey.new_path('ids_%i')
+            ids = file(filename, 'w')
+
+        for id in survey.questionnaire_ids:
+            ids.write(unicode(id).encode('utf-8'))
+            ids.write('\n')
+
+        if ids != sys.stdout:
+            ids.close()
+
+
diff --git a/sdaps/cmdline/info.py b/sdaps/cmdline/info.py
new file mode 100644
index 0000000..c632e2c
--- /dev/null
+++ b/sdaps/cmdline/info.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+from sdaps import model
+from sdaps import script
+from sdaps import log
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+
+parser = script.subparsers.add_parser("info",
+    help=_("Display and modify metadata of project."),
+    description=_("""This command lets you modify the metadata of the SDAPS
+    project. You can modify, add and remove arbitrary keys that will be printed
+    on the report. The only key that always exist is "title".
+    If no key is given then a list of defined keys is printed."""))
+
+parser.add_argument('key',
+    nargs="?",
+    help=_("The key to display or modify."))
+
+parser.add_argument('-s', '--set',
+    metavar="VALUE",
+    help=_("Set the given key to this value."))
+
+parser.add_argument('-d', '--delete',
+    action="store_true",
+    help=_("Delete the key and value pair."))
+
+
+ at script.connect(parser)
+ at script.logfile
+def info(cmdline):
+    survey = model.survey.Survey.load(cmdline['project'])
+
+    if cmdline['key']:
+        key = cmdline['key'].decode('utf-8').strip()
+        if cmdline['set']:
+            value = cmdline['set'].decode('utf-8').strip()
+            if key == "title":
+                survey.title = value
+            else:
+                survey.info[key] = value
+        elif cmdline['delete']:
+            del survey.info[key]
+        else:
+            if key == "title":
+                print survey.title
+            else:
+                print survey.info[key]
+    else:
+        log.interactive(_(u'Existing fields:\n'))
+        print "title"
+        for key in survey.info.keys():
+            print key
+
+    survey.save()
+
+
diff --git a/sdaps/cmdline/recognize.py b/sdaps/cmdline/recognize.py
new file mode 100644
index 0000000..d425b69
--- /dev/null
+++ b/sdaps/cmdline/recognize.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+
+from sdaps import model
+from sdaps import script
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+
+parser = script.subparsers.add_parser("recognize",
+    help=_("Run the optical mark recognition."),
+    description=_("""Iterates over all images and runs the optical mark
+    recognition. It will reevaluate sheets even if "recognize" has already
+    run or manual changes were made."""))
+
+parser.add_argument('--identify',
+    help=_("Only identify the page properties, but don't recognize the checkbox states."),
+    action="store_true",
+    default=False)
+
+parser.add_argument('--rerun', '-r',
+    help=_("Rerun the recognition for all pages. The default is to skip all pages that were recognized or verified already."),
+    action="store_true",
+    default=False)
+
+ at script.connect(parser)
+ at script.logfile
+def recognize(cmdline):
+    survey = model.survey.Survey.load(cmdline['project'])
+    from sdaps import recognize
+
+    if not cmdline['rerun']:
+        filter = lambda : not (survey.sheet.verified or survey.sheet.recognized)
+    else:
+        filter = lambda: True
+
+    if cmdline['identify']:
+        return recognize.identify(survey, filter)
+    else:
+        return recognize.recognize(survey, filter)
+
+
diff --git a/sdaps/cmdline/reorder.py b/sdaps/cmdline/reorder.py
new file mode 100644
index 0000000..62420e5
--- /dev/null
+++ b/sdaps/cmdline/reorder.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2012, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+from sdaps import model
+from sdaps import script
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+
+parser = script.subparsers.add_parser("reorder",
+    help=_("Reorder pages according to questionnaire ID."),
+    description=_("""This command reorders all pages according to the already
+    recognized questionnaire ID. To use it add all the files to the project,
+    then run a partial recognition using "recognize --identify". After this
+    you have to run this command to reorder the data for the real recognition.
+    """))
+
+
+ at script.connect(parser)
+ at script.logfile
+def reorder(cmdline):
+    survey = model.survey.Survey.load(cmdline['project'])
+
+    from sdaps import reorder
+
+    return reorder.reorder(survey)
+
diff --git a/sdaps/cmdline/report.py b/sdaps/cmdline/report.py
new file mode 100644
index 0000000..29f3877
--- /dev/null
+++ b/sdaps/cmdline/report.py
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+from sdaps import model
+from sdaps import script
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+parser = script.subparsers.add_parser("report",
+    help=_("Create a PDF report."),
+    description=_("""This command creates a PDF report using reportlab that
+    contains statistics and if selected the freeform fields."""))
+
+parser.add_argument('-f', '--filter',
+    help=_("Filter to only export a partial dataset."))
+parser.add_argument('--all-filters',
+    help=_("Create a filtered report for every checkbox."),
+    action="store_true")
+parser.add_argument('-s', '--short',
+    help=_("Short format (without freeform text fields)."),
+    action="store_const",
+    const="short",
+    dest="format")
+parser.add_argument('-l', '--long',
+    help=_("Detailed output. (default)"),
+    dest="format",
+    action="store_const",
+    const="long",
+    default="long")
+parser.add_argument('--suppress-images',
+    help=_('Do not include original images in the report. This is useful if there are privacy concerns.'),
+    dest='suppress',
+    action='store_const',
+    const='images')
+parser.add_argument('--suppress-substitutions',
+    help=_('Do not use substitutions instead of images.'),
+    dest='suppress',
+    action='store_const',
+    const='substitutions',
+    default=None)
+parser.add_argument('-p', '--paper',
+    help=_('The paper size used for the output (default: locale dependent)'),
+    dest='papersize')
+parser.add_argument('-o', '--output',
+    help=_("Filename to store the data to (default: report_%%i.pdf)"))
+
+ at script.connect(parser)
+ at script.logfile
+def report(cmdline):
+    from sdaps import report
+
+    survey = model.survey.Survey.load(cmdline['project'])
+
+    if cmdline['format'] == 'short':
+        small = 1
+    else:
+        small = 0
+
+    if cmdline['all_filters']:
+        return report.stats(survey, cmdline['filter'], cmdline['output'], cmdline['papersize'], small, cmdline['suppress'])
+    else:
+        return report.report(survey, cmdline['filter'], cmdline['output'], cmdline['papersize'], small, cmdline['suppress'])
+
+
diff --git a/sdaps/cmdline/reporttex.py b/sdaps/cmdline/reporttex.py
new file mode 100644
index 0000000..bb6cbe4
--- /dev/null
+++ b/sdaps/cmdline/reporttex.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, 2011, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+from sdaps import model
+from sdaps import script
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+parser = script.subparsers.add_parser("report_tex",
+    help=_("Create a PDF report using LaTeX."),
+    description=_("""This command creates a PDF report using LaTeX that
+    contains statistics and freeform fields."""))
+parser.add_argument('--suppress-images',
+    help=_('Do not include original images in the report. This is useful if there are privacy concerns.'),
+    dest='suppress',
+    action='store_const',
+    const='images')
+parser.add_argument('--suppress-substitutions',
+    help=_('Do not use substitutions instead of images.'),
+    dest='suppress',
+    action='store_const',
+    const='substitutions',
+    default=None)
+parser.add_argument('--create-tex',
+    help=_('Save the generated TeX files instead of the final PDF.'),
+    dest='create-tex',
+    action='store_true',
+    default=False)
+parser.add_argument('-p', '--paper',
+    help=_('The paper size used for the output (default: locale dependent)'),
+    dest='papersize')
+parser.add_argument('-o', '--output',
+    help=_("Filename to store the data to (default: report_%%i.pdf)"))
+
+parser.add_argument('-f', '--filter',
+    help=_("Filter to only export a partial dataset."))
+
+ at script.connect(parser)
+ at script.logfile
+def report_tex(cmdline):
+    from sdaps import reporttex
+
+    survey = model.survey.Survey.load(cmdline['project'])
+
+    return reporttex.report(survey, cmdline['filter'], cmdline['output'], cmdline['papersize'], suppress=cmdline['suppress'], tex_only=cmdline['create-tex'])
+
+
diff --git a/sdaps/cmdline/setup.py b/sdaps/cmdline/setup.py
new file mode 100644
index 0000000..7b95794
--- /dev/null
+++ b/sdaps/cmdline/setup.py
@@ -0,0 +1,102 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+from sdaps import model
+from sdaps import script
+import optparse
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+
+parser = script.subparsers.add_parser("setup",
+    help=_("Create a new survey using an ODT document."),
+    description=_("""Create a new surevey from an ODT document. The PDF export
+    of the file needs to be specified at the same time. SDAPS will import
+    metadata (properties) and the title from the ODT document."""))
+
+parser.add_argument('questionnaire.odt',
+    help=_("The ODT Document"))
+parser.add_argument('questionnaire.pdf',
+    help=_("PDF export of the ODT document"))
+parser.add_argument('additional_questions',
+    nargs='?',
+    help=_("Additional questions that are not part of the questionnaire."))
+
+parser.set_defaults(print_survey_id=True)
+parser.set_defaults(print_questionnaire_id=False)
+parser.set_defaults(global_id="")
+parser.set_defaults(duplex=True)
+
+parser.add_argument('--print-survey-id',
+    action="store_const",
+    help=_('Enable printing of the survey ID (default).'),
+    dest='print_survey_id', const=True)
+parser.add_argument('--no-print-survey-id',
+    action="store_const",
+    help=_('Disable printing of the survey ID.'),
+    dest='print_survey_id', const=False)
+
+parser.add_argument('--print-questionnaire-id',
+    action="store_true",
+    help=_('Enable printing of the questionnaire ID.'),
+    dest='print_questionnaire_id')
+parser.add_argument('--no-print-questionnaire-id',
+    action="store_false",
+    help=_('Disable printing of the questionnaire ID (default).'),
+    dest='print_questionnaire_id')
+
+parser.add_argument('--global-id',
+    help=_('Set an additional global ID for tracking. This can can only be used in the "code128" style.'),
+    dest='global_id')
+
+parser.add_argument('--style',
+    choices=model.survey.valid_styles,
+    help=_('The stamping style to use. Should be either "classic" or "code128". Use "code128" for more features.'),
+    dest='style',
+    default="code128")
+
+parser.add_argument('--checkmode',
+    choices=model.survey.valid_checkmodes,
+    help=_('The mode to use when detecting checkboxes.'),
+    dest='checkmode',
+    default="checkcorrect")
+
+parser.add_argument('--duplex',
+    action="store_true",
+    help=_('Use duplex mode (ie. only print the IDs on the back side). Requires a mulitple of two pages.'),
+    dest='duplex')
+
+parser.add_argument('--simplex',
+    action="store_false",
+    help=_('Use simplex mode. IDs are printed on each page. You need a simplex scan currently!'),
+    dest='duplex')
+
+ at script.connect(parser)
+def setup(cmdline):
+    from sdaps import setupodt
+
+    survey = model.survey.Survey.new(cmdline['project'])
+
+    # Cleanup of options.
+    if cmdline['global_id'] == '':
+        cmdline['global_id'] = None
+
+    return setupodt.setup(survey, cmdline['questionnaire.odt'], cmdline['questionnaire.pdf'], cmdline['additional_questions'], cmdline)
+
+
diff --git a/sdaps/cmdline/setuptex.py b/sdaps/cmdline/setuptex.py
new file mode 100644
index 0000000..5c05a4f
--- /dev/null
+++ b/sdaps/cmdline/setuptex.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2010, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+from sdaps import model
+from sdaps import script
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+
+parser = script.subparsers.add_parser("setup_tex",
+    help=_("Create a new survey using a LaTeX document."),
+    description=_("""Create a new survey from a LaTeX document. You need to
+    be using the SDAPS class. All the metadata and options for the project
+    can be set inside the LaTeX document."""))
+
+parser.add_argument('questionnaire.tex',
+    help=_("The LaTeX Document"))
+parser.add_argument('-a', '--add',
+    help=_("Additional files that are required by the LaTeX document and need to be copied into the project directory."),
+    action='append', default=[])
+parser.add_argument('additional_questions',
+    nargs='?',
+    help=_("Additional questions that are not part of the questionnaire."))
+
+ at script.connect(parser)
+def setup_tex(cmdline):
+    from sdaps import setuptex
+
+    survey = model.survey.Survey.new(cmdline['project'])
+
+    return setuptex.setup(survey, cmdline['questionnaire.tex'], cmdline['additional_questions'], cmdline['add'])
+
+
diff --git a/sdaps/cmdline/stamp.py b/sdaps/cmdline/stamp.py
new file mode 100644
index 0000000..b72a56a
--- /dev/null
+++ b/sdaps/cmdline/stamp.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+from sdaps import model
+from sdaps import script
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+parser = script.subparsers.add_parser("stamp",
+    help=_("Add marks for automatic processing."),
+    description=_("""This command creates the printable document. Depending on
+    the projects setting you are required to specifiy a source for questionnaire
+    IDs."""))
+
+parser.add_argument('-r', '--random',
+    metavar="N",
+    help=_("If using questionnaire IDs, create N questionnaires with randomized IDs."),
+    type=int)
+parser.add_argument('-f', '--file',
+    help=_("If using questionnaire IDs, create questionnaires from the IDs read from the specified file."))
+parser.add_argument('--existing',
+    action="store_true",
+    help=_("If using questionnaire IDs, create questionnaires for all stored IDs."))
+
+parser.add_argument('-o', '--output',
+    help=_("Filename to store the data to (default: stamp_%%i.pdf)"))
+
+ at script.connect(parser)
+ at script.logfile
+def stamp(cmdline):
+    from sdaps import stamp
+
+    survey = model.survey.Survey.load(cmdline['project'])
+
+    if cmdline['output'] is None:
+        output = survey.new_path('stamped_%i.pdf')
+    else:
+        output = cmdline['output']
+
+    return stamp.stamp(survey, output, cmdline)
+
+
diff --git a/sdaps/convert/__init__.py b/sdaps/convert/__init__.py
new file mode 100644
index 0000000..af0e0bd
--- /dev/null
+++ b/sdaps/convert/__init__.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2013, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+from sdaps import image
+from sdaps.utils import opencv
+from sdaps import log
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+def convert_images(images, outfile, paper_width, paper_height, transform=False):
+
+    portrait = paper_height >= paper_width
+
+    for i, (img, filename, page) in enumerate(opencv.iter_images_and_pages(images)):
+        img = opencv.ensure_orientation(img, portrait)
+        img = opencv.sharpen(img)
+
+        if transform:
+            try:
+                img = opencv.transform_using_corners(img, paper_width, paper_height)
+            except AssertionError:
+                log.warning(_("Could not apply 3D-transformation to image '%s', page %i!") % (filename, page))
+
+        mono = opencv.convert_to_monochrome(img)
+        image.write_a1_to_tiff(outfile, opencv.to_a1_surf(mono))
+
+
diff --git a/sdaps/cover/__init__.py b/sdaps/cover/__init__.py
new file mode 100644
index 0000000..b6c4cce
--- /dev/null
+++ b/sdaps/cover/__init__.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+from sdaps import template
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+
+def cover(survey, output=None):
+
+    story = template.story_title(survey)
+    subject = []
+    for key, value in survey.info.iteritems():
+        subject.append(u'%(key)s: %(value)s' % {'key': key, 'value': value})
+    subject = u'\n'.join(subject)
+
+    if output:
+        filename = output
+    else:
+        filename = survey.new_path('cover_%i.pdf')
+
+    doc = template.DocTemplate(
+        filename,
+        _(u'sdaps questionnaire'),
+        {
+            'title': survey.title,
+            'subject': subject
+        }
+    )
+    doc.build(story)
+
+
diff --git a/sdaps/csvdata/__init__.py b/sdaps/csvdata/__init__.py
new file mode 100644
index 0000000..e4be368
--- /dev/null
+++ b/sdaps/csvdata/__init__.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import csv
+
+from sdaps import clifilter
+from sdaps import model
+
+from . import buddies
+
+
+def csvdata_export(survey, csvfile, image_writer, filter=None, export_images=False, export_question_images=False, export_quality=False, csvoptions={}):
+    # compile clifilter
+    filter = clifilter.clifilter(survey, filter)
+
+    # export
+    survey.questionnaire.csvdata.open_csv(csvfile, image_writer, export_images, export_question_images, export_quality, csvoptions)
+
+    survey.iterate(
+        survey.questionnaire.csvdata.export_data,
+        filter,
+    )
+
+    survey.questionnaire.csvdata.export_finish()
+
+
+def csvdata_import(survey, csvfile):
+    csvreader = csv.DictReader(csvfile)
+
+    for data in csvreader:
+        survey.questionnaire.csvdata.import_data(data)
+
+    csvfile.close()
+
+    survey.save()
+
+
diff --git a/sdaps/csvdata/buddies.py b/sdaps/csvdata/buddies.py
new file mode 100644
index 0000000..4068734
--- /dev/null
+++ b/sdaps/csvdata/buddies.py
@@ -0,0 +1,259 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import csv
+
+from sdaps import model
+
+import os
+import os.path
+
+class Questionnaire(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'csvdata'
+    obj_class = model.questionnaire.Questionnaire
+
+    def open_csv(self, csvfile, image_writer=None, export_images=False, export_question_images=False, export_quality=False, csv_options={}):
+        header = ['questionnaire_id', 'global_id']
+
+        self.export_quality = export_quality
+
+        self.image_writer = image_writer
+        if image_writer is not None and (export_images or export_question_images):
+            from sdaps.utils.image import ImageWriter
+
+            self.export_images = export_images
+            self.export_question_images = export_question_images
+
+        else:
+            self.export_images = None
+            self.export_question_images = None
+
+        for qobject in self.obj.qobjects:
+            header.extend(qobject.csvdata.export_header())
+        self.file = csvfile
+        self.csv = csv.DictWriter(self.file, header, **csv_options)
+        self.csv.writerow({value: value for value in header})
+
+    def export_data(self):
+        data = {'questionnaire_id': unicode(self.obj.sheet.questionnaire_id),
+            'global_id': unicode(self.obj.sheet.global_id)}
+        for qobject in self.obj.qobjects:
+            data.update(qobject.csvdata.export_data())
+        self.csv.writerow(data)
+
+    def export_finish(self):
+        del self.csv
+
+    def import_data(self, data):
+        try:
+            self.obj.survey.goto_questionnaire_id(data['questionnaire_id'])
+        except ValueError:
+            # The sheet does not exist
+            # Ignore it
+            pass
+        else:
+            for qobject in self.obj.qobjects:
+                qobject.csvdata.import_data(data)
+
+
+class QObject(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'csvdata'
+    obj_class = model.questionnaire.QObject
+
+    def export_header(self):
+        if self.obj.questionnaire.csvdata.export_question_images:
+            return [self.obj.id_csv(self.obj.id) + '_image']
+        else:
+            return []
+
+    def export_data(self):
+        data = {}
+        if self.obj.questionnaire.csvdata.export_question_images:
+            if self.obj.boxes:
+                img = self.obj.questionnaire.csvdata.image_writer.output_boxes(self.obj.boxes, real=False)
+                data[self.obj.id_csv() + '_image'] = img
+
+        return data
+
+    def import_data(self, data):
+        pass
+
+class QHead(QObject):
+
+    __metaclass__ = model.buddy.Register
+    name = 'csvdata'
+    obj_class = model.questionnaire.Head
+
+    def export_header(self):
+        return []
+
+    def export_data(self):
+        return {}
+
+class Choice(QObject):
+
+    __metaclass__ = model.buddy.Register
+    name = 'csvdata'
+    obj_class = model.questionnaire.Choice
+
+    def export_header(self):
+        header = QObject.export_header(self)
+        for box in self.obj.boxes:
+            header += box.csvdata.export_header()
+
+        return header
+
+    def export_data(self):
+        data = QObject.export_data(self)
+
+        for box in self.obj.boxes:
+            data.update(box.csvdata.export_data())
+
+        return data
+
+    def import_data(self, data):
+        for box in self.obj.boxes:
+            if self.obj.id_csv(box.id) in data:
+                box.csvdata.import_data(data[self.obj.id_csv(box.id)])
+
+class Text(QObject):
+
+    __metaclass__ = model.buddy.Register
+    name = 'csvdata'
+    obj_class = model.questionnaire.Text
+
+    def export_header(self):
+        header = QObject.export_header(self)
+        for box in self.obj.boxes:
+            header += box.csvdata.export_header()
+
+        return header
+
+    def export_data(self):
+        data = QObject.export_data(self)
+
+        for box in self.obj.boxes:
+            data.update(box.csvdata.export_data())
+
+        return data
+
+    def import_data(self, data):
+        for box in self.obj.boxes:
+            if self.obj.id_csv(box.id) in data:
+                box.csvdata.import_data(data[self.obj.id_csv(box.id)])
+
+class Mark(QObject):
+
+    __metaclass__ = model.buddy.Register
+    name = 'csvdata'
+    obj_class = model.questionnaire.Mark
+
+    def export_header(self):
+        header = QObject.export_header(self)
+        header += [self.obj.id_csv()]
+        return header
+
+    def export_data(self):
+        data = {self.obj.id_csv(): '%i' % self.obj.get_answer()}
+        data.update(QObject.export_data(self))
+        return data
+
+    def import_data(self, data):
+        if self.obj.id_csv() in data:
+            self.obj.set_answer(int(data[self.obj.id_csv()]))
+
+
+class Additional_Mark(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'csvdata'
+    obj_class = model.questionnaire.Additional_Mark
+
+    def export_header(self):
+        return [self.obj.id_csv()]
+
+    def export_data(self):
+        return {self.obj.id_csv(): '%i' % self.obj.get_answer()}
+
+    def import_data(self, data):
+        if self.obj.id_csv() in data:
+            self.obj.set_answer(int(data[self.obj.id_csv()]))
+
+
+class Box(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'csvdata'
+    obj_class = model.questionnaire.Box
+
+    def export_header(self):
+        header = [self.obj.id_csv()]
+        if self.obj.question.questionnaire.csvdata.export_quality:
+            header += [self.obj.id_csv() + '_quality']
+        return header
+
+    def export_data(self):
+        data = {self.obj.id_csv() : str(int(self.obj.data.state))}
+        if self.obj.question.questionnaire.csvdata.export_quality:
+            data.update({self.obj.id_csv() + '_quality' : self.obj.data.quality})
+        return data
+
+    def import_data(self, data):
+        self.obj.data.state = int(data)
+
+
+class Textbox(Box):
+
+    __metaclass__ = model.buddy.Register
+    name = 'csvdata'
+    obj_class = model.questionnaire.Textbox
+
+    def export_header(self):
+        header = [self.obj.id_csv()]
+        return header
+
+    def export_data(self):
+        data = str(int(self.obj.data.state))
+
+        image_writer = self.obj.question.questionnaire.csvdata.image_writer
+
+        if self.obj.data.state and self.obj.data.text:
+            data = self.obj.data.text.encode('utf-8')
+        elif self.obj.data.state and self.obj.question.questionnaire.csvdata.export_images:
+            data = image_writer.output_box(self.obj)
+
+        data = { self.obj.id_csv() : data }
+
+        return data
+
+    def import_data(self, data):
+        try:
+            state = int(data)
+            text = u''
+        except ValueError:
+            state = 1
+            text = unicode(data)
+
+        self.obj.data.state = state
+        self.obj.data.text = text
+
+
diff --git a/sdaps/defs.py b/sdaps/defs.py
new file mode 100644
index 0000000..24205de
--- /dev/null
+++ b/sdaps/defs.py
@@ -0,0 +1,201 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+u"""
+Defs
+====
+
+This module contains constants and some magic values.
+"""
+
+
+# Corner Marks ============================================
+
+# The position of the corner markings from the side of the paper
+corner_mark_left = 10.0 # mm
+corner_mark_right = 10.0 # mm
+corner_mark_top = 12.0 # mm
+corner_mark_bottom = 12.0 # mm
+corner_mark_length = 20.0 # mm
+
+# Bottom page margin (for QR-Code size calculation in fallback rendering)
+bottom_page_margin = 25.0 # mm
+
+# Length in mm of the corner marks in the scanned image
+corner_mark_min_length = 15 # mm
+corner_mark_max_length = 22 # mm
+
+# The distance into the image that will be searched for the corner mark.
+# Basically a square area of this size will be searched in each corner.
+corner_mark_search_distance = 50 # mm
+
+# Corner Boxes ============================================
+
+# What corners are filled for each page, this is choosen so that
+# the page number and rotation can be derived from the information.
+# Order: top left, top right, bottom left, bottom right
+corner_boxes = [
+    [0, 1, 1, 1],
+    [1, 1, 0, 0],
+    [1, 0, 1, 1],
+    [1, 0, 1, 0],
+    [1, 0, 0, 0],
+    [0, 0, 0, 1],
+]
+
+# Size of the corner boxes(measured from the center of the line)
+corner_box_width = 3.5 # mm
+corner_box_height = 3.5 # mm
+# Distance from the corner marker(and also the distance to the codebox)
+corner_box_padding = 3 # mm
+
+# The coverage above which a cornerbox is considered to be a logical 1
+cornerbox_on_coverage = 0.6
+
+
+# Codebox =================================================
+
+# Length in bits
+codebox_length = 16 # bits
+codebox_step = 3.5 # mm
+codebox_width = codebox_step * codebox_length # mm
+codebox_height = 3.5 # mm
+# Padding that is ignored when testing the image whether it is 1/0
+codebox_offset = 0.75 # mm
+
+# The font and size of the font for the text that is printed between the codeboxes
+codebox_text_font = 'Courier-Bold'
+codebox_text_font_size = 10.5 # pt
+# This is added to the vertical position to shift the text visually into the center
+codebox_text_baseline_shift = 3 # mm
+
+# Magic values for recognition
+# The coverage above which a codebox is considered to be a logical 1
+codebox_on_coverage = 0.7
+
+# Code 128 Barcodes =======================================
+
+code128_barwidth = 0.33 # mm. This is 0.93pt or 3.89px after scanning
+code128_height = 6.5 # pt(5 mm)
+code128_hpad = 6.5 # mm
+code128_vpad = 4.02 # mm
+
+code128_text_font = 'Courier'
+code128_text_font_size = 9 # pt
+
+# Checkbox ================================================
+
+checkbox_metrics = {}
+
+# The metrics is a mapping from the value to the quality and expected
+# checkbox state.
+# It works by searching the interval that we are in, and doing
+# a linear interpolation.
+# At state changes the point should be inserted twice.
+# The touple is(metric, state, quality). To disable one you can just
+# insert two dummy points with zero quality. To try and find better
+# values have a look at the output of "boxgallery". Any suggestions
+# for improvements(also algorithmic wise) are always welcome!
+checkbox_metrics['checkcorrect'] = {}
+checkbox_metrics['checkcorrect']['coverage'] = \
+    [(0, 0, 1.0), (0.02, 0, 0.9), (0.05, 0, 0.3), (0.05, 1, 0.3),
+     (0.1, 1, 1.0), (0.4, 1, 1.0), (0.5, 1, 0.2), (0.5, 0, 0.2),
+     (0.7, 0, 0.3), (1.0, 0, 0.6)]
+checkbox_metrics['checkcorrect']['cov-lines-removed'] = \
+    [(0, 1, 0), (0.01, 1, 0), (0.07, 1, 1.0), (0.10, 1, 1.0),
+     (0.13, 1, 0.3), (0.13, 0, 0.3), (0.25, 0, 0.7), (1, 0, 0.7)]
+checkbox_metrics['checkcorrect']['cov-min-size'] = \
+    [(0, 0, .9), (0.35, 0, 0.0), (0.35, 1, 0.0), (0.5, 1, 0.9),
+     (0.55, 1, 1.0), (0.8, 1, 1.0), (0.9, 1, 0.9), (0.95, 1, 0.5),
+     (0.95, 0, 0.5), (0.99, 0, 0.9), (1.0, 0, 1.0)]
+
+# Same as above, but no correction mode. A light check is enough.
+checkbox_metrics['check'] = {}
+checkbox_metrics['check']['coverage'] = \
+    [(0, 0, 1.0), (0.02, 0, 0.9), (0.05, 0, 0.3), (0.05, 1, 0.3),
+     (0.1, 1, 1.0), (1.0, 0, 1.0)]
+checkbox_metrics['check']['cov-lines-removed'] = \
+    [(0, 1, 0), (1, 1, 0)]
+checkbox_metrics['check']['cov-min-size'] = \
+    [(0, 1, 0), (1, 1, 0)]
+
+# Similar, but a light checkmark, or just dirt will be ignored.
+checkbox_metrics['fill'] = {}
+checkbox_metrics['fill']['coverage'] = \
+    [(0, 0, 1.0), (0.2, 0, 0.9), (0.3, 0, 0), (0.3, 1, 0),
+     (0.4, 1, 1.0), (1.0, 1, 1.0)]
+checkbox_metrics['fill']['cov-lines-removed'] = \
+    [(0, 1, 0), (1, 1, 0)]
+checkbox_metrics['fill']['cov-min-size'] = \
+    [(0, 1, 0), (1, 1, 0)]
+
+
+# Textbox =================================================
+
+# Tolerance for the text box corner adjustment. If the difference is more than
+# this in mm, the adjusted values will be discarded.
+find_box_corners_tolerance = 1.5 # mm
+
+# The size in mm of the area that is scanned during checking of textboxes. The step
+# is the x/y distance in mm of each checked area. They overlap on purpose.
+textbox_scan_step_x = 1.0 # mm
+textbox_scan_step_y = 1.0 # mm
+textbox_scan_width = 2.0 # mm
+textbox_scan_height = 2.0 # mm
+
+# The pixel coverage of the scan area that is required for it to
+# be considered writing.
+textbox_scan_coverage = 0.06
+
+# Minimum size in mm for a textbox to be considered filled in.
+# This is usefull because otherwise small dirt dots will be considered writing.
+textbox_minimum_writing_width = 5 # mm
+textbox_minimum_writing_height = 5 # mm
+
+# Distance to stay away from the outline in mm so that it will not be detected
+# as handwriting. The "uncorrected" value is used when the corners have not been
+# detected correctly, which should hopefully never happen.
+textbox_scan_padding = 0.5 # mm
+textbox_scan_uncorrected_padding = 1.5 # mm
+
+# Padding tha will always be added to the textbox size if content has been recognized.
+# This is important because we want the outline to be visible if someone wrote
+# over the outline(so that the reader can see that the software worked correctly).
+textbox_extra_padding = 0.5 # mm
+
+
+# Image ===================================================
+
+# The width of the lines in the scanned image.
+# All lines are 1pt wide(1/72 inch or 25.4/72 mm)
+image_line_width = 24.4/72. # mm
+# The coverage that the line needs to have for recognition
+image_line_coverage = 0.35
+
+
+# Allowed characters in code 128 barcodes (only ascii for now)
+c128_chars = [chr(i) for i in xrange(32, 127)] #+ [u'È', u'É', u'Ê', u'Ë', u'Ì', u'Í', u'Î', u'Ï', u'Ð', u'Ñ', u'Ò', u'Ó']
+
+
+# External commands =======================================
+#: The binary used to compile latex documents.
+latex_engine = "pdflatex"
+
+#: A function that is called after fork and before exec of the latex engine.
+#: This is useful when e.g. the LateX environment should be secured.
+latex_preexec_hook = None
diff --git a/sdaps/gui/__init__.py b/sdaps/gui/__init__.py
new file mode 100644
index 0000000..2694aec
--- /dev/null
+++ b/sdaps/gui/__init__.py
@@ -0,0 +1,471 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2007-2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2007-2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+u"""
+This modules contains a GTK+ user interface. With the help of this GUI it is
+possible to manually correct the automatic recognition and do basic debugging.
+"""
+
+
+from gi.repository import GObject
+from gi.repository import GLib
+from gi.repository import Gtk
+from gi.repository import Gdk
+import os
+import time
+import sys
+import signal
+
+from sdaps import model
+from sdaps import surface
+from sdaps import clifilter
+from sdaps import defs
+from sdaps import paths
+from sdaps import log
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+from .sheet_widget import SheetWidget
+from . import buddies
+from . import widget_buddies
+
+
+zoom_steps = [0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0,
+              1.25, 1.5, 2.0, 2.5, 3.0]
+
+
+def gui(survey, cmdline):
+    filter = clifilter.clifilter(survey, cmdline['filter'])
+    provider = Provider(survey, filter)
+    if not provider.images:
+        log.error(_("The survey does not have any images! Please add images (and run recognize) before using the GUI."))
+        return 1
+
+    try:
+        # Exit the mainloop if Ctrl+C is pressed in the terminal.
+        GLib.unix_signal_add_full(GLib.PRIORITY_HIGH, signal.SIGINT, lambda *args : Gtk.main_quit(), None)
+    except AttributeError:
+        # Whatever, it is only to enable Ctrl+C anyways
+        pass
+
+    MainWindow(provider).run()
+
+
+class Provider(object):
+
+    def __init__(self, survey, filter, by_quality=False):
+        self._by_quality = by_quality
+
+        self.survey = survey
+        self.images = list()
+        self.qualities = list()
+        self.survey.iterate(self, filter)
+        self.qualities.sort(reverse=False)
+        self.index = 0
+
+        # There may be no images. This error is
+        # caught and printed in the "gui" function.
+        if not self.images:
+            return
+
+        self.image.surface.load_rgb()
+        self.survey.goto_sheet(self.image.sheet)
+        #self._surface = None
+
+    def __call__(self):
+        # Add all images that are "valid" ie. everything except back side of
+        # a simplex printout
+        new_images = [img for img in sorted(self.survey.sheet.images, key=lambda Image: Image.page_number) if not img.ignored]
+
+        self.images.extend(new_images)
+        # Insert each image of the sheet into the qualities array
+        for i in xrange(len(new_images)):
+            self.qualities.append((self.survey.sheet.quality, len(self.qualities)))
+
+    def next(self, cycle=True):
+        if self.index >= len(self.images) - 1:
+            if cycle:
+                new_index = 0
+            else:
+                return False
+        else:
+            new_index = self.index + 1
+
+        self.image.surface.clean()
+
+        self.index = new_index
+
+        self.image.surface.load_rgb()
+        self.survey.goto_sheet(self.image.sheet)
+
+        return True
+
+    def previous(self, cycle=True):
+        if self.index <= 0:
+            if cycle:
+                new_index = len(self.images) - 1
+            else:
+                return False
+        else:
+            new_index = self.index - 1
+
+        self.image.surface.clean()
+
+        self.index = new_index
+
+        self.image.surface.load_rgb()
+        self.survey.goto_sheet(self.image.sheet)
+
+        return True
+
+    def goto(self, index):
+        if index >= 0 and index < len(self.images):
+            self.image.surface.clean()
+            self.index = index
+            self.image.surface.load_rgb()
+            self.survey.goto_sheet(self.image.sheet)
+
+    def set_sort_by_quality(self, value):
+        self.image.surface.clean()
+        self._by_quality = value
+        self.image.surface.load_rgb()
+        self.survey.goto_sheet(self.image.sheet)
+
+    def get_image(self):
+        if self._by_quality:
+            return self.images[self.qualities[self.index][1]]
+        else:
+            return self.images[self.index]
+
+    image = property(get_image)
+
+
+class MainWindow(object):
+
+    def __init__(self, provider):
+        self.about_dialog = None
+        self.close_dialog = None
+        self.ask_open_dialog = None
+        self.provider = provider
+
+        self._load_image = 0
+        self._builder = Gtk.Builder()
+        if paths.local_run:
+            self._builder.add_from_file(
+                os.path.join(os.path.dirname(__file__), 'main_window.ui'))
+        else:
+            self._builder.add_from_file(
+                os.path.join(
+                    paths.prefix,
+                    'share', 'sdaps', 'ui', 'main_window.ui'))
+
+        self._window = self._builder.get_object("main_window")
+        self._builder.connect_signals(self)
+        self._window.maximize()
+
+        scrolled_window = self._builder.get_object("sheet_scrolled_window")
+        self.sheet = SheetWidget(self.provider)
+        self.sheet.show()
+        scrolled_window.add(self.sheet)
+
+        self.data_viewport = self._builder.get_object("data_view")
+        widgets = provider.survey.questionnaire.widget.create_widget()
+        widgets.show_all()
+        self.data_viewport.add(widgets)
+
+        provider.survey.questionnaire.widget.connect_ensure_visible(self.data_view_ensure_visible)
+
+        self.sheet.connect('key-press-event', self.sheet_view_key_press)
+
+        combo = self._builder.get_object("page_number_combo")
+        cell = Gtk.CellRendererText()
+        combo.pack_start(cell, True)
+        combo.add_attribute(cell, 'text', 0)
+
+        store = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_INT)
+        store.append(row=(_("Page|Invalid"), -1))
+        for i in range(self.provider.survey.questionnaire.page_count):
+            store.append(row=(
+                ungettext("Page %i", "Page %i", i + 1) % (i + 1), i + 1))
+
+        combo.set_model(store)
+
+        self.sheet.props.zoom = 0.2
+
+        # So the buttons are insensitive
+        self.update_ui()
+
+    def zoom_in(self, *args):
+        cur_zoom = self.sheet.props.zoom
+        try:
+            i = zoom_steps.index(cur_zoom)
+            i += 1
+            if i < len(zoom_steps):
+                self.sheet.props.zoom = zoom_steps[i]
+        except:
+            self.sheet.props.zoom = 1.0
+
+    def zoom_out(self, *args):
+        cur_zoom = self.sheet.props.zoom
+        try:
+            i = zoom_steps.index(cur_zoom)
+            i -= 1
+            if i >= 0:
+                self.sheet.props.zoom = zoom_steps[i]
+        except:
+            self.sheet.props.zoom = 1.0
+
+    def null_event_handler(self, *args):
+        return True
+
+    def show_about_dialog(self, *args):
+        if not self.about_dialog:
+            self.about_dialog = Gtk.AboutDialog()
+            self.about_dialog.set_program_name("SDAPS")
+            #self.about_dialog.set_version("")
+            self.about_dialog.set_authors(
+                [u"Benjamin Berg <benjamin at sipsolution.net>",
+                 u"Ferdinand Schwenk <ferdisdot at gmail.com>",
+                 u"Christoph Simon <post at christoph-simon.eu>",
+                 u"Tobias Simon <tobsimon at googlemail.com>"])
+            self.about_dialog.set_copyright(_(u"Copyright © 2007-2014 The SDAPS Authors"))
+            self.about_dialog.set_license_type(Gtk.License.GPL_3_0)
+            self.about_dialog.set_comments(_(u"Scripts for data acquisition with paper based surveys"))
+            self.about_dialog.set_website(_(u"http://sdaps.org"))
+            self.about_dialog.set_translator_credits(_("translator-credits"))
+            self.about_dialog.set_default_response(Gtk.ResponseType.CANCEL)
+
+        self.about_dialog.set_transient_for(self._window)
+        self.about_dialog.run()
+        self.about_dialog.hide()
+
+        return True
+
+    def update_page_status(self):
+        combo = self._builder.get_object("page_number_combo")
+        turned_toggle = self._builder.get_object("turned_toggle")
+
+        # Update the combobox
+        if self.provider.image.survey_id == self.provider.survey.survey_id:
+            page_number = self.provider.image.page_number
+        else:
+            page_number = -1
+
+        # Find the page_number in the model
+        model = combo.get_model()
+        iter = model.get_iter_first()
+
+        while iter:
+            i = combo.get_model().get(iter, 1)[0]
+            if page_number == i:
+                combo.set_active_iter(iter)
+                iter = None
+            else:
+                iter = model.iter_next(iter)
+
+        # Update the toggle
+        turned_toggle.set_active(self.provider.image.rotated or False)
+
+    def update_ui(self):
+        # Update the next/prev button states
+        #next_button = self._builder.get_object("forward_toolbutton")
+        #prev_button = self._builder.get_object("backward_toolbutton")
+
+        #next_button.set_sensitive(True)
+        #prev_button.set_sensitive(True)
+
+        position_label = self._builder.get_object("position_label")
+        quality_label = self._builder.get_object("quality_label")
+        page_spin = self._builder.get_object("page_spin")
+        position_label.set_text(_(u" of %i") % len(self.provider.images))
+        quality_label.set_text(_(u"Recognition Quality: %.2f") % self.provider.image.sheet.quality)
+        #position_label.props.sensitive = True
+        page_spin.set_range(1, len(self.provider.images))
+        page_spin.set_value(self.provider.index + 1)
+
+        self.update_page_status()
+        self.sheet.update_state()
+
+        self.provider.survey.questionnaire.widget.sync_state()
+
+    def go_to_previous_page(self, *args):
+        if not self.provider.previous(cycle=False):
+            dialog = Gtk.MessageDialog(
+                flags=Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                type=Gtk.MessageType.INFO,
+                buttons=Gtk.ButtonsType.CANCEL,
+                message_format=_("You have reached the first page of the survey. Would you like to go to the last page?"))
+            dialog.set_transient_for(self._window)
+            dialog.add_button(_("Go to last page"), Gtk.ResponseType.OK)
+            if dialog.run() == Gtk.ResponseType.OK:
+                self.provider.previous(cycle=True)
+            dialog.destroy()
+        self.update_ui()
+        return True
+
+    def go_to_page(self, page):
+        if page == self.provider.index:
+            return True
+
+        self.provider.goto(int(page))
+
+        self.update_ui()
+        return True
+
+    def go_to_next_page(self, *args):
+        if not self.provider.next(cycle=False):
+            dialog = Gtk.MessageDialog(
+                flags=Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                type=Gtk.MessageType.INFO,
+                buttons=Gtk.ButtonsType.CANCEL,
+                message_format=_("You have reached the last page of the survey. Would you like to go to the first page?"))
+            dialog.set_transient_for(self._window)
+            dialog.add_button(_("Go to first page"), Gtk.ResponseType.OK)
+            if dialog.run() == Gtk.ResponseType.OK:
+                self.provider.next(cycle=True)
+            dialog.destroy()
+
+        self.update_ui()
+        return True
+
+    def page_spin_value_changed_cb(self, *args):
+        page_spin = self._builder.get_object("page_spin")
+        page = page_spin.get_value() - 1
+        self.go_to_page(page)
+
+    def page_number_combo_changed_cb(self, *args):
+        combo = self._builder.get_object("page_number_combo")
+        active = combo.get_active_iter()
+        page_number = combo.get_model().get(active, 1)[0]
+        if self.provider.image.page_number != page_number:
+            if page_number != -1:
+                self.provider.image.page_number = page_number
+                self.provider.image.survey_id = self.provider.survey.survey_id
+            else:
+                self.provider.image.page_number = None
+                self.provider.image.survey_id = None
+            self.update_ui()
+            return False
+
+    def turned_toggle_toggled_cb(self, *args):
+        toggle = self._builder.get_object("turned_toggle")
+        rotated = toggle.get_active()
+        if self.provider.image.rotated != rotated:
+            self.provider.image.rotated = rotated
+            self.provider.image.surface.load_rgb()
+            self.update_ui()
+        return False
+
+    def quality_sort_toggle_toggled_cb(self, *args):
+        toggle = self._builder.get_object("quality_sort_toggle")
+        self.provider.set_sort_by_quality(toggle.get_active())
+        self.update_ui()
+        return False
+
+    def toggle_fullscreen(self, *args):
+        flags = self._window.get_window().get_state()
+        if flags & Gdk.WindowState.FULLSCREEN:
+            self._window.unfullscreen()
+        else:
+            self._window.fullscreen()
+        return True
+
+    def save_project(self, *args):
+        self.provider.survey.save()
+        return True
+
+    def data_view_ensure_visible(self, widget):
+        allocation = widget.get_allocation()
+
+        vadj = self.data_viewport.props.vadjustment
+        lower = vadj.props.lower
+        upper = vadj.props.upper
+        value = vadj.props.value
+        page_size = vadj.props.page_size
+
+        value = max(value, allocation.y + allocation.height - page_size)
+        value = min(value, allocation.y)
+        value = max(value, lower)
+        value = min(value, upper)
+
+        vadj.props.value = value
+
+    def sheet_view_key_press(self, window, event):
+        # Go to the next when Enter or Tab is pressed
+        enter_keyvals = [Gdk.keyval_from_name(k) for k in ["Return", "KP_Enter", "ISO_Enter"]]
+        if event.keyval in enter_keyvals:
+            # If "Return" is pressed, then the examiner figured that the data
+            # is good.
+
+            # Mark as verified
+            self.provider.image.sheet.verified = True
+
+            # Mark the sheet as valid
+            self.provider.image.sheet.valid = True
+
+            if event.state & Gdk.ModifierType.SHIFT_MASK:
+                self.go_to_previous_page()
+            else:
+                self.go_to_next_page()
+            return True
+        elif event.keyval == Gdk.KEY_Tab or event.keyval == Gdk.KEY_KP_Tab or event.keyval == Gdk.KEY_ISO_Left_Tab:
+            # Allow tabbing out with Ctrl
+            if event.state & Gdk.ModifierType.CONTROL_MASK:
+                return False
+
+            if event.state & Gdk.ModifierType.SHIFT_MASK:
+                self.go_to_previous_page()
+            else:
+                self.go_to_next_page()
+            return True
+
+        return False
+
+    def quit_application(self, *args):
+        if not self.close_dialog:
+            self.close_dialog = Gtk.MessageDialog(
+                parent=self._window,
+                flags=Gtk.DialogFlags.MODAL,
+                type=Gtk.MessageType.WARNING)
+            self.close_dialog.add_buttons(
+                _(u"Close without saving"), Gtk.ResponseType.CLOSE,
+                Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
+                Gtk.STOCK_SAVE, Gtk.ResponseType.OK)
+            self.close_dialog.set_markup(
+                _(u"<b>Save the project before closing?</b>\n\nIf you do not save you may loose data."))
+            self.close_dialog.set_default_response(Gtk.ResponseType.CANCEL)
+
+        response = self.close_dialog.run()
+        self.close_dialog.hide()
+
+        if response == Gtk.ResponseType.CLOSE:
+            Gtk.main_quit()
+            return False
+        elif response == Gtk.ResponseType.OK:
+            self.save_project()
+            Gtk.main_quit()
+            return False
+        else:
+            return True
+
+    def run(self):
+        self._window.show()
+        Gtk.main()
+
+
diff --git a/sdaps/gui/buddies.py b/sdaps/gui/buddies.py
new file mode 100644
index 0000000..bf2daba
--- /dev/null
+++ b/sdaps/gui/buddies.py
@@ -0,0 +1,297 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import cairo
+import math
+
+from sdaps import model
+from sdaps import defs
+
+
+LINE_WIDTH = 25.4/72
+MIN_FREETEXT_SIZE = 4.0
+
+_LEFT = 1
+_RIGHT = 2
+_TOP = 3
+_BOTTOM = 4
+
+
+def inner_box(cr, x, y, width, height):
+    line_width = cr.get_line_width()
+
+    cr.rectangle(x + line_width / 2.0, y + line_width / 2.0,
+                 width - line_width, height - line_width)
+
+def ellipse(cr, x, y, width, height):
+    cr.save()
+
+    cr.translate(x + width / 2.0, y + height / 2.0)
+
+    line_width = cr.get_line_width()
+
+    cr.scale(width / 2.0, height / 2.0)
+    cr.arc(0, 0, 1.0, 0, 2*math.pi)
+    cr.close_path()
+
+    # Restore old matrix (without removing the current path)
+    cr.restore()
+
+def inner_ellipse(cr, x, y, width, height):
+    cr.save()
+
+    cr.translate(x + width / 2.0, y + height / 2.0)
+
+    line_width = cr.get_line_width()
+
+    cr.scale((width - line_width) / 2.0, (height - line_width) / 2.0)
+    cr.arc(0, 0, 1.0, 0, 2*math.pi)
+    cr.close_path()
+
+    # Restore old matrix (without removing the current path)
+    cr.restore()
+
+class Questionnaire(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'gui'
+    obj_class = model.questionnaire.Questionnaire
+
+    def draw(self, cr, page_number):
+        for qobject in self.obj.qobjects:
+            qobject.gui.draw(cr, page_number)
+
+    def find_box(self, page_number, x, y):
+        for qobject in self.obj.qobjects:
+            result = qobject.gui.find_box(page_number, x, y)
+            if result:
+                return result
+        return None
+
+    def find_edge(self, page_number, x, y, tollerance_x, tollerance_y):
+        for qobject in self.obj.qobjects:
+            result = qobject.gui.find_edge(page_number, x, y, tollerance_x, tollerance_y)
+            if result:
+                return result
+        return None
+
+
+class QObject(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'gui'
+    obj_class = model.questionnaire.QObject
+
+    def draw(self, cr, page_number):
+        pass
+
+    def find_box(self, page_number, x, y):
+        pass
+
+    def find_edge(self, page_number, x, y, tollerance_x, tollerance_y):
+        return None
+
+
+class Question(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'gui'
+    obj_class = model.questionnaire.Question
+
+    def draw(self, cr, page_number):
+        # Does the sheet contain this question?
+        if page_number == self.obj.page_number:
+            # iterate over boxes
+            for box in self.obj.boxes:
+                box.gui.draw(cr)
+
+    def find_box(self, page_number, x, y):
+        # Does the sheet contain this question?
+        if page_number == self.obj.page_number:
+            # iterate over boxes
+            for box in self.obj.boxes:
+                result = box.gui.find(x, y)
+                if result is not None:
+                    return result
+        return None
+
+    def find_edge(self, page_number, x, y, tollerance_x, tollerance_y):
+        # Does the sheet contain this question?
+        if page_number == self.obj.page_number:
+            # iterate over boxes
+            for box in self.obj.boxes:
+                result = box.gui.find_edge(x, y, tollerance_x, tollerance_y)
+                if result is not None:
+                    return result
+        return None
+
+
+class Box(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'gui'
+    obj_class = model.questionnaire.Checkbox
+
+    def draw(self, cr):
+        cr.set_fill_rule(cairo.FILL_RULE_EVEN_ODD)
+
+        cr.set_source_rgba(0.57, 1.0, 0.0, 0.5)
+        cr.set_line_width(LINE_WIDTH)
+
+        inner_box(cr, self.obj.data.x, self.obj.data.y, self.obj.data.width, self.obj.data.height)
+        cr.stroke()
+
+    def find(self, x, y):
+        if self.obj.data.x < x < self.obj.data.x + self.obj.data.width:
+            if self.obj.data.y < y < self.obj.data.y + self.obj.data.height:
+                return self.obj
+        return None
+
+    def find_edge(self, x, y, tollerance_x, tollerance_y):
+        return None
+
+
+class Checkbox(Box):
+
+    __metaclass__ = model.buddy.Register
+    name = 'gui'
+    obj_class = model.questionnaire.Checkbox
+
+    def find(self, x, y):
+        if self.obj.data.x - 2*LINE_WIDTH < x < self.obj.data.x + self.obj.data.width + 2*LINE_WIDTH:
+            if self.obj.data.y - 2*LINE_WIDTH < y < self.obj.data.y + self.obj.data.height + 2*LINE_WIDTH:
+                return self.obj
+        return None
+
+
+    def draw(self, cr):
+        cr.save()
+
+        cr.set_fill_rule(cairo.FILL_RULE_EVEN_ODD)
+
+        if self.obj.data.quality < 0.5:
+            cr.save()
+            cr.set_line_width(2*LINE_WIDTH)
+            cr.set_source_rgba(1.0, 0.0, 0.2, 0.6)
+
+            if self.obj.form == "box":
+                inner_box(cr, self.obj.data.x - 4*LINE_WIDTH, self.obj.data.y - 4*LINE_WIDTH,  self.obj.data.width+8*LINE_WIDTH, self.obj.data.height+8*LINE_WIDTH)
+                cr.stroke()
+            elif self.obj.form == "ellipse":
+                inner_ellipse(cr, self.obj.data.x - 4*LINE_WIDTH, self.obj.data.y - 4*LINE_WIDTH,  self.obj.data.width+8*LINE_WIDTH, self.obj.data.height+8*LINE_WIDTH)
+                cr.stroke()
+
+            cr.restore()
+
+        cr.set_source_rgba(0.57, 1.0, 0.0, 0.5)
+        cr.set_line_width(LINE_WIDTH)
+
+        if self.obj.data.state:
+            if self.obj.form == "box":
+                cr.rectangle(self.obj.data.x - 2*LINE_WIDTH, self.obj.data.y - 2*LINE_WIDTH, self.obj.data.width + 4*LINE_WIDTH, self.obj.data.height + 4*LINE_WIDTH)
+                cr.fill()
+            elif self.obj.form == "ellipse":
+                ellipse(cr, self.obj.data.x - 2*LINE_WIDTH, self.obj.data.y - 2*LINE_WIDTH, self.obj.data.width + 4*LINE_WIDTH, self.obj.data.height + 4*LINE_WIDTH)
+                cr.fill()
+        else:
+            if self.obj.form == "box":
+                inner_box(cr, self.obj.data.x, self.obj.data.y, self.obj.data.width, self.obj.data.height)
+                cr.stroke()
+            elif self.obj.form == "ellipse":
+                inner_ellipse(cr, self.obj.data.x, self.obj.data.y, self.obj.data.width, self.obj.data.height)
+                cr.stroke()
+
+        cr.restore()
+
+
+class Textbox(Box):
+
+    __metaclass__ = model.buddy.Register
+    name = 'gui'
+    obj_class = model.questionnaire.Textbox
+
+    def draw(self, cr):
+        cr.save()
+
+        cr.set_fill_rule(cairo.FILL_RULE_EVEN_ODD)
+
+        cr.set_source_rgba(0.57, 1.0, 0.0, 0.5)
+
+        if self.obj.data.state:
+            cr.set_line_width(3 * LINE_WIDTH)
+        else:
+            cr.set_line_width(LINE_WIDTH)
+
+        inner_box(cr, self.obj.data.x, self.obj.data.y, self.obj.data.width, self.obj.data.height)
+        cr.stroke()
+
+        cr.restore()
+
+    def find_edge(self, x, y, tollerance_x, tollerance_y):
+        if self.obj.data.x - tollerance_x <= x and \
+           self.obj.data.x + tollerance_x >= x and \
+           self.obj.data.y <= y and \
+           self.obj.data.y + self.obj.data.height >= y:
+            return(self.obj, _LEFT)
+
+        if self.obj.data.x + self.obj.data.width - tollerance_x <= x and \
+           self.obj.data.x + self.obj.data.width + tollerance_x >= x and \
+           self.obj.data.y <= y and \
+           self.obj.data.y + self.obj.data.height >= y:
+            return(self.obj, _RIGHT)
+
+        if self.obj.data.y - tollerance_y <= y and \
+           self.obj.data.y + tollerance_y >= y and \
+           self.obj.data.x <= x and \
+           self.obj.data.x + self.obj.data.width >= x:
+            return(self.obj, _TOP)
+
+        if self.obj.data.y + self.obj.data.height - tollerance_y <= y and \
+           self.obj.data.y + self.obj.data.height + tollerance_y >= y and \
+           self.obj.data.x <= x and \
+           self.obj.data.x + self.obj.data.width >= x:
+            return(self.obj, _BOTTOM)
+
+    def move_edge(self, side, x, y):
+        if side == _LEFT:
+            x = max(x, defs.corner_mark_left)
+            new_width = max(MIN_FREETEXT_SIZE, self.obj.data.width + self.obj.data.x - x)
+            x = self.obj.data.x + (self.obj.data.width - new_width)
+
+            self.obj.data.width = new_width
+            self.obj.data.x = x
+        elif side == _RIGHT:
+            x = min(x, self.obj.question.questionnaire.survey.defs.paper_width - defs.corner_mark_right)
+            new_width = max(MIN_FREETEXT_SIZE, x - self.obj.data.x)
+            new_width = min(new_width, self.obj.data.x + self.obj.data.width)
+
+            self.obj.data.width = new_width
+        elif side == _TOP:
+            y = max(y, defs.corner_mark_top)
+            new_height = max(MIN_FREETEXT_SIZE, self.obj.data.height + self.obj.data.y - y)
+            new_y = self.obj.data.y + (self.obj.data.height - new_height)
+
+            self.obj.data.height = new_height
+            self.obj.data.y = new_y
+        elif side == _BOTTOM:
+            y = min(y, self.obj.question.questionnaire.survey.defs.paper_height - defs.corner_mark_bottom)
+            new_height = max(MIN_FREETEXT_SIZE, y - self.obj.data.y)
+
+            self.obj.data.height = new_height
+
+
diff --git a/sdaps/gui/main_window.ui b/sdaps/gui/main_window.ui
new file mode 100644
index 0000000..ae34ac5
--- /dev/null
+++ b/sdaps/gui/main_window.ui
@@ -0,0 +1,457 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface>
+  <requires lib="gtk+" version="3.0"/>
+  <object class="GtkAdjustment" id="adjustment1">
+    <property name="upper">100</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+  </object>
+  <object class="GtkAccelGroup" id="navigation_accel_group"/>
+  <object class="GtkActionGroup" id="navigation">
+    <property name="name">navigation</property>
+    <property name="accel_group">navigation_accel_group</property>
+    <child>
+      <object class="GtkAction" id="action-next-page">
+        <property name="label" translatable="yes">Forward</property>
+        <property name="stock_id">gtk-go-forward</property>
+        <signal name="activate" handler="go_to_next_page" swapped="no"/>
+      </object>
+    </child>
+    <child>
+      <object class="GtkAction" id="action-prev-page">
+        <property name="label" translatable="yes">Previous</property>
+        <property name="stock_id">gtk-go-back</property>
+        <signal name="activate" handler="go_to_previous_page" swapped="no"/>
+      </object>
+    </child>
+    <child>
+      <object class="GtkAction" id="zoom-in">
+        <property name="label" translatable="yes">Zoom In</property>
+        <property name="stock_id">gtk-zoom-in</property>
+        <signal name="activate" handler="zoom_in" swapped="no"/>
+      </object>
+    </child>
+    <child>
+      <object class="GtkAction" id="zoom-out">
+        <property name="label" translatable="yes">Zoom Out</property>
+        <property name="stock_id">gtk-zoom-out</property>
+        <signal name="activate" handler="zoom_out" swapped="no"/>
+      </object>
+    </child>
+  </object>
+  <object class="GtkWindow" id="main_window">
+    <property name="can_focus">False</property>
+    <property name="title" translatable="yes">SDAPS</property>
+    <signal name="delete-event" handler="quit_application" swapped="no"/>
+    <child>
+      <object class="GtkBox" id="box1">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkMenuBar" id="menubar1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <child>
+              <object class="GtkMenuItem" id="menuitem1">
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">_File</property>
+                <property name="use_underline">True</property>
+                <child type="submenu">
+                  <object class="GtkMenu" id="menu1">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <child>
+                      <object class="GtkImageMenuItem" id="imagemenuitem2">
+                        <property name="label">gtk-open</property>
+                        <property name="use_action_appearance">False</property>
+                        <property name="can_focus">False</property>
+                        <property name="use_underline">True</property>
+                        <property name="use_stock">True</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkImageMenuItem" id="imagemenuitem3">
+                        <property name="label">gtk-save</property>
+                        <property name="use_action_appearance">False</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="use_underline">True</property>
+                        <property name="use_stock">True</property>
+                        <signal name="activate" handler="save_project" swapped="no"/>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkSeparatorMenuItem" id="separatormenuitem1">
+                        <property name="use_action_appearance">False</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkImageMenuItem" id="imagemenuitem5">
+                        <property name="label">gtk-quit</property>
+                        <property name="use_action_appearance">False</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="use_underline">True</property>
+                        <property name="use_stock">True</property>
+                        <signal name="activate" handler="quit_application" swapped="no"/>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkMenuItem" id="menuitem3">
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">_View</property>
+                <property name="use_underline">True</property>
+                <child type="submenu">
+                  <object class="GtkMenu" id="menu2">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <child>
+                      <object class="GtkImageMenuItem" id="menuitem2">
+                        <property name="use_action_appearance">True</property>
+                        <property name="related_action">action-next-page</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="use_stock">True</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkImageMenuItem" id="menuitem5">
+                        <property name="related_action">action-prev-page</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="use_stock">True</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkMenuItem" id="menuitem4">
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">_Help</property>
+                <property name="use_underline">True</property>
+                <child type="submenu">
+                  <object class="GtkMenu" id="menu3">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <child>
+                      <object class="GtkImageMenuItem" id="imagemenuitem10">
+                        <property name="label">gtk-about</property>
+                        <property name="use_action_appearance">False</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="use_underline">True</property>
+                        <property name="use_stock">True</property>
+                        <signal name="activate" handler="show_about_dialog" swapped="no"/>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkToolbar" id="toolbar1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <child>
+              <object class="GtkToolButton" id="toolbutton1">
+                <property name="related_action">zoom-in</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="use_underline">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="homogeneous">True</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkToolButton" id="toolbutton2">
+                <property name="related_action">zoom-out</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="use_underline">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="homogeneous">True</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkSeparatorToolItem" id="toolbutton3">
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="homogeneous">True</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkToolButton" id="toolbutton4">
+                <property name="related_action">action-prev-page</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="use_underline">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="homogeneous">True</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkToolButton" id="toolbutton5">
+                <property name="related_action">action-next-page</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="use_underline">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="homogeneous">True</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkSeparatorToolItem" id="toolbutton6">
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="homogeneous">True</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkToolButton" id="fullscreen_toolbutton">
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="use_underline">True</property>
+                <property name="stock_id">gtk-fullscreen</property>
+                <signal name="clicked" handler="toggle_fullscreen" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="homogeneous">True</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkSeparatorToolItem" id="toolbutton7">
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="homogeneous">True</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkToolItem" id="toolbutton8">
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <child>
+                  <object class="GtkSpinButton" id="page_spin">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="invisible_char">●</property>
+                    <property name="adjustment">adjustment1</property>
+                    <signal name="value-changed" handler="page_spin_value_changed_cb" swapped="no"/>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="homogeneous">False</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkToolItem" id="toolbutton9">
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <child>
+                  <object class="GtkLabel" id="position_label">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="homogeneous">False</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkSeparatorToolItem" id="toolbutton10">
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="homogeneous">True</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkToolItem" id="toolbutton11">
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <child>
+                  <object class="GtkComboBox" id="page_number_combo">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <signal name="changed" handler="page_number_combo_changed_cb" swapped="no"/>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="homogeneous">False</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkToggleToolButton" id="turned_toggle">
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="is_important">True</property>
+                <property name="label" translatable="yes">Page Rotated</property>
+                <property name="use_underline">True</property>
+                <signal name="toggled" handler="turned_toggle_toggled_cb" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="homogeneous">True</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkSeparatorToolItem" id="toolbutton12">
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="homogeneous">True</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkToggleToolButton" id="quality_sort_toggle">
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="is_important">True</property>
+                <property name="label" translatable="yes">Sort by Quality</property>
+                <property name="use_underline">True</property>
+                <signal name="toggled" handler="quality_sort_toggle_toggled_cb" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="homogeneous">True</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkToolItem" id="toolbutton14">
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <child>
+                  <object class="GtkLabel" id="quality_label">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="label" translatable="yes">label</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="homogeneous">True</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkPaned" id="paned1">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <child>
+              <object class="GtkScrolledWindow" id="sheet_scrolled_window">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="events">GDK_STRUCTURE_MASK | GDK_SMOOTH_SCROLL_MASK</property>
+                <property name="shadow_type">in</property>
+                <child>
+                  <placeholder/>
+                </child>
+              </object>
+              <packing>
+                <property name="resize">True</property>
+                <property name="shrink">True</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkScrolledWindow" id="scrolledwindow1">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <child>
+                  <object class="GtkViewport" id="data_view">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="border_width">5</property>
+                    <property name="hscroll_policy">natural</property>
+                    <property name="vscroll_policy">natural</property>
+                    <property name="shadow_type">none</property>
+                    <child>
+                      <placeholder/>
+                    </child>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="resize">True</property>
+                <property name="shrink">True</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>
diff --git a/sdaps/gui/sheet_widget.py b/sdaps/gui/sheet_widget.py
new file mode 100644
index 0000000..23d2e77
--- /dev/null
+++ b/sdaps/gui/sheet_widget.py
@@ -0,0 +1,446 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2007-2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2007-2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import math
+from gi.repository import GObject
+from gi.repository import Gtk
+from gi.repository import Gdk
+import cairo
+import copy
+import os
+from sdaps import defs
+
+from sdaps import model
+
+from sdaps import matrix
+
+
+class SheetWidget(Gtk.DrawingArea, Gtk.Scrollable):
+    __gtype_name__ = "SDAPSSheetWidget"
+
+    def __init__(self, provider):
+        Gtk.DrawingArea.__init__(self)
+        events = Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK | \
+            Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.BUTTON_MOTION_MASK | \
+            Gdk.EventMask.SCROLL_MASK | Gdk.EventMask.KEY_PRESS_MASK
+        try:
+            events = events | Gdk.EventMask.SMOOTH_SCROLL_MASK
+        except AttributeError:
+            # Does only work for GTK+ >=3.4
+            pass
+        self.add_events(events)
+
+        self.hadj = None
+        self.vadj = None
+        self._hadj_value_changed_cb_id = None
+        self._vadj_value_changed_cb_id = None
+
+        self.provider = provider
+
+        self._old_scroll_x = 0
+        self._old_scroll_y = 0
+        self._edge_drag_active = False
+
+        self._zoom = 1.0
+
+        self.props.can_focus = True
+
+        self._update_matrices()
+        self._cs_image = None
+        self._ss_image = None
+
+        self.provider.survey.questionnaire.connect_data_changed(self.partial_update)
+
+    def update_state(self):
+        # Cancel any dragging operation
+        self._edge_drag_active = False
+        self._update_matrices()
+        self.queue_resize()
+        self.queue_draw()
+
+    def partial_update(self, questionnaire, qobj, obj, name, old_value):
+        if not isinstance(obj, model.data.Box):
+            return
+
+        if self.provider.image.page_number != qobj.page_number:
+            return
+
+        self.invalidate_area(obj.x, obj.y, obj.width, obj.height)
+
+    def _update_matrices(self):
+        xoffset = 0
+        yoffset = 0
+        if self.hadj:
+            xoffset = int(self.hadj.props.value)
+        if self.vadj:
+            yoffset = int(self.vadj.props.value)
+
+        m = cairo.Matrix(self._zoom, 0,
+                         0, self._zoom,
+                         -xoffset, -yoffset)
+        form_matrix = self.provider.image.matrix.mm_to_px()
+        m = form_matrix.multiply(m)
+
+        self._mm_to_widget_matrix = m
+        self._widget_to_mm_matrix = \
+            cairo.Matrix(*m)
+        self._widget_to_mm_matrix.invert()
+
+    def invalidate_area(self, x_mm, y_mm, width_mm, height_mm):
+        x, y = self._mm_to_widget_matrix.transform_point(x_mm, y_mm)
+        width, height = self._mm_to_widget_matrix.transform_distance(width_mm, height_mm)
+
+        width = int(math.ceil(width + x - int(x))) + 20
+        height = int(math.ceil(height + y - int(y))) + 20
+        x = int(x) - 10
+        y = int(y) - 10
+
+        self.queue_draw_area(x, y, width, height)
+
+    def _adjustment_changed_cb(self, adjustment):
+        dx = int(self._old_scroll_x) - int(self.hadj.props.value)
+        dy = int(self._old_scroll_y) - int(self.vadj.props.value)
+
+        if self.get_window() is not None:
+            self.get_window().scroll(dx, dy)
+
+        self._old_scroll_x = self.hadj.props.value
+        self._old_scroll_y = self.vadj.props.value
+
+        # Update the transformation matrices
+        self._update_matrices()
+
+    def do_button_press_event(self, event):
+        # Pass everything except normal clicks down
+        if event.button != 1 and event.button != 2 and event.button != 3:
+            return False
+
+        if event.button == 2:
+            self._drag_start_x = event.x
+            self._drag_start_y = event.y
+            cursor = Gdk.Cursor(Gdk.CursorType.HAND2)
+            self.get_window().set_cursor(cursor)
+            return True
+
+        mm_x, mm_y = self._widget_to_mm_matrix.transform_point(event.x, event.y)
+
+        if event.button == 3:
+            # Give the corresponding widget the focus.
+            box = self.provider.survey.questionnaire.gui.find_box(self.provider.image.page_number, mm_x, mm_y)
+            if hasattr(box, "widget"):
+                box.widget.focus()
+
+            return True
+
+        # button 1
+        self.grab_focus()
+
+        # Look for edges to drag first(on a 4x4px target)
+        tollerance_x, tollerance_y = self._widget_to_mm_matrix.transform_distance(4.0, 4.0)
+        result = self.provider.survey.questionnaire.gui.find_edge(self.provider.image.page_number, mm_x, mm_y,
+                                                                  tollerance_x, tollerance_y)
+        if result:
+            self._edge_drag_active = True
+            self._edge_drag_obj = result[0]
+            self._edge_drag_data = result[1]
+            return True
+
+        box = self.provider.survey.questionnaire.gui.find_box(self.provider.image.page_number, mm_x, mm_y)
+
+        if box is not None:
+            box.data.state = not box.data.state
+
+            return True
+
+    def do_button_release_event(self, event):
+        if event.button != 1 and event.button != 2 and event.button != 3:
+            return False
+
+        self.get_window().set_cursor(None)
+
+        if event.button == 1:
+            self._edge_drag_active = False
+
+        return True
+
+    def do_motion_notify_event(self, event):
+        if event.state & Gdk.ModifierType.BUTTON2_MASK:
+            x = int(event.x)
+            y = int(event.y)
+
+            dx = self._drag_start_x - x
+            dy = self._drag_start_y - y
+
+            if self.hadj:
+                value = self.hadj.props.value + dx
+                value = min(value, self.hadj.props.upper - self.hadj.props.page_size)
+                self.hadj.set_value(value)
+            if self.vadj:
+                value = self.vadj.props.value + dy
+                value = min(value, self.vadj.props.upper - self.vadj.props.page_size)
+                self.vadj.set_value(value)
+
+            self._drag_start_x = event.x
+            self._drag_start_y = event.y
+
+            return True
+        elif event.state & Gdk.ModifierType.BUTTON1_MASK:
+            if self._edge_drag_active:
+                mm_x, mm_y = self._widget_to_mm_matrix.transform_point(event.x, event.y)
+
+                self._edge_drag_obj.gui.move_edge(self._edge_drag_data, mm_x, mm_y)
+
+                self.queue_draw()
+                return True
+
+        return False
+
+    def do_get_request_mode(self):
+        return Gtk.SizeRequestMode.CONSTANT_SIZE
+
+    def do_get_preferred_height(self):
+        if self.vadj:
+            self.vadj.props.upper = self._render_height
+
+        return min(self._render_height, 300), self._render_height
+
+    def do_get_preferred_width(self):
+        if self.hadj:
+            self.hadj.props.upper = self._render_width
+
+        return min(self._render_width, 300), self._render_width
+
+    def do_size_allocate(self, allocation):
+        # WTF? Why does this happen?
+        if allocation.x < 0 or allocation.y < 0:
+            GObject.idle_add(self.queue_resize)
+
+        if self.hadj:
+            self.hadj.props.page_size = min(self._render_width, allocation.width)
+            if self.hadj.props.value > self._render_width - allocation.width:
+                self.hadj.props.value = self._render_width - allocation.width
+            self.hadj.props.page_increment = allocation.width * 0.9
+            self.hadj.props.step_increment = allocation.width * 0.1
+        if self.vadj:
+            self.vadj.props.page_size = min(self._render_height, allocation.height)
+            if self.vadj.props.value > self._render_height - allocation.height:
+                self.vadj.props.value = self._render_height - allocation.height
+            self.vadj.props.page_increment = allocation.height * 0.9
+            self.vadj.props.step_increment = allocation.height * 0.1
+
+        self._update_matrices()
+
+        Gtk.DrawingArea.do_size_allocate(self, allocation)
+
+    def do_draw(self, cr):
+
+        cr.save()
+        cr.save()
+
+        # For the image
+        xoffset = -int(self.hadj.props.value)
+        yoffset = -int(self.vadj.props.value)
+
+        image = self.provider.image.surface.surface_rgb
+
+        if image != self._cs_image or self._ss_image is None:
+            self._cs_image = image
+            target = cr.get_target()
+            self._ss_image = target.create_similar(cairo.CONTENT_COLOR, image.get_width(), image.get_height())
+            subcr = cairo.Context(self._ss_image)
+            subcr.set_source_surface(self._cs_image)
+            subcr.paint()
+
+        # Draw the image in the background
+        cr.translate(xoffset, yoffset)
+        cr.scale(self._zoom, self._zoom)
+
+        cr.set_source_surface(self._ss_image, 0, 0)
+        cr.paint()
+
+        cr.restore()
+
+        # Set the matrix _after_ drawing the background pixbuf.
+        cr.transform(self._mm_to_widget_matrix)
+
+        cr.set_source_rgba(1.0, 0.0, 0.0, 0.6)
+        cr.set_line_width(1.0 * 25.4 / 72.0)
+        cr.rectangle(defs.corner_mark_left, defs.corner_mark_top,
+                     self.provider.survey.defs.paper_width - defs.corner_mark_left - defs.corner_mark_right,
+                     self.provider.survey.defs.paper_height - defs.corner_mark_top - defs.corner_mark_bottom)
+        cr.stroke()
+
+        # Draw the overlay stuff.
+        self.provider.survey.questionnaire.gui.draw(cr, self.provider.image.page_number)
+
+        def inner_box(cr, x, y, width, height):
+            line_width = cr.get_line_width()
+
+            cr.rectangle(x + line_width / 2.0, y + line_width / 2.0,
+                         width - line_width, height - line_width)
+
+        if self.provider.survey.defs.style == 'classic':
+            half_pt = 0.5 / 72.0 * 25.4
+            pt = 1.0 / 72.0 * 25.4
+            inner_box(cr,
+                      defs.corner_mark_left + defs.corner_box_padding - half_pt,
+                      defs.corner_mark_top + defs.corner_box_padding - half_pt,
+                      defs.corner_box_width + pt,
+                      defs.corner_box_height + pt)
+            inner_box(cr,
+                      self.provider.survey.defs.paper_width
+                          - defs.corner_mark_right
+                          - defs.corner_box_padding
+                          - defs.corner_box_width - half_pt,
+                      defs.corner_mark_top + defs.corner_box_padding - half_pt,
+                      defs.corner_box_width + pt,
+                      defs.corner_box_height + pt)
+            inner_box(cr,
+                      defs.corner_mark_left + defs.corner_box_padding - half_pt,
+                      self.provider.survey.defs.paper_height
+                          - defs.corner_mark_bottom
+                          - defs.corner_box_padding
+                          - defs.corner_box_height
+                          - half_pt,
+                      defs.corner_box_width + pt,
+                      defs.corner_box_height + pt)
+            inner_box(cr,
+                      self.provider.survey.defs.paper_width
+                          - defs.corner_mark_right
+                          - defs.corner_box_padding
+                          - defs.corner_box_width
+                          - half_pt,
+                      self.provider.survey.defs.paper_height
+                          - defs.corner_mark_bottom
+                          - defs.corner_box_padding
+                          - defs.corner_box_height
+                          - half_pt,
+                      defs.corner_box_width + pt,
+                      defs.corner_box_height + pt)
+            cr.stroke()
+
+        cr.restore()
+
+        return True
+
+    def do_key_press_event(self, event):
+        if self.vadj:
+            if event.keyval == Gdk.keyval_from_name("Up"):
+                value = self.vadj.props.value - self.vadj.props.step_increment
+                value = min(value, self.vadj.props.upper - self.vadj.props.page_size)
+                self.vadj.set_value(value)
+                return True
+            if event.keyval == Gdk.keyval_from_name("Down"):
+                value = self.vadj.props.value + self.vadj.props.step_increment
+                value = min(value, self.vadj.props.upper - self.vadj.props.page_size)
+                self.vadj.set_value(value)
+                return True
+
+        if self.hadj:
+            if event.keyval == Gdk.keyval_from_name("Left"):
+                value = self.hadj.props.value - self.hadj.props.step_increment
+                value = min(value, self.hadj.props.upper - self.hadj.props.page_size)
+                self.hadj.set_value(value)
+                return True
+            if event.keyval == Gdk.keyval_from_name("Right"):
+                value = self.hadj.props.value + self.hadj.props.step_increment
+                value = min(value, self.hadj.props.upper - self.hadj.props.page_size)
+                self.hadj.set_value(value)
+                return True
+        return False
+
+    def _get_render_width(self):
+        image = self.provider.image.surface.surface_rgb
+        if image:
+            width = image.get_width()
+        else:
+            width = 400
+        width = int(math.ceil(self._zoom * width))
+        return width
+
+    def _get_render_height(self):
+        image = self.provider.image.surface.surface_rgb
+        if image:
+            height = image.get_height()
+        else:
+            height = 400
+
+        height = int(math.ceil(self._zoom * height))
+        return height
+
+    _render_width = property(_get_render_width)
+    _render_height = property(_get_render_height)
+
+    def get_hscroll_policy(self):
+        # Does not matter, we don't support natural sizes
+        return Gtk.ScrollablePolicy.NATURAL
+    hscroll_policy = \
+        GObject.property(get_hscroll_policy,
+                         type=Gtk.ScrollablePolicy,
+                         default=Gtk.ScrollablePolicy.NATURAL)
+
+    def get_vscroll_policy(self):
+        # Does not matter, we don't support natural sizes
+        return Gtk.ScrollablePolicy.NATURAL
+    vscroll_policy = \
+        GObject.property(get_vscroll_policy,
+                         type=Gtk.ScrollablePolicy,
+                         default=Gtk.ScrollablePolicy.NATURAL)
+
+    def get_vadjustment(self):
+        return self.vadj
+
+    def set_vadjustment(self, value):
+        if self._vadj_value_changed_cb_id is not None:
+            self.vadj.disconnect(self._vadj_value_changed_cb_id)
+            self._vadj_value_changed_cb_id = None
+        self.vadj = value
+
+        if self.vadj is not None:
+            self._vadj_value_changed_cb_id = self.vadj.connect('value-changed', self._adjustment_changed_cb)
+
+        self._update_matrices()
+
+    vadjustment = GObject.property(get_vadjustment, set_vadjustment, type=Gtk.Adjustment)
+
+    def get_hadjustment(self):
+        return self.hadj
+
+    def set_hadjustment(self, value):
+        if self._hadj_value_changed_cb_id is not None:
+            self.hadj.disconnect(self._hadj_value_changed_cb_id)
+            self._hadj_value_changed_cb_id = None
+        self.hadj = value
+
+        if self.hadj is not None:
+            self._hadj_value_changed_cb_id = self.hadj.connect('value-changed', self._adjustment_changed_cb)
+
+        self._update_matrices()
+
+    hadjustment = GObject.property(get_hadjustment, set_hadjustment, type=Gtk.Adjustment)
+
+    def set_zoom(self, value):
+        self._zoom = value
+        self.queue_resize()
+
+    def get_zoom(self):
+        return self._zoom
+
+    zoom = GObject.property(get_zoom, set_zoom, float, minimum=0.001, maximum=1024.0, default=1.0)
+
+
diff --git a/sdaps/gui/widget_buddies.py b/sdaps/gui/widget_buddies.py
new file mode 100644
index 0000000..06f8012
--- /dev/null
+++ b/sdaps/gui/widget_buddies.py
@@ -0,0 +1,313 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+from gi.repository import Gtk
+from gi.repository import GLib
+import cairo
+
+from sdaps import model
+from sdaps import defs
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+def markup_escape_text(text):
+    # Unfortunately the API returns a byte string, so we need to decode it
+    # as the formatting would not work otherwise.
+    return GLib.markup_escape_text(text).decode('utf-8')
+
+class Questionnaire(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'widget'
+    obj_class = model.questionnaire.Questionnaire
+
+    def __init__(self, *args):
+        model.buddy.Buddy.__init__(self, *args)
+
+        self.obj.connect_data_changed(self.data_changed)
+
+        self._notify_ensure_visible = list()
+
+    def data_changed(self, questionnaire, qobj, obj, name, old_value):
+        # Simply sync everything on every change.
+        self.sync_state()
+
+    def create_widget(self):
+        self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+
+        # First some global options
+        widget = Gtk.Label()
+        widget.set_markup(_(u'<b>Global Properties</b>'))
+        widget.props.xalign = 0.0
+        self.box.pack_start(widget, False, True, 0)
+
+        self.valid_checkbox = Gtk.CheckButton.new_with_label(_(u'Sheet valid'))
+        self.verified_checkbox = Gtk.CheckButton.new_with_label(_(u'Verified'))
+        self.empty_checkbox = Gtk.CheckButton.new_with_label(_(u'Empty'))
+        self.empty_checkbox.set_sensitive(False)
+
+        self.valid_checkbox.connect('toggled', self.toggled_valid_cb)
+        self.verified_checkbox.connect('toggled', self.toggled_verified_cb)
+
+        indent = Gtk.Alignment()
+        indent.set_padding(0, 0, 10, 0)
+
+        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+
+        self.qid = Gtk.Label()
+        self.qid.set_markup(_(u'<b>Questionnaire ID: </b>') + markup_escape_text(str(self.obj.survey.sheet.questionnaire_id)))
+        self.qid.props.xalign = 0.0
+
+        indent.add(vbox)
+
+	vbox.add(self.qid)
+        vbox.add(self.valid_checkbox)
+        vbox.add(self.verified_checkbox)
+        vbox.add(self.empty_checkbox)
+
+        self.box.pack_start(indent, False, True, 0)
+
+        # And all the questions
+        for qobject in self.obj.qobjects:
+            widget = qobject.widget.create_widget()
+            if widget is not None:
+                self.box.pack_start(widget, False, True, 0)
+
+        return self.box
+
+    def sync_state(self):
+        for qobject in self.obj.qobjects:
+            qobject.widget.sync_state()
+
+        self.qid.set_markup(_(u'<b>Questionnaire ID: </b>') + markup_escape_text(str(self.obj.survey.sheet.questionnaire_id)))
+        self.valid_checkbox.set_active(self.obj.survey.sheet.valid)
+        self.verified_checkbox.set_active(self.obj.survey.sheet.verified)
+        self.empty_checkbox.set_active(self.obj.survey.sheet.empty)
+
+    def ensure_visible(self, widget):
+        for func in self._notify_ensure_visible:
+            func(widget)
+
+    def connect_ensure_visible(self, func):
+        self._notify_ensure_visible.append(func)
+
+    def disconnect_ensure_visible(self, func):
+        self._notify_ensure_visible.remove(func)
+
+    def toggled_valid_cb(self, widget):
+        self.obj.survey.sheet.valid = widget.get_active()
+
+    def toggled_verified_cb(self, widget):
+        self.obj.survey.sheet.verified = widget.get_active()
+
+class QObject(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'widget'
+    obj_class = model.questionnaire.QObject
+
+    def create_widget(self):
+        self.widget = None
+
+        return self.widget
+
+    def sync_state(self):
+        for box in self.obj.boxes:
+            box.widget.sync_state()
+
+    def focus(self):
+        self.obj.question.questionnaire.widget.ensure_visible(self.widget)
+
+        if len(boxes) > 0:
+            self.obj.boxes[0].widget.focus()
+
+
+class Head(QObject):
+
+    __metaclass__ = model.buddy.Register
+    name = 'widget'
+    obj_class = model.questionnaire.Head
+
+    def create_widget(self):
+        self.widget = Gtk.Label()
+        self.widget.set_markup(u'<b>%s %s</b>' % (self.obj.id_str(), markup_escape_text(self.obj.title)))
+        self.widget.props.xalign = 0.0
+
+        return self.widget
+
+
+class Question(QObject):
+
+    __metaclass__ = model.buddy.Register
+    name = 'widget'
+    obj_class = model.questionnaire.Question
+
+    def create_widget(self):
+        self.widget = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+
+        self.label = Gtk.Label()
+        self.label.set_markup(u'<b>%s %s</b>' % (self.obj.id_str(), markup_escape_text(self.obj.question)))
+        self.label.props.xalign = 0.0
+
+        self.widget.pack_start(self.label, False, True, 0)
+
+        indent = Gtk.Alignment()
+        indent.set_padding(0, 0, 10, 0)
+        self.widget.pack_end(indent, False, True, 0)
+
+        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+        indent.add(vbox)
+
+        for box in self.obj.boxes:
+            widget = box.widget.create_widget()
+            if widget is not None:
+                vbox.pack_start(widget, False, True, 0)
+
+        return self.widget
+
+class Mark(Question):
+
+    __metaclass__ = model.buddy.Register
+    name = 'widget'
+    obj_class = model.questionnaire.Mark
+
+    def create_widget(self):
+        self.widget = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+
+        self.label = Gtk.Label()
+        self.label.set_markup(u'<b>%s %s</b>' % (self.obj.id_str(), markup_escape_text(self.obj.question)))
+        self.label.props.xalign = 0.0
+
+        self.widget.pack_start(self.label, False, True, 0)
+
+        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
+        lower_label = Gtk.Label()
+        lower_label.set_text(self.obj.answers[0])
+        lower_label.props.xalign = 1.0
+
+        upper_label = Gtk.Label()
+        upper_label.set_text(self.obj.answers[1])
+        upper_label.props.xalign = 0.0
+
+        sizegroup = Gtk.SizeGroup()
+        sizegroup.set_mode(Gtk.SizeGroupMode.BOTH)
+        sizegroup.add_widget(lower_label)
+        sizegroup.add_widget(upper_label)
+
+        hbox.pack_start(lower_label, True, True, 0)
+
+        # Maybe use radiobuttons instead?
+        for box in self.obj.boxes:
+            widget = box.widget.create_widget()
+            if widget is not None:
+                hbox.pack_start(widget, False, True, 0)
+
+        hbox.pack_start(upper_label, True, True, 0)
+
+        self.widget.pack_start(hbox, False, True, 0)
+
+        return self.widget
+
+
+class Box(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'widget'
+    obj_class = model.questionnaire.Checkbox
+
+    def create_widget(self):
+        self.widget = Gtk.CheckButton.new_with_label(self.obj.text)
+        self.widget.connect('toggled', self.toggled_cb)
+
+        return self.widget
+
+    def sync_state(self):
+        self.widget.props.active = self.obj.data.state
+
+    def toggled_cb(self, widget):
+        self.obj.data.state = widget.props.active
+
+    def focus(self):
+        self.widget.grab_focus()
+
+        self.obj.question.questionnaire.widget.ensure_visible(self.obj.question.widget.widget)
+        self.obj.question.questionnaire.widget.ensure_visible(self.widget)
+
+class Checkbox(Box):
+
+    __metaclass__ = model.buddy.Register
+    name = 'widget'
+    obj_class = model.questionnaire.Checkbox
+
+
+class Textbox(Box):
+
+    __metaclass__ = model.buddy.Register
+    name = 'widget'
+    obj_class = model.questionnaire.Textbox
+
+    def create_widget(self):
+        self.widget = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+
+        self.checkbox = Gtk.CheckButton.new_with_label(self.obj.text)
+        self.checkbox.connect('toggled', self.toggled_cb)
+
+        self.widget.add(self.checkbox)
+
+        indent = Gtk.Alignment()
+        indent.set_padding(0, 0, 10, 0)
+        frame = Gtk.Frame()
+        indent.add(frame)
+        self.widget.pack_end(indent, False, True, 0)
+
+        self.textbox = Gtk.TextView()
+        self.textbox.set_wrap_mode(Gtk.WrapMode.WORD_CHAR)
+        self.buffer = self.textbox.get_buffer()
+        self.buffer.connect('changed', self.buffer_changed_cb)
+
+        frame.add(self.textbox)
+
+        return self.widget
+
+    def buffer_changed_cb(self, buf):
+        start = buf.get_start_iter()
+        end = buf.get_end_iter()
+        self.obj.data.text = buf.get_text(start, end, False).decode('UTF-8')
+
+    def sync_state(self):
+        self.checkbox.props.active = self.obj.data.state
+
+        self.textbox.props.sensitive = self.obj.data.state
+
+        # Only update the text if it changed (or else recursion hits)
+        start = self.buffer.get_start_iter()
+        end = self.buffer.get_end_iter()
+        currtext = self.buffer.get_text(start, end, False).decode('UTF-8')
+        if self.obj.data.text != currtext:
+            self.buffer.set_text(self.obj.data.text)
+
+    def focus(self):
+        if self.textbox.props.sensitive:
+            self.textbox.grab_focus()
+        else:
+            self.checkbox.grab_focus()
+
+        self.obj.question.questionnaire.widget.ensure_visible(self.obj.question.widget.widget)
+        self.obj.question.questionnaire.widget.ensure_visible(self.widget)
+
diff --git a/sdaps/image/__init__.py b/sdaps/image/__init__.py
new file mode 100644
index 0000000..5b84797
--- /dev/null
+++ b/sdaps/image/__init__.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+u"""
+This modules contains low level image processing functions. These functions
+are implemented in C for speed reasons. Usually one will not need to use these
+directly, instead modules like "recognize" or "surface" use them to load and
+analyze the image data.
+"""
+
+import os
+import sys
+
+from sdaps import paths
+from sdaps import defs
+from sdaps import log
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+if paths.local_run:
+    # image.so liegt in lib_build_dir/image/
+    __path__.append(os.path.join(paths.lib_build_dir, 'image'))
+
+# If SDAPS is installed, then the image.so file is in the current directory.
+# Simply importing it without changes to the paths will work.
+
+try:
+    from image import *
+except ImportError, e:
+    print e
+    log.error(_("It appears you have not build the C extension. Please run \"./setup.py build\" in the toplevel directory."))
+    sys.exit(1)
+
+set_magic_values(defs.corner_mark_min_length,
+                 defs.corner_mark_max_length,
+                 defs.image_line_width,
+                 defs.corner_mark_search_distance,
+                 defs.image_line_coverage)
+
diff --git a/sdaps/image/image.c b/sdaps/image/image.c
new file mode 100644
index 0000000..199d65f
--- /dev/null
+++ b/sdaps/image/image.c
@@ -0,0 +1,1351 @@
+/* SDAPS
+ * Copyright (C) 2008  Christoph Simon <post at christoph-simon.eu>
+ * Copyright (C) 2008, 2011  Benjamin Berg <benjamin at sipsolutions.net>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/*#include <gdk-pixbuf/gdk-pixbuf.h>*/
+#include <tiffio.h>
+#include "image.h"
+#include <math.h>
+#include "surface.h"
+#include "transform.h"
+
+/* Some of the more important Magic Values */
+gdouble sdaps_line_min_length = 20-2;
+gdouble sdaps_line_max_length = 20+2;
+gdouble sdaps_line_width = 1/72*25.4;
+gdouble sdaps_corner_mark_search_distance = 50;
+gdouble sdaps_line_coverage = 0.65;
+
+gboolean sdaps_create_debug_surface = FALSE;
+gint sdaps_debug_surface_ox;
+gint sdaps_debug_surface_oy;
+cairo_surface_t *sdaps_debug_surface = NULL;
+
+static void
+debug_surface_clear(void)
+{
+	if (sdaps_debug_surface != NULL) {
+		cairo_surface_destroy(sdaps_debug_surface);
+		sdaps_debug_surface = NULL;
+	}
+}
+
+static cairo_surface_t*
+debug_surface_create(gint x, gint y, gint width, gint height, gdouble r, gdouble g, gdouble b, gdouble a)
+{
+	cairo_t* cr;
+
+	debug_surface_clear();
+
+	if (!sdaps_create_debug_surface)
+		return NULL;
+
+	sdaps_debug_surface_ox = x;
+	sdaps_debug_surface_oy = y;
+
+	sdaps_debug_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+
+	cr = cairo_create (sdaps_debug_surface);
+	cairo_set_source_rgba (cr, r, g, b, a);
+	cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+	cairo_paint (cr);
+	cairo_destroy (cr);
+
+	cairo_surface_flush (sdaps_debug_surface);
+
+	return sdaps_debug_surface;
+}
+
+
+void
+disable_libtiff_warnings (void)
+{
+	TIFFSetWarningHandler(NULL);
+}
+
+cairo_surface_t*
+get_a1_from_tiff (const char *filename, gint page, gboolean rotated)
+{
+	TIFF* tiff;
+	cairo_surface_t *surface;
+	guint32 *s_pixels;
+	guint32 *t_pixels;
+	guint32 *t_row;
+	int s_stride, t_stride;
+	int width, height;
+	BARREL_VARS
+
+	int x, y;
+
+	tiff = TIFFOpen(filename, "r");
+	if (tiff == NULL)
+		return NULL;
+
+	if (!TIFFSetDirectory(tiff, page)) {
+		TIFFClose(tiff);
+		return NULL;
+	}
+
+	TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &width);
+	TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &height);
+	t_pixels = g_new(guint32, width * height);
+	if (!rotated)
+		TIFFReadRGBAImageOriented(tiff, width, height, t_pixels, ORIENTATION_TOPLEFT, 0);
+	else
+		TIFFReadRGBAImageOriented(tiff, width, height, t_pixels, ORIENTATION_BOTRIGHT, 0);
+
+	surface = cairo_image_surface_create(CAIRO_FORMAT_A1, width, height);
+	s_pixels = (guint32*) cairo_image_surface_get_data(surface);
+	s_stride = cairo_image_surface_get_stride(surface);
+
+	t_stride = width * sizeof(guint32);
+	t_row = t_pixels;
+
+	for (y = 0; y < height; y++) {
+		guint32 *t_p = t_row;
+		BARREL_START_ROW((char*)s_pixels + y * s_stride)
+		for (x = 0; x < width; x++) {
+			BARREL_STORE_BIT(!(TIFFGetR(*t_p) >> 7));
+			/* SET_PIXEL(s_pixels, s_stride, x, y, !(TIFFGetR(*t_p) >> 7)); */
+			t_p = t_p + 1;
+		}
+		BARREL_FLUSH
+		t_row = (guint32*) ((char*) t_row + t_stride);
+	}
+
+	g_free(t_pixels);
+	TIFFClose(tiff);
+
+	cairo_surface_mark_dirty(surface);
+
+	return surface;
+}
+
+gboolean
+write_a1_to_tiff (const char *filename, cairo_surface_t *surf)
+{
+	TIFF *tiff;
+	gint width, height;
+	gint stride;
+	gint y;
+	guint32 stripsize;
+	guint8 *data;
+
+	g_assert(cairo_image_surface_get_format(surf) == CAIRO_FORMAT_A1);
+
+	width = cairo_image_surface_get_width(surf);
+	height = cairo_image_surface_get_height(surf);
+	stride = cairo_image_surface_get_stride(surf);
+	data = (guint8*) cairo_image_surface_get_data(surf);
+
+	/* We create a new TIFF file if it doesn't exist yet, otherwise we append
+	 * to it. */
+	tiff = TIFFOpen(filename, "a");
+	if (tiff == NULL)
+		return FALSE;
+
+	/* Reverse *ALL* bits ...
+	 * TODO: Is this correct for big endian??? */
+	TIFFReverseBits(data, stride*(height-1) + (width+7)/8);
+
+	/* Setup the new image. */
+	TIFFSetField(tiff, TIFFTAG_IMAGEWIDTH, width);
+	TIFFSetField(tiff, TIFFTAG_IMAGELENGTH, height);
+	TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 1);
+	TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 1);
+	TIFFSetField(tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_SEPARATE);
+
+	stripsize = TIFFDefaultStripSize(tiff, -1);
+
+	TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, stripsize);
+	TIFFSetField(tiff, TIFFTAG_COMPRESSION, COMPRESSION_CCITTFAX4);
+	TIFFSetField(tiff, TIFFTAG_GROUP4OPTIONS, 0);
+	TIFFSetField(tiff, TIFFTAG_FAXMODE, FAXMODE_CLASSF);
+	TIFFSetField(tiff, TIFFTAG_THRESHHOLDING, THRESHHOLD_BILEVEL);
+	TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISWHITE);
+
+	for (y = 0; y < height; y++) {
+		if (TIFFWriteScanline(tiff, data + y * stride, y, 0) == -1)
+			goto BAIL_WRITE_A1;
+	}
+
+	/* Undo reversal again. */
+	TIFFReverseBits(data, stride*(height-1) + (width+7)/8);
+	TIFFClose(tiff);
+
+	return TRUE;
+
+BAIL_WRITE_A1:
+
+	/* Undo reversal again. */
+	TIFFReverseBits(data, stride*(height-1) + (width+7)/8);
+	TIFFClose(tiff);
+
+	return FALSE;
+}
+
+cairo_surface_t*
+get_rgb24_from_tiff (const char *filename, gint page, gboolean rotated)
+{
+	TIFF* tiff;
+	cairo_surface_t *surface;
+	guint32 *s_pixels;
+	guint32 *t_pixels;
+	guint32 *t_row;
+	int s_stride, t_stride;
+	int width, height;
+
+	int x, y;
+
+	tiff = TIFFOpen(filename, "r");
+	if (tiff == NULL)
+		return NULL;
+
+	if (!TIFFSetDirectory(tiff, page)) {
+		TIFFClose(tiff);
+		return NULL;
+	}
+
+	TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &width);
+	TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &height);
+	t_pixels = g_new(guint32, width * height);
+	if (!rotated)
+		TIFFReadRGBAImageOriented(tiff, width, height, t_pixels, ORIENTATION_TOPLEFT, 0);
+	else
+		TIFFReadRGBAImageOriented(tiff, width, height, t_pixels, ORIENTATION_BOTRIGHT, 0);
+
+	surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height);
+	s_pixels = (guint32*) cairo_image_surface_get_data(surface);
+	s_stride = cairo_image_surface_get_stride(surface);
+
+	t_stride = width * sizeof(guint32);
+	t_row = t_pixels;
+
+	for (y = 0; y < height; y++) {
+		guint32 *t_p = t_row;
+
+		for (x = 0; x < width; x++) {
+			*((guint32*) (((guint8*) s_pixels) + 4 * x + y * s_stride)) = (TIFFGetR(*t_p) << 16) | (TIFFGetG(*t_p) << 8) | TIFFGetB(*t_p);
+			t_p = t_p + 1;
+		}
+		t_row = (guint32*) ((char*) t_row + t_stride);
+	}
+
+	g_free(t_pixels);
+	TIFFClose(tiff);
+
+	cairo_surface_mark_dirty(surface);
+
+	return surface;
+}
+
+gint
+get_tiff_page_count (const char *filename)
+{
+	TIFF* tiff;
+	gint pages;
+
+	tiff = TIFFOpen(filename, "r");
+	if (tiff == NULL)
+		return 0;
+
+	pages = TIFFNumberOfDirectories(tiff);
+
+	TIFFClose(tiff);
+
+	return pages;
+}
+
+gboolean
+get_tiff_resolution (const char *filename, gint page, gdouble *xresolution, gdouble *yresolution)
+{
+	TIFF* tiff;
+	float xres = 0.0;
+	float yres = 0.0;
+	uint16 unit = RESUNIT_NONE;
+
+	tiff = TIFFOpen(filename, "r");
+	if (tiff == NULL)
+		return FALSE;
+
+	if (!TIFFSetDirectory(tiff, page)) {
+		TIFFClose(tiff);
+		return FALSE;
+	}
+
+	TIFFGetField(tiff, TIFFTAG_XRESOLUTION, &xres);
+	TIFFGetField(tiff, TIFFTAG_YRESOLUTION, &yres);
+	TIFFGetField(tiff, TIFFTAG_RESOLUTIONUNIT, &unit);
+
+	if (unit == RESUNIT_CENTIMETER) {
+		*xresolution = xres / 10.0;
+		*yresolution = yres / 10.0;
+	} else if (unit == RESUNIT_INCH) {
+		*xresolution = xres / 25.4;
+		*yresolution = yres / 25.4;
+	} else {
+		/* Nothing good, pass back zeros */
+		*xresolution = 0;
+		*yresolution = 0;
+	}
+
+	TIFFClose(tiff);
+	return TRUE;
+}
+
+gboolean
+check_tiff_monochrome (const char *filename)
+{
+	TIFF* tiff;
+	gboolean monochrome = TRUE;
+
+	tiff = TIFFOpen(filename, "r");
+	if (tiff == NULL)
+		return FALSE;
+
+	do {
+		uint16 bits_per_sample;
+		TIFFGetField(tiff, TIFFTAG_BITSPERSAMPLE, &bits_per_sample);
+		if (bits_per_sample != 1)
+			monochrome = FALSE;
+	} while (TIFFReadDirectory(tiff) && monochrome);
+
+	if (!TIFFLastDirectory(tiff)) {
+		/* This should never ever happen ... */
+		monochrome = FALSE;
+	}
+
+	TIFFClose(tiff);
+
+	return monochrome;
+}
+
+#define LINE_COVERAGE sdaps_line_coverage
+
+static gint
+transform_distance_to_pixel(cairo_matrix_t *matrix, gdouble distance)
+{
+	gdouble dx, dy;
+
+	dx = distance;
+	dy = distance;
+	cairo_matrix_transform_distance(matrix, &dx, &dy);
+	return (gint) ceil(MAX(dx, dy));
+}
+
+static gboolean
+follow_line(cairo_surface_t *surface,
+            gint             x_start,
+            gint             y_start,
+            gint             x_dir,
+            gint             y_dir,
+            gint             line_width,
+            gint             line_length,
+            gint             line_max_length,
+            gdouble         *x1,
+            gdouble         *y1,
+            gdouble         *x2,
+            gdouble         *y2)
+{
+	gboolean found_segment;
+	gboolean found_line = FALSE;
+	gint x, y;
+	gint start_x, start_y;
+	gint end_x, end_y;
+	double length = 0;
+	gint search_length_left;
+
+	/* Large default values to begin with. These may not overflow when added up! */
+	start_x = 100000;
+	start_y = 100000;
+	end_x = 0;
+	end_y = 0;
+
+	found_segment = TRUE;
+
+	/* ****** Positive Direction */
+
+	x = x_start;
+	y = y_start;
+
+	search_length_left = 2*line_width;
+	while (found_segment || search_length_left > 0) {
+		gint offset;
+		gint found_offset = 0;
+		gint coverage;
+		gint max_coverage = 0;
+
+		search_length_left -= 1;
+
+		x += x_dir;
+		y += y_dir;
+
+		found_segment = FALSE;
+		for (offset = -line_width; offset <= line_width; offset++) {
+			gint coverage_1, coverage_2, coverage_3;
+
+			coverage_1 = count_black_pixel(surface,
+			                               x + offset * y_dir - line_width / 2,
+			                               y + offset * x_dir - line_width / 2,
+			                               line_width,
+			                               line_width);
+			coverage_2 = count_black_pixel(surface,
+			                               x - line_width*x_dir + offset * y_dir - line_width / 2,
+			                               y - line_width*y_dir + offset * x_dir - line_width / 2,
+			                               line_width,
+			                               line_width);
+			coverage_3 = count_black_pixel(surface,
+			                               x + line_width*x_dir + offset * y_dir - line_width / 2,
+			                               y + line_width*y_dir + offset * x_dir - line_width / 2,
+			                               line_width,
+			                               line_width);
+
+			if (coverage_1 < (line_width * line_width) * LINE_COVERAGE)
+				continue;
+
+			if (coverage_2 < (line_width * line_width) * LINE_COVERAGE)
+				continue;
+
+			if (coverage_3 < (line_width * line_width) * LINE_COVERAGE)
+				continue;
+
+			coverage = coverage_1 + coverage_2 + coverage_3;
+
+			if ((coverage >= (3*line_width * line_width) * LINE_COVERAGE) && (coverage > max_coverage)) {
+				gint p_x, p_y, cov_area;
+
+				/* Is this really a "line", ie. is the surrounding area *not* dark. */
+				cov_area = count_black_pixel(surface,
+				                             x + offset * y_dir - line_width - line_width / 2,
+				                             y + offset * x_dir - line_width - line_width / 2,
+				                             line_width * 3, line_width * 3);
+
+				if ((abs(x - start_x + y - start_y) > line_width*1.5) && cov_area >= 2*coverage)
+					continue;
+
+				found_segment = TRUE;
+				found_offset = offset;
+				max_coverage = coverage;
+				search_length_left = 0;
+
+				p_x = x - line_width*ABS(x_dir) + offset * y_dir;
+				p_y = y - line_width*ABS(y_dir) + offset * x_dir;
+
+				if (ABS(start_x * x_dir + start_y * y_dir) > ABS(p_x * x_dir + p_y * y_dir)) {
+					start_x = p_x;
+					start_y = p_y;
+				}
+
+				p_x = x + line_width*ABS(x_dir) + offset * y_dir;
+				p_y = y + line_width*ABS(y_dir) + offset * x_dir;
+
+				if (ABS(end_x * x_dir + end_y * y_dir) < ABS(p_x * x_dir + p_y * y_dir)) {
+					end_x = p_x;
+					end_y = p_y;
+				}
+			}
+		}
+
+		x += found_offset * y_dir;
+		y += found_offset * x_dir;
+
+		length = sqrt((start_x - end_x)*(start_x - end_x) + (start_y - end_y)*(start_y - end_y));
+		if (length >= line_max_length)
+			goto FOLLOW_LINE_BAIL;
+	}
+
+	/* ****** Negative Direction */
+
+	x = x_start;
+	y = y_start;
+
+	found_segment = TRUE;
+
+	search_length_left = 2*line_width;
+	while (found_segment || search_length_left > 0) {
+		gint offset;
+		gint found_offset = 0;
+		gint coverage;
+		gint max_coverage = 0;
+		search_length_left -= 1;
+
+		x -= x_dir;
+		y -= y_dir;
+
+		found_segment = FALSE;
+		for (offset = -line_width; (offset <= line_width) && !found_segment; offset++) {
+			gint coverage_1, coverage_2, coverage_3;
+
+			coverage_1 = count_black_pixel(surface,
+			                               x + offset * y_dir - line_width / 2,
+			                               y + offset * x_dir - line_width / 2,
+			                               line_width,
+			                               line_width);
+			coverage_2 = count_black_pixel(surface,
+			                               x - line_width*x_dir + offset * y_dir - line_width / 2,
+			                               y - line_width*y_dir + offset * x_dir - line_width / 2,
+			                               line_width,
+			                               line_width);
+			coverage_3 = count_black_pixel(surface,
+			                               x + line_width*x_dir + offset * y_dir - line_width / 2,
+			                               y + line_width*y_dir + offset * x_dir - line_width / 2,
+			                               line_width,
+			                               line_width);
+
+			if (coverage_1 < (line_width * line_width) * LINE_COVERAGE)
+				continue;
+
+			if (coverage_2 < (line_width * line_width) * LINE_COVERAGE)
+				continue;
+
+			if (coverage_3 < (line_width * line_width) * LINE_COVERAGE)
+				continue;
+
+			coverage = coverage_1 + coverage_2 + coverage_3;
+
+			if ((coverage >= (3*line_width * line_width) * LINE_COVERAGE) && (coverage > max_coverage)) {
+				gint p_x, p_y, cov_area;
+
+				/* Is this really a "line", ie. is the surrounding area *not* dark. */
+				cov_area = count_black_pixel(surface,
+				                             x + offset * y_dir - line_width - line_width / 2,
+				                             y + offset * x_dir - line_width - line_width / 2,
+				                             line_width * 3, line_width * 3);
+
+				if ((abs(x - start_x + y - start_y) > line_width*1.5) && cov_area >= 2*coverage)
+					continue;
+
+				found_segment = TRUE;
+				found_offset = offset;
+				max_coverage = coverage;
+				search_length_left = 0;
+
+				p_x = x - line_width*ABS(x_dir) + offset * y_dir;
+				p_y = y - line_width*ABS(y_dir) + offset * x_dir;
+
+				if (ABS(start_x * x_dir + start_y * y_dir) > ABS(p_x * x_dir + p_y * y_dir)) {
+					start_x = p_x;
+					start_y = p_y;
+				}
+
+				p_x = x + line_width*ABS(x_dir) + offset * y_dir;
+				p_y = y + line_width*ABS(y_dir) + offset * x_dir;
+
+				if (ABS(end_x * x_dir + end_y * y_dir) < ABS(p_x * x_dir + p_y * y_dir)) {
+					end_x = p_x;
+					end_y = p_y;
+				}
+			}
+		}
+
+		x += found_offset * y_dir;
+		y += found_offset * x_dir;
+
+		length = sqrt((start_x - end_x)*(start_x - end_x) + (start_y - end_y)*(start_y - end_y));
+		if (length >= line_max_length)
+			goto FOLLOW_LINE_BAIL;
+	}
+
+	found_line = length >= line_length;
+
+	if (found_line) {
+		gint offset;
+		gdouble w1_x, w1_y;
+		gdouble w2_x, w2_y;
+		gdouble weight;
+
+		x = (start_x * 3 + end_x) / 4;
+		y = (start_y * 3 + end_y) / 4;
+		w1_x = 0;
+		w1_y = 0;
+		weight = 0;
+
+		for (offset = - 3 - line_width; offset <= 3 + line_width; offset++) {
+			gint seg_weight;
+			seg_weight = count_black_pixel(surface,
+			                               x + offset * y_dir - MAX(ABS((line_length / 2 - line_width * 2) * x_dir), 1) / 2,
+			                               y + offset * x_dir - MAX(ABS((line_length / 2 - line_width * 2) * y_dir), 1) / 2,
+			                               MAX(ABS((line_length / 2 - line_width * 2) * x_dir), 1),
+			                               MAX(ABS((line_length / 2 - line_width * 2) * y_dir), 1));
+
+			if (weight == 0) { /* this prevents a division by zero if seg_weight is 0 too. */
+				weight = seg_weight;
+				w1_x = x + offset * y_dir + 0.5;
+				w1_y = y + offset * x_dir + 0.5;
+			} else {
+				gdouble seg_x, seg_y;
+				seg_x = x + offset * y_dir + 0.5;
+				seg_y = y + offset * x_dir + 0.5;
+
+				w1_x = w1_x * weight / (weight + seg_weight) + seg_x * seg_weight / (weight + seg_weight);
+				w1_y = w1_y * weight / (weight + seg_weight) + seg_y * seg_weight / (weight + seg_weight);
+				weight += seg_weight;
+			}
+		}
+
+		x = (start_x + end_x * 3) / 4;
+		y = (start_y + end_y * 3) / 4;
+		w2_x = 0;
+		w2_y = 0;
+		weight = 0;
+
+		for (offset = - 3 - line_width; offset <= 3 + line_width; offset++) {
+			gint seg_weight;
+			seg_weight = count_black_pixel(surface,
+			                               x + offset * y_dir - MAX(ABS((line_length / 2 - line_width * 2) * x_dir), 1) / 2,
+			                               y + offset * x_dir - MAX(ABS((line_length / 2 - line_width * 2) * y_dir), 1) / 2,
+			                               MAX(ABS((line_length / 2 - line_width * 2) * x_dir), 1),
+			                               MAX(ABS((line_length / 2 - line_width * 2) * y_dir), 1));
+
+			if (weight == 0) { /* this prevents a division by zero if seg_weight is 0 too. */
+				weight = seg_weight;
+				w2_x = x + offset * y_dir + 0.5;
+				w2_y = y + offset * x_dir + 0.5;
+			} else {
+				gdouble seg_x, seg_y;
+				seg_x = x + offset * y_dir + 0.5;
+				seg_y = y + offset * x_dir + 0.5;
+
+				w2_x = w2_x * weight / (weight + seg_weight) + seg_x * seg_weight / (weight + seg_weight);
+				w2_y = w2_y * weight / (weight + seg_weight) + seg_y * seg_weight / (weight + seg_weight);
+				weight += seg_weight;
+			}
+		}
+
+		/* got two points, now extrapolate them to the line start/end. */
+		*x1 = w1_x - (w2_x - w1_x) / 2.0;
+		*y1 = w1_y - (w2_y - w1_y) / 2.0;
+		*x2 = w2_x - (w1_x - w2_x) / 2.0;
+		*y2 = w2_y - (w1_y - w2_y) / 2.0;
+	}
+
+FOLLOW_LINE_BAIL:
+
+	return found_line;
+}
+
+static void
+calc_intersection(gdouble  l1a_x,    gdouble l1a_y,
+                  gdouble  l1b_x,    gdouble l1b_y,
+                  gdouble  l2a_x,    gdouble l2a_y,
+                  gdouble  l2b_x,    gdouble l2b_y,
+                  gdouble *result_x, gdouble *result_y)
+{
+	gdouble u;
+
+	u = ((l2b_x - l2a_x)*(l1a_y - l2a_y) - (l2b_y - l2a_y)*(l1a_x - l2a_x)) / ((l2b_y - l2a_y)*(l1b_x - l1a_x) - (l2b_x - l2a_x)*(l1b_y - l1a_y));
+	*result_x = l1a_x + u*(l1b_x - l1a_x);
+	*result_y = l1a_y + u*(l1b_y - l1a_y);
+}
+
+#define DIST(x1, y1, x2, y2) sqrt(((x1) - (x2))*((x1) - (x2)) + ((y1) - (y2))*((y1) - (y2)))
+
+static gboolean
+test_corner_marker(cairo_surface_t *surface,
+                   gint             x,
+                   gint             y,
+                   gint             x_dir,
+                   gint             y_dir,
+                   gint             line_width,
+                   gint             line_length,
+                   gint             line_max_length,
+                   gdouble         *x_result,
+                   gdouble         *y_result)
+{
+	gdouble h_x1, h_x2, h_y1, h_y2;
+	gboolean h_found_line;
+	gdouble v_x1, v_x2, v_y1, v_y2;
+	gboolean v_found_line;
+	gint width, height;
+
+	/* We just try to find both right away, even though we can only
+	 * expect to find one of them. */
+	h_found_line = follow_line(surface, x, y, x_dir, 0,
+	                           line_width, line_length, line_max_length,
+	                           &h_x1, &h_y1, &h_x2, &h_y2);
+
+	v_found_line = follow_line(surface, x, y, 0, y_dir,
+	                           line_width, line_length, line_max_length,
+	                           &v_x1, &v_y1, &v_x2, &v_y2);
+
+	if (!(h_found_line || v_found_line))
+		return FALSE;
+
+	if (!h_found_line) {
+		if (y_dir < 0)
+			h_found_line = follow_line(surface, v_x1, v_y1, x_dir, 0,
+			                           line_width, line_length, line_max_length,
+			                           &h_x1, &h_y1, &h_x2, &h_y2);
+		else
+			h_found_line = follow_line(surface, v_x2, v_y2, x_dir, 0,
+			                           line_width, line_length, line_max_length,
+			                           &h_x1, &h_y1, &h_x2, &h_y2);
+	}
+
+	if (!v_found_line) {
+		if (x_dir < 0)
+			v_found_line = follow_line(surface, h_x1, h_y1, 0, y_dir,
+			                           line_width, line_length, line_max_length,
+			                           &v_x1, &v_y1, &v_x2, &v_y2);
+		else
+			v_found_line = follow_line(surface, h_x2, h_y2, 0, y_dir,
+			                           line_width, line_length, line_max_length,
+			                           &v_x1, &v_y1, &v_x2, &v_y2);
+	}
+
+	if (!v_found_line || !h_found_line)
+		return FALSE;
+
+	/* Check that two of the endpoints are close together. */
+	if ((DIST(h_x1, h_y1, v_x1, v_y1) > line_width * 3) &&
+	    (DIST(h_x1, h_y1, v_x2, v_y2) > line_width * 3) &&
+	    (DIST(h_x2, h_y2, v_x1, v_y1) > line_width * 3) &&
+	    (DIST(h_x2, h_y2, v_x2, v_y2) > line_width * 3))
+		return FALSE;
+
+	calc_intersection(h_x1, h_y1, h_x2, h_y2,
+	                  v_x1, v_y1, v_x2, v_y2,
+	                  x_result, y_result);
+
+	/* Check that the resulting position is not on the border of the image. */
+	width = cairo_image_surface_get_width (surface);
+	height = cairo_image_surface_get_height (surface);
+
+	if (*x_result - 3*line_width <= 0)
+		return FALSE;
+	if (*x_result + 3*line_width >= width)
+		return FALSE;
+
+	if (*y_result - 3*line_width <= 0)
+		return FALSE;
+	if (*y_result + 3*line_width >= height)
+		return FALSE;
+
+	return TRUE;
+}
+
+static gboolean
+real_find_corner_marker(cairo_surface_t *surface,
+                        gint             x_start,
+                        gint             y_start,
+                        gint             x_dir,
+                        gint             y_dir,
+                        gint             search_distance,
+                        gint             line_width,
+                        gint             line_length,
+                        gint             line_max_length,
+                        gdouble         *x_result,
+                        gdouble         *y_result)
+{
+	gint x, y;
+	gint distance = 0;
+	gint search;
+	gboolean found = FALSE;
+	gint coverage = 0;
+
+	while (!found && (distance < search_distance)) {
+		distance += line_length / 4;
+
+		/* Try searching from the top/bottom. */
+		coverage = 0;
+		x = x_start + (x_dir * line_width) / 2 + x_dir * distance;
+		y = y_start + (y_dir * line_width) / 2;
+
+		for (search = 0; search < distance; search++) {
+			gint old_coverage = coverage;
+
+			y += y_dir;
+
+			coverage = count_black_pixel(surface,
+			                             x - line_width / 2,
+			                             y - line_width / 2,
+			                             line_width,
+			                             line_width);
+
+			if ((old_coverage > (line_width * line_width) * LINE_COVERAGE) && (old_coverage > coverage)) {
+				if (test_corner_marker(surface, x, y, -x_dir, -y_dir,
+				                       line_width, line_length, line_max_length,
+				                       x_result, y_result))
+					return TRUE;
+			}
+		}
+
+		/* Try searching from the left/right. */
+		coverage = 0;
+		x = x_start + x_dir * line_width / 2;
+		y = y_start + y_dir * line_width / 2 + y_dir * distance;
+
+		for (search = 0; search < distance; search++) {
+			gint old_coverage = coverage;
+
+			x += x_dir;
+
+			coverage = count_black_pixel(surface,
+			                             x - line_width / 2,
+			                             y - line_width / 2,
+			                             line_width,
+			                             line_width);
+
+			if ((old_coverage > (line_width * line_width) * LINE_COVERAGE) && (old_coverage > coverage)) {
+				if (test_corner_marker(surface, x, y, -x_dir, -y_dir,
+				                       line_width, line_length, line_max_length,
+				                       x_result, y_result))
+					return TRUE;
+			}
+		}
+	}
+
+	return FALSE;
+}
+
+
+
+/* Find corner marker */
+gboolean
+find_corner_marker(cairo_surface_t *surface,
+                   cairo_matrix_t  *matrix,
+                   gint             corner,
+                   gdouble         *marker_x,
+                   gdouble         *marker_y)
+{
+	gint width, height;
+	gint line_width;
+	gint line_length;
+	gint line_max_length;
+	gint dx, dy;
+	gint start_x, start_y;
+	gint search_distance;
+
+	line_width = transform_distance_to_pixel(matrix, sdaps_line_width);
+	line_length = transform_distance_to_pixel(matrix, sdaps_line_min_length);
+	line_max_length = transform_distance_to_pixel(matrix, sdaps_line_max_length);
+	search_distance = transform_distance_to_pixel(matrix, sdaps_corner_mark_search_distance);
+
+	width = cairo_image_surface_get_width (surface);
+	height = cairo_image_surface_get_height (surface);
+
+	switch (corner) {
+		case 1:
+			dx = 1;
+			dy = 1;
+			start_x = 0;
+			start_y = 0;
+			break;
+		case 2:
+			dx = -1;
+			dy = 1;
+			start_x = width;
+			start_y = 0;
+			break;
+		case 3:
+			dx = -1;
+			dy = -1;
+			start_x = width;
+			start_y = height;
+			break;
+		case 4:
+			dx = 1;
+			dy = -1;
+			start_x = 0;
+			start_y = height;
+			break;
+		default:
+			g_assert_not_reached();
+	}
+
+	return real_find_corner_marker(surface, start_x, start_y, dx, dy, search_distance, line_width, line_length, line_max_length, marker_x, marker_y);
+}
+
+/*****************************************************************************
+ * calculate_matrix
+ *
+ * This function calculates the matrix based on the corner markers.
+ * NB: The corner markers are lines with the end points on the positions
+ *     passed into this function.
+ *     This is different to boxes, where the coordinates are the bounding
+ *     box of the checkbox!
+ *
+ *****************************************************************************/
+cairo_matrix_t*
+calculate_matrix(cairo_surface_t *surface,
+                 cairo_matrix_t *matrix,
+                 gdouble mm_x,
+                 gdouble mm_y,
+                 gdouble mm_width,
+                 gdouble mm_height)
+{
+	gint width, height;
+	gint line_width;
+	gint line_length;
+	gint line_max_length;
+	gdouble x_topleft, y_topleft;
+	gdouble x_topright, y_topright;
+	gdouble x_bottomleft, y_bottomleft;
+	gdouble x_bottomright, y_bottomright;
+	gdouble x_center, y_center;
+	gdouble dx, dy;
+	gdouble length_squared;
+	gint search_distance;
+	cairo_matrix_t *result;
+	gint found_corners = 4;
+	enum {
+		CORNER_NONE,
+		CORNER_TOP_LEFT,
+		CORNER_TOP_RIGHT,
+		CORNER_BOTTOM_LEFT,
+		CORNER_BOTTOM_RIGHT
+	} missing_corner = CORNER_NONE;
+
+	line_width = transform_distance_to_pixel(matrix, sdaps_line_width);
+	line_length = transform_distance_to_pixel(matrix, sdaps_line_min_length);
+	line_max_length = transform_distance_to_pixel(matrix, sdaps_line_max_length);
+	search_distance = transform_distance_to_pixel(matrix, sdaps_corner_mark_search_distance);
+
+	width = cairo_image_surface_get_width (surface);
+	height = cairo_image_surface_get_height (surface);
+
+	if (!real_find_corner_marker(surface, 0, 0, 1, 1, search_distance, line_width, line_length, line_max_length, &x_topleft, &y_topleft)) {
+		found_corners -= 1;
+		missing_corner = CORNER_TOP_LEFT;
+	}
+	if (!real_find_corner_marker(surface, width - 1, 0, -1, 1, search_distance, line_width, line_length, line_max_length, &x_topright, &y_topright)) {
+		found_corners -= 1;
+		missing_corner = CORNER_TOP_RIGHT;
+	}
+	if (!real_find_corner_marker(surface, 0, height - 1, 1, -1, search_distance, line_width, line_length, line_max_length, &x_bottomleft, &y_bottomleft)) {
+		found_corners -= 1;
+		missing_corner = CORNER_BOTTOM_LEFT;
+	}
+	if (!real_find_corner_marker(surface, width - 1, height - 1,-1, -1, search_distance, line_width, line_length, line_max_length, &x_bottomright, &y_bottomright)) {
+		found_corners -= 1;
+		missing_corner = CORNER_BOTTOM_RIGHT;
+	}
+
+	if (found_corners < 3)
+		return NULL;
+
+	/* Corners are known, now calculate the matrix. */
+
+	result = g_new(cairo_matrix_t, 1);
+
+	/* Simply calculate the missing corner, that seems easier to write down. */
+	if (missing_corner == CORNER_TOP_LEFT) {
+		x_topleft = x_bottomleft - x_bottomright + x_topright;
+		y_topleft = y_topright - y_bottomright + y_bottomleft;
+	}
+	if (missing_corner == CORNER_TOP_RIGHT) {
+		x_topright = x_bottomright - x_bottomleft + x_topleft;
+		y_topright = y_topleft - y_bottomleft + y_bottomright;
+	}
+	if (missing_corner == CORNER_BOTTOM_LEFT) {
+		x_bottomleft = x_topleft - x_topright + x_bottomright;
+		y_bottomleft = y_bottomright - y_topright + y_topleft;
+	}
+	if (missing_corner == CORNER_BOTTOM_RIGHT) {
+		x_bottomright = x_topright - x_topleft + x_bottomleft;
+		y_bottomright = y_bottomleft - y_topleft + y_topright;
+	}
+
+	/* X-Axis *********************/
+	dx = ((x_topright - x_topleft) + (x_bottomright - x_bottomleft)) / 2.0;
+	dy = ((y_topright - y_topleft) + (y_bottomright - y_bottomleft)) / 2.0;
+	length_squared = dx*dx + dy*dy;
+
+	result->xx = dx / length_squared * mm_width;
+	result->yx = -dy / length_squared * mm_width; /* negative for some reason, no idea why right now ... */
+
+	/* Y-Axis *********************/
+	dx = ((x_bottomright - x_topright) + (x_bottomleft - x_topleft)) / 2.0;
+	dy = ((y_bottomright - y_topright) + (y_bottomleft - y_topleft)) / 2.0;
+
+	length_squared = dx*dx + dy*dy;
+	result->xy = -dx / length_squared * mm_height; /* negative for some reason, no idea why right now ... */
+	result->yy = dy / length_squared * mm_height;
+
+	/* Center everything between the markers. */
+	x_center = (x_bottomleft + x_bottomright + x_topleft + x_topright) / 4.0;
+	y_center = (y_bottomleft + y_bottomright + y_topleft + y_topright) / 4.0;
+	/* top/left corner (based on the center) */
+	result->x0 = mm_width / 2.0 + mm_x;
+	result->y0 = mm_height / 2.0 + mm_y;
+
+	dx = x_center * result->xx + y_center * result->xy;
+	dy = x_center * result->yx + y_center * result->yy;
+	result->x0 -= dx;
+	result->y0 -= dy;
+
+	return result;
+}
+
+cairo_matrix_t*
+calculate_correction_matrix_masked(cairo_surface_t  *surface,
+                                   cairo_surface_t  *mask,
+                                   cairo_matrix_t   *matrix,
+                                   gdouble           mm_x,
+                                   gdouble           mm_y,
+                                   gdouble          *covered)
+{
+	gdouble tmp_x, tmp_y;
+	gint px_x, px_y, px_width, px_height;
+	cairo_matrix_t inverse;
+	cairo_matrix_t *result = NULL;
+	gint test_dist;
+	gint x_offset, y_offset;
+	gint x_cov;
+	gint y_cov;
+	gint coverage = 0;
+
+	inverse = *matrix;
+	cairo_matrix_invert(&inverse);
+
+	tmp_x = mm_x;
+	tmp_y = mm_y;
+	cairo_matrix_transform_point(matrix, &tmp_x, &tmp_y);
+	px_x = tmp_x;
+	px_y = tmp_y;
+
+	/* Shut up the compiler. */
+	x_cov = px_x;
+	y_cov = px_y;
+
+	px_width = cairo_image_surface_get_width(mask);
+	px_height = cairo_image_surface_get_height(mask);
+
+	test_dist = MIN(px_width, px_height) / 2;
+
+	/* Top */
+	for (x_offset = -test_dist; x_offset <= test_dist; x_offset++) {
+		for (y_offset = -test_dist; y_offset <= test_dist; y_offset++) {
+			gint new_cov;
+
+			new_cov = count_black_pixel_masked(surface, mask, px_x + x_offset, px_y + y_offset);
+			if (coverage < new_cov) {
+				coverage = new_cov;
+				x_cov = px_x + x_offset;
+				y_cov = px_y + y_offset;
+			}
+		}
+	}
+
+	tmp_x = x_cov;
+	tmp_y = y_cov;
+	cairo_matrix_transform_point(&inverse, &tmp_x, &tmp_y);
+
+	result = g_new(cairo_matrix_t, 1);
+	cairo_matrix_init_identity(result);
+
+	/* Just a translation */
+	result->x0 = tmp_x - mm_x;
+	result->y0 = tmp_y - mm_y;
+
+	*covered = coverage / (float) count_black_pixel(mask, 0, 0, px_width, px_height);
+
+	return result;
+}
+
+gboolean
+find_box_corners(cairo_surface_t  *surface,
+                 cairo_matrix_t   *matrix,
+                 gdouble           mm_x,
+                 gdouble           mm_y,
+                 gdouble           mm_width,
+                 gdouble           mm_height,
+                 gdouble          *mm_x1,
+                 gdouble          *mm_y1,
+                 gdouble          *mm_x2,
+                 gdouble          *mm_y2,
+                 gdouble          *mm_x3,
+                 gdouble          *mm_y3,
+                 gdouble          *mm_x4,
+                 gdouble          *mm_y4)
+{
+	cairo_matrix_t inverse;
+	gdouble px_x1, px_y1, px_x2, px_y2, px_x3, px_y3, px_x4, px_y4;
+	gdouble px_width, px_height;
+	gint line_width;
+	gint line_length;
+	gint line_max_length;
+	gint search_distance;
+
+	line_width = transform_distance_to_pixel(matrix, sdaps_line_width);
+
+	inverse = *matrix;
+	cairo_matrix_invert(&inverse);
+
+	/* Assume that the image is loaded rotated and pixel with/height is positive. */
+	px_x1 = mm_x;
+	px_y1 = mm_y;
+	px_x2 = mm_x + mm_width;
+	px_y2 = mm_y;
+	px_x3 = mm_x + mm_width;
+	px_y3 = mm_y + mm_height;
+	px_x4 = mm_x;
+	px_y4 = mm_y + mm_height;
+
+	px_width = mm_width;
+	px_height = mm_height;
+
+	cairo_matrix_transform_point(matrix, &px_x1, &px_y1);
+	cairo_matrix_transform_point(matrix, &px_x2, &px_y2);
+	cairo_matrix_transform_point(matrix, &px_x3, &px_y3);
+	cairo_matrix_transform_point(matrix, &px_x4, &px_y4);
+
+	cairo_matrix_transform_distance(matrix, &px_width, &px_height);
+
+	line_length = MIN(10*line_width, MIN(px_width, px_height)) - line_width;
+	line_max_length = MAX(10*line_width, MAX(px_width, px_height)) + 5*line_width;
+	search_distance = line_length;
+	/* We have the corner pixel positions, now try to find them. */
+
+	if (!real_find_corner_marker(surface, px_x1 - 4*line_width, px_y1 - 4*line_width, 1, 1, search_distance, line_width, line_length, line_max_length, &px_x1, &px_y1))
+		return FALSE;
+	if (!real_find_corner_marker(surface, px_x2 + 4*line_width, px_y2 - 4*line_width, -1, 1, search_distance, line_width, line_length, line_max_length, &px_x2, &px_y2))
+		return FALSE;
+	if (!real_find_corner_marker(surface, px_x3 + 4*line_width, px_y3 + 4*line_width, -1, -1, search_distance, line_width, line_length, line_max_length, &px_x3, &px_y3))
+		return FALSE;
+	if (!real_find_corner_marker(surface, px_x4 - 4*line_width, px_y4 + 4*line_width, 1, -1, search_distance, line_width, line_length, line_max_length, &px_x4, &px_y4))
+		return FALSE;
+
+	/* Found the corners, convert them back and return. */
+	*mm_x1 = px_x1;
+	*mm_y1 = px_y1;
+	*mm_x2 = px_x2;
+	*mm_y2 = px_y2;
+	*mm_x3 = px_x3;
+	*mm_y3 = px_y3;
+	*mm_x4 = px_x4;
+	*mm_y4 = px_y4;
+
+	cairo_matrix_transform_point(&inverse, mm_x1, mm_y1);
+	cairo_matrix_transform_point(&inverse, mm_x2, mm_y2);
+	cairo_matrix_transform_point(&inverse, mm_x3, mm_y3);
+	cairo_matrix_transform_point(&inverse, mm_x4, mm_y4);
+
+	return TRUE;
+}
+
+float
+get_coverage(cairo_surface_t *surface,
+             cairo_matrix_t  *matrix,
+             gdouble          mm_x,
+             gdouble          mm_y,
+             gdouble          mm_width,
+             gdouble          mm_height)
+{
+	gint x, y, width, height;
+	gdouble tmp_x, tmp_y;
+	gint black, all;
+
+	/* Transform to pixel. */
+	tmp_x = mm_x;
+	tmp_y = mm_y;
+	cairo_matrix_transform_point(matrix, &tmp_x, &tmp_y);
+	x = tmp_x;
+	y = tmp_y;
+
+	tmp_x = mm_width;
+	tmp_y = mm_height;
+	cairo_matrix_transform_distance(matrix, &tmp_x, &tmp_y);
+	width = tmp_x;
+	height = tmp_y;
+
+	black = count_black_pixel(surface, x, y, width, height);
+	all = width * height;
+
+	if (sdaps_create_debug_surface) {
+		cairo_surface_t *surf = debug_surface_create(x, y, width, height, 0, 0, 0, 0);
+		cairo_t *cr = cairo_create(surf);
+
+		cairo_set_source_rgba(cr, 1, 0, 0, 0.5);
+		cairo_paint(cr);
+
+		cairo_destroy(cr);
+		cairo_surface_flush(surf);
+	}
+
+	return black / (double) all;
+}
+
+float
+get_masked_coverage(cairo_surface_t *surface,
+                    cairo_surface_t *mask,
+                    gint             x,
+                    gint             y)
+{
+	gint width, height;
+	gint black, all;
+
+	width = cairo_image_surface_get_width(mask);
+	height = cairo_image_surface_get_height(mask);
+
+	all = count_black_pixel(mask, 0, 0, width, height);
+	black = count_black_pixel_masked(surface, mask, x, y);
+
+	if (sdaps_create_debug_surface) {
+		cairo_surface_t *surf = debug_surface_create(x, y, width, height, 0, 0, 0, 0);
+		cairo_t *cr = cairo_create(surf);
+
+		cairo_set_source_rgba(cr, 1, 0, 0, 0.5);
+		cairo_mask_surface(cr, mask, 0, 0);
+
+		cairo_destroy(cr);
+		cairo_surface_flush(surf);
+	}
+
+	return black / (double) all;
+}
+
+/* First removes the number of lines, and then calculates the coverage of what
+ * is left. */
+gdouble
+get_masked_coverage_without_lines(cairo_surface_t *surface,
+                                  cairo_surface_t *mask,
+                                  gint             x,
+                                  gint             y,
+                                  gdouble          line_width,
+                                  gint             line_count)
+{
+	cairo_surface_t *tmp_surface;
+	cairo_surface_t *debug_surf;
+	gint width, height;
+	gdouble result;
+	gint i, all;
+
+	width = cairo_image_surface_get_width(mask);
+	height = cairo_image_surface_get_height(mask);
+
+	all = count_black_pixel(mask, 0, 0, width, height);
+
+	tmp_surface = surface_copy_masked(surface, mask, x, y);
+
+	debug_surf = debug_surface_create(x, y, width, height, 0, 0, 0, 0);
+	if (debug_surf) {
+		cairo_t *cr;
+		cr = cairo_create(debug_surf);
+		cairo_set_source_rgba(cr, 0, 0, 1, 0.5);
+		cairo_mask_surface(cr, mask, 0, 0);
+
+		cairo_destroy(cr);
+		cairo_surface_flush(debug_surf);
+	}
+
+#if 0
+	/* Something like this could be used to filter the image first.
+	 * Obviously, for that to work, the size of the surface needs to be
+	 * adjusted accordingly. */
+	kfill_modified(tmp_surface, 5);
+
+	cr = cairo_create(tmp_surface);
+	cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
+	cairo_rectangle(cr, 0, 0, width+4, height+4);
+	cairo_rectangle(cr, 2, 2, width, height);
+	cairo_set_source_rgba(cr, 0, 0, 0, 0);
+	cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
+	cairo_fill(cr);
+	cairo_destroy(cr);
+	cairo_surface_flush(tmp_surface);
+#endif
+
+	/* Remove the requested number of lines. */
+	for (i = 0; i < line_count; i++)
+		remove_maximum_line(tmp_surface, debug_surf, line_width);
+
+	result = count_black_pixel(tmp_surface, 0, 0, width, height) / (gdouble) all;
+
+	cairo_surface_destroy(tmp_surface);
+
+	return result;
+}
+
+guint
+get_masked_white_area_count(cairo_surface_t *surface,
+                            cairo_surface_t *mask,
+                            gint             x,
+                            gint             y,
+                            gdouble          min_size,
+                            gdouble          max_size,
+                            gdouble         *filled_area)
+{
+	cairo_surface_t *tmp_surface;
+	cairo_surface_t *backup_surface;
+	cairo_surface_t *debug_surf;
+	cairo_t *backup_cr;
+	gint width, height;
+	guint result = 0;
+	guint min_size_px;
+	guint max_size_px;
+	guint all;
+
+	width = cairo_image_surface_get_width(mask);
+	height = cairo_image_surface_get_height(mask);
+
+	all = count_black_pixel(mask, 0, 0, width, height);
+
+	min_size_px = all * min_size;
+	max_size_px = all * max_size;
+
+	tmp_surface = surface_inverted_copy_masked(surface, mask, x, y);
+
+	/* Debug images */
+	debug_surf = debug_surface_create(x, y, width, height, 0, 0, 0, 0);
+	if (debug_surf != NULL) {
+		cairo_t *debug_cr;
+
+		backup_surface = surface_copy(tmp_surface);
+		backup_cr = cairo_create(backup_surface);
+		cairo_set_operator(backup_cr, CAIRO_OPERATOR_SOURCE);
+
+		debug_cr = cairo_create(debug_surf);
+		cairo_set_source_rgba(debug_cr, 0, 0, 1, 0.5);
+		cairo_mask_surface(debug_cr, tmp_surface, 0, 0);
+
+		cairo_destroy(debug_cr);
+		cairo_surface_flush(debug_surf);
+	}
+
+	*filled_area = 0;
+
+	for (y = 0; y < height; y++) {
+		for (x = 0; x < width; x++) {
+			if (debug_surf != NULL) {
+				cairo_set_source_surface(backup_cr, tmp_surface, 0, 0);
+				cairo_paint(backup_cr);
+			}
+
+			guint area = flood_fill(tmp_surface, NULL, x, y, 1);
+			if ((area >= min_size_px) && (area <= max_size_px)) {
+				result += 1;
+				*filled_area += area / ((gdouble) all);
+
+				/* Flood fill again, this time also mark the area on the debug surface. */
+				if (debug_surf)
+					flood_fill(backup_surface, debug_surf, x, y, 1);
+			}
+		}
+	}
+
+	if (debug_surf != NULL) {
+		cairo_surface_destroy(backup_surface);
+		cairo_destroy(backup_cr);
+	}
+
+	cairo_surface_destroy(tmp_surface);
+
+	return result;
+}
+
+
diff --git a/sdaps/image/image.h b/sdaps/image/image.h
new file mode 100644
index 0000000..715a8f6
--- /dev/null
+++ b/sdaps/image/image.h
@@ -0,0 +1,79 @@
+/* SDAPS
+ * Copyright (C) 2008  Benjamin Berg <benjamin at sipsolutions.net>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <glib.h>
+#include <cairo.h>
+
+/* Some of the more important Magic Values */
+extern gdouble sdaps_line_min_length;
+extern gdouble sdaps_line_max_length;
+extern gdouble sdaps_line_width;
+extern gdouble sdaps_corner_mark_search_distance;
+extern gdouble sdaps_line_coverage;
+
+extern gboolean sdaps_create_debug_surface;
+extern gint sdaps_debug_surface_ox;
+extern gint sdaps_debug_surface_oy;
+extern cairo_surface_t *sdaps_debug_surface;
+
+void
+disable_libtiff_warnings (void);
+
+cairo_surface_t*
+get_a1_from_tiff (const char *filename, gint page, gboolean rotated);
+
+gboolean
+write_a1_to_tiff (const char *filename, cairo_surface_t *surf);
+
+cairo_surface_t*
+get_rgb24_from_tiff (const char *filename, gint page, gboolean rotated);
+
+gint
+get_tiff_page_count (const char *filename);
+
+gboolean
+get_tiff_resolution (const char *filename, gint page, gdouble *xresolution, gdouble *yresolution);
+
+gboolean
+check_tiff_monochrome (const char *filename);
+
+gboolean
+find_corner_marker(cairo_surface_t *surface, cairo_matrix_t *matrix, gint corner, gdouble *marker_x, gdouble *marker_y);
+
+cairo_matrix_t*
+calculate_matrix(cairo_surface_t *surface, cairo_matrix_t *matrix, gdouble mm_x, gdouble mm_y, gdouble mm_width, gdouble mm_height);
+
+cairo_matrix_t*
+calculate_correction_matrix_masked(cairo_surface_t *surface, cairo_surface_t *mask, cairo_matrix_t *matrix, gdouble mm_x, gdouble mm_y, gdouble *covered);
+
+gboolean
+find_box_corners(cairo_surface_t *surface, cairo_matrix_t *matrix, gdouble mm_x, gdouble mm_y, gdouble mm_width, gdouble mm_height,
+                 gdouble *mm_x1, gdouble *mm_y1, gdouble *mm_x2, gdouble *mm_y2, gdouble *mm_x3, gdouble *mm_y3, gdouble *mm_x4, gdouble *mm_y4);
+
+float
+get_coverage(cairo_surface_t *surface, cairo_matrix_t *matrix, gdouble mm_x, gdouble mm_y, gdouble mm_width, gdouble mm_height);
+
+float
+get_masked_coverage(cairo_surface_t *surface, cairo_surface_t *mask, gint x, gint y);
+
+gdouble
+get_masked_coverage_without_lines(cairo_surface_t *surface, cairo_surface_t *mask, gint x, gint y, gdouble line_width, gint line_count);
+
+guint
+get_masked_white_area_count(cairo_surface_t *surface, cairo_surface_t *mask, gint x, gint y, gdouble min_size, gdouble max_size, gdouble *filled_area);
+
diff --git a/sdaps/image/surface.c b/sdaps/image/surface.c
new file mode 100644
index 0000000..0f61dc4
--- /dev/null
+++ b/sdaps/image/surface.c
@@ -0,0 +1,373 @@
+/* SDAPS
+ * Copyright (C) 2012  Benjamin Berg <benjamin at sipsolutions.net>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "string.h"
+#include "surface.h"
+
+#define WORD_COUNT_BITS(x) (bitcount_table[(x) & 0xff] + bitcount_table[((x) >> 8) & 0xff] + bitcount_table[((x) >> 16) & 0xff] + bitcount_table[((x) >> 24) & 0xff])
+
+static guint8 bitcount_table[256];
+static gint bitcount_table_initialized = FALSE;
+
+static void
+ensure_bitcount_table(void)
+{
+	if (G_UNLIKELY(!bitcount_table_initialized)) {
+		int i;
+
+		for (i = 0; i < 256; i++) {
+			int j;
+			bitcount_table[i] = 0;
+			for (j = i; j; j = j >> 1) {
+				bitcount_table[i] += j & 0x1;
+			}
+		}
+		bitcount_table_initialized = TRUE;
+	}
+}
+
+cairo_surface_t*
+surface_copy_partial(cairo_surface_t *surface, int x, int y, int width, int height)
+{
+	cairo_surface_t *result;
+	cairo_t *cr;
+
+	result = cairo_image_surface_create(cairo_image_surface_get_format(surface), width, height);
+	cr = cairo_create(result);
+
+	cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
+	/* In case the area is outside of the source image, fill with zeros. */
+	cairo_set_source_rgba(cr, 0, 0, 0, 0);
+	cairo_paint(cr);
+
+	cairo_set_source_surface(cr, surface, -x, -y);
+	cairo_paint(cr);
+
+	cairo_destroy(cr);
+
+	cairo_surface_flush(result);
+
+	return result;
+}
+
+cairo_surface_t*
+surface_copy(cairo_surface_t *surface)
+{
+	int width, height;
+	width = cairo_image_surface_get_width(surface);
+	height = cairo_image_surface_get_height(surface);
+
+	return surface_copy_partial(surface, 0, 0, width, height);
+}
+
+cairo_surface_t*
+surface_copy_masked(cairo_surface_t *surface, cairo_surface_t *mask, gint x, gint y)
+{
+	gint width, height;
+	gint word_width;
+	cairo_surface_t *result;
+	gint result_stride, mask_stride;
+	guint32 *result_pixels, *mask_pixels;
+
+	width = cairo_image_surface_get_width(mask);
+	height = cairo_image_surface_get_height(mask);
+
+	result = surface_copy_partial(surface, x, y, width, height);
+
+	result_pixels = (guint32*) cairo_image_surface_get_data(result);
+	result_stride = cairo_image_surface_get_stride(result);
+	mask_pixels = (guint32*) cairo_image_surface_get_data(mask);
+	mask_stride = cairo_image_surface_get_stride(mask);
+
+	word_width = (width + 31) / 32;
+	for (y = 0; y < height; y++) {
+		for (x = 0; x < word_width; x++) {
+			result_pixels[x + y*result_stride/4] &= mask_pixels[x + y*mask_stride/4];
+		}
+	}
+
+	cairo_surface_mark_dirty(result);
+
+	return result;
+}
+
+
+cairo_surface_t*
+surface_inverted_copy_masked(cairo_surface_t *surface, cairo_surface_t *mask, gint x, gint y)
+{
+	gint width, height;
+	gint word_width;
+	cairo_surface_t *result;
+	gint result_stride, mask_stride;
+	guint32 *result_pixels, *mask_pixels;
+
+	width = cairo_image_surface_get_width(mask);
+	height = cairo_image_surface_get_height(mask);
+
+	result = surface_copy_partial(surface, x, y, width, height);
+
+	result_pixels = (guint32*) cairo_image_surface_get_data(result);
+	result_stride = cairo_image_surface_get_stride(result);
+	mask_pixels = (guint32*) cairo_image_surface_get_data(mask);
+	mask_stride = cairo_image_surface_get_stride(mask);
+
+	word_width = (width + 31) / 32;
+	for (y = 0; y < height; y++) {
+		for (x = 0; x < word_width; x++) {
+			result_pixels[x + y*result_stride/4] = ~result_pixels[x + y*result_stride/4] & mask_pixels[x + y*mask_stride/4];
+		}
+	}
+
+	cairo_surface_mark_dirty(result);
+
+	return result;
+}
+
+
+/* Generic pixel routines */
+void
+set_pixels_unchecked(guint32* pixels, guint32 stride, gint x, gint y, gint width, gint height, int value)
+{
+	guint x_pos, y_pos;
+
+	for (y_pos = y; y_pos < y + height; y_pos++) {
+		for (x_pos = x; x_pos < x + width; x_pos++) {
+			SET_PIXEL(pixels, stride, x_pos, y_pos, value);
+		}
+	}
+}
+
+void
+get_pbm(cairo_surface_t *surface, void **data, int *length)
+{
+	int width, height;
+	int s_stride;
+	int d_stride;
+	unsigned char* s_pixel;
+	unsigned char* d_pixel;
+	char *start;
+	int x, y;
+
+	*data = NULL;
+	*length = 0;
+
+	if (cairo_image_surface_get_format (surface) != CAIRO_FORMAT_A1)
+		return;
+
+	width = cairo_image_surface_get_width(surface);
+	height = cairo_image_surface_get_height(surface);
+	s_stride = cairo_image_surface_get_stride(surface);
+	s_pixel = cairo_image_surface_get_data(surface);
+
+	start = g_strdup_printf("P4\n%i %i\n", width, height);
+	d_stride = (width + 7) / 8;
+	*length = strlen(start) + height * d_stride;
+	*data = g_malloc0(*length);
+	strcpy(*data, start);
+	d_pixel = *data + strlen(start);
+	g_free(start);
+
+	for (y = 0; y < height; y++) {
+		for (x = 0; x < width; x++) {
+			*(d_pixel + y*d_stride + x / 8) |= (GET_PIXEL(s_pixel, s_stride, x, y)) << (7 - x % 8);
+		}
+	}
+}
+
+gint
+count_black_pixel(cairo_surface_t *surface, gint x, gint y, gint width, gint height)
+{
+	guint32 *pixels;
+	guint stride;
+	guint img_width, img_height;
+
+	pixels = (guint32*) cairo_image_surface_get_data(surface);
+	img_width = cairo_image_surface_get_width(surface);
+	img_height = cairo_image_surface_get_height(surface);
+	stride = cairo_image_surface_get_stride(surface);
+
+	if (y < 0) {
+		height += y;
+		y = 0;
+	}
+	if (x < 0) {
+		width += x;
+		x = 0;
+	}
+	if ((width <= 0) || (height <= 0))
+		return 0;
+	if (x + width > img_width) {
+		width = img_width - x;
+	}
+	if (y + height > img_height) {
+		height = img_height - y;
+	}
+
+	return count_black_pixel_unchecked(pixels, stride, x, y, width, height);
+}
+
+gint
+count_black_pixel_unchecked(guint32* pixels, guint32 stride, gint x, gint y, gint width, gint height)
+{
+	guint y_pos;
+	guint black_pixel = 0;
+
+	ensure_bitcount_table();
+
+	for (y_pos = y; y_pos < y + height; y_pos++) {
+
+		guint32 start_mask;
+		guint32 end_mask;
+		int start;
+		int end;
+		int pos;
+
+#if G_BYTE_ORDER == G_BIG_ENDIAN
+		start_mask = (1 << (x & 0x1f)) - 1;
+		end_mask = -(1 << ((x + width) & 0x1f));
+#else
+		start_mask = -(1 << (x & 0x1f));
+		end_mask = (1 << ((x + width) & 0x1f)) - 1;
+#endif
+		start = x >> 5;
+		end = (x + width) >> 5;
+
+		if (start == end) {
+			black_pixel += WORD_COUNT_BITS(pixels[start + y_pos * stride / 4] & start_mask & end_mask);
+		} else {
+			black_pixel += WORD_COUNT_BITS(pixels[start + y_pos * stride / 4] & start_mask);
+			for (pos = start + 1; pos < end; pos++) {
+				black_pixel += WORD_COUNT_BITS(pixels[pos + y_pos * stride / 4]);
+			}
+			black_pixel += WORD_COUNT_BITS(pixels[end + y_pos * stride / 4] & end_mask);
+		}
+	}
+
+	return black_pixel;
+}
+
+gint
+count_black_pixel_masked(cairo_surface_t *surface, cairo_surface_t *mask, gint x, gint y)
+{
+	guint32 *pixels;
+	guint32 *mask_pixels;
+	guint stride;
+	guint mask_stride;
+	gint width, height;
+	guint img_width, img_height;
+
+	width = cairo_image_surface_get_width(mask);
+	height = cairo_image_surface_get_height(mask);
+	mask_pixels = (guint32*) cairo_image_surface_get_data(mask);
+	mask_stride = cairo_image_surface_get_stride(mask);
+
+	pixels = (guint32*) cairo_image_surface_get_data(surface);
+	img_width = cairo_image_surface_get_width(surface);
+	img_height = cairo_image_surface_get_height(surface);
+	stride = cairo_image_surface_get_stride(surface);
+
+	/* Ignore if the mask is not completely in the image ... */
+	if (y < 0) {
+		return 0;
+	}
+	if (x < 0) {
+		return 0;
+	}
+	if ((width <= 0) || (height <= 0))
+		return 0;
+	if (x + width > img_width) {
+		return 0;
+	}
+	if (y + height > img_height) {
+		return 0;
+	}
+
+	return count_black_pixel_masked_unchecked(pixels, stride, mask_pixels, mask_stride, x, y, width, height);
+}
+
+gint
+count_black_pixel_masked_unchecked(guint32* pixels, guint32 stride, guint32 *mask_pixels, guint32 mask_stride, gint x, gint y, gint width, gint height)
+{
+	guint y_pos;
+	guint black_pixel = 0;
+
+	ensure_bitcount_table();
+
+	for (y_pos = 0; y_pos < height; y_pos++) {
+		guint32 end_mask;
+		guint32 curr_pixels;
+		int end;
+		int pos;
+
+#if G_BYTE_ORDER == G_BIG_ENDIAN
+		end_mask = -(1 << (width & 0x1f));
+#else
+		end_mask = (1 << (width & 0x1f)) - 1;
+#endif
+		end = width >> 5;
+
+		for (pos = 0; pos <= end; pos++) {
+			/* Note that a shift of 32 is not defined, it may also be 0. */
+#if G_BYTE_ORDER == G_BIG_ENDIAN
+			curr_pixels = pixels[(x / 32) + pos + (y_pos + y) * stride / 4] << (x % 32);
+			curr_pixels |= pixels[((x + 31) / 32) + pos + (y_pos + y) * stride / 4] >> (32 - (x % 32));
+#else
+			curr_pixels = pixels[(x / 32) + pos + (y_pos + y) * stride / 4] >> (x % 32);
+			curr_pixels |= pixels[((x + 31) / 32) + pos + (y_pos + y) * stride / 4] << (32 - (x % 32));
+#endif
+
+			curr_pixels &= mask_pixels[pos + y_pos * mask_stride / 4];
+
+			if (pos == end)
+				curr_pixels &= end_mask;
+
+			black_pixel += WORD_COUNT_BITS(curr_pixels);
+		}
+	}
+
+	return black_pixel;
+}
+
+#if 0
+/* This function is for debugging purposes. */
+void
+a1_surface_write_to_png(cairo_surface_t* surface, gchar* filename)
+{
+	guint img_width, img_height;
+	cairo_surface_t *tmp_surface;
+	cairo_t *cr;
+
+	img_width = cairo_image_surface_get_width(surface);
+	img_height = cairo_image_surface_get_height(surface);
+
+	tmp_surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, img_width, img_height);
+
+	cr = cairo_create(tmp_surface);
+	cairo_set_source_rgb(cr, 1, 1, 1);
+	cairo_paint(cr);
+	cairo_set_source_rgb(cr, 0, 0, 0);
+	cairo_mask_surface(cr, surface, 0, 0);
+
+	cairo_surface_write_to_png(tmp_surface, filename);
+
+	cairo_destroy(cr);
+	cairo_surface_destroy(tmp_surface);
+}
+#endif
+
+
+
diff --git a/sdaps/image/surface.h b/sdaps/image/surface.h
new file mode 100644
index 0000000..8d0fa6a
--- /dev/null
+++ b/sdaps/image/surface.h
@@ -0,0 +1,76 @@
+/* SDAPS
+ * Copyright (C) 2012  Benjamin Berg <benjamin at sipsolutions.net>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+#include <cairo.h>
+
+#if G_BYTE_ORDER == G_BIG_ENDIAN
+#define SET_PIXEL(_pixels, _stride, _x, _y, _value) *(guint32*)((char*)(_pixels) + (_stride) * (_y) + (_x) / 32 * 4) = (*(guint32*)((char*)(_pixels) + (_stride) * (_y) + (_x) / 32 * 4) & (0xffffffff ^ (1 << (31 - (_x) % 32)))) | ((!!(_value)) << (31 - (_x) % 32))
+#define GET_PIXEL(_pixels, _stride, _x, _y) (((*(guint32*)((char*)(_pixels) + (_stride) * (_y) + (_x) / 32 * 4)) >> (31 - (_x) % 32)) & 0x1)
+
+#define BARREL_VARS guint32* _barrel_curr_pos; guint _barrel_bits; guint32 _barrel_value_storage;
+#define BARREL_START_ROW(_row_ptr) _barrel_value_storage = 0; _barrel_bits = 0; _barrel_curr_pos = (guint32*)(_row_ptr);
+#define BARREL_STORE_BIT(_value) _barrel_bits++; _barrel_value_storage = (_value) | _barrel_value_storage << 1; if (_barrel_bits == 32) { *_barrel_curr_pos = _barrel_value_storage; _barrel_curr_pos++; _barrel_bits = 0; };
+#define BARREL_FLUSH if (_barrel_bits > 0) { *_barrel_curr_pos = _barrel_value_storage << (32-_barrel_bits); _barrel_curr_pos++; _barrel_bits = 0; }
+
+#else
+#define SET_PIXEL(_pixels, _stride, _x, _y, _value) *(guint32*)((char*)(_pixels) + (_stride) * (_y) + (_x) / 32 * 4) = (*(guint32*)((char*)(_pixels) + (_stride) * (_y) + (_x) / 32 * 4) & (0xffffffff ^ (1 << ((_x) % 32)))) | ((!!(_value)) << ((_x) % 32))
+#define GET_PIXEL(_pixels, _stride, _x, _y) (((*(guint32*)((char*)(_pixels) + (_stride) * (_y) + (_x) / 32 * 4)) >> ((_x) % 32)) & 0x1)
+
+#define BARREL_VARS guint32* _barrel_curr_pos; guint _barrel_bits; guint32 _barrel_value_storage;
+#define BARREL_START_ROW(_row_ptr) _barrel_value_storage = 0; _barrel_bits = 0; _barrel_curr_pos = (guint32*)(_row_ptr);
+#define BARREL_STORE_BIT(_value) _barrel_bits++; _barrel_value_storage = (_value) << 31 | _barrel_value_storage >> 1; if (_barrel_bits == 32) { *_barrel_curr_pos = _barrel_value_storage; _barrel_curr_pos++; _barrel_bits = 0; };
+#define BARREL_FLUSH if (_barrel_bits > 0) { *_barrel_curr_pos = _barrel_value_storage >> (32-_barrel_bits); _barrel_curr_pos++; _barrel_bits = 0; }
+#endif
+
+
+cairo_surface_t*
+surface_copy_partial(cairo_surface_t *surface, int x, int y, int width, int height);
+
+cairo_surface_t*
+surface_copy(cairo_surface_t *surface);
+
+cairo_surface_t*
+surface_copy_masked(cairo_surface_t *surface, cairo_surface_t *mask, gint x, gint y);
+
+cairo_surface_t*
+surface_inverted_copy_masked(cairo_surface_t *surface, cairo_surface_t *mask, gint x, gint y);
+
+void
+get_pbm(cairo_surface_t *surface, void **data, int *length);
+
+#if 0
+void
+a1_surface_write_to_png(cairo_surface_t* surface, gchar* filename);
+#endif
+
+gint
+count_black_pixel(cairo_surface_t *surface, gint x, gint y, gint width, gint height);
+
+gint
+count_black_pixel_masked(cairo_surface_t *surface, cairo_surface_t *mask, gint x, gint y);
+
+gint
+count_black_pixel_unchecked(guint32* pixels, guint32 stride, gint x, gint y, gint width, gint height);
+
+gint
+count_black_pixel_masked_unchecked(guint32* pixels, guint32 stride, guint32 *mask_pixels, guint32 mask_stride, gint x, gint y, gint width, gint height);
+
+void
+set_pixels_unchecked(guint32* pixels, guint32 stride, gint x, gint y, gint width, gint height, int value);
+
+
diff --git a/sdaps/image/transform.c b/sdaps/image/transform.c
new file mode 100644
index 0000000..24283ad
--- /dev/null
+++ b/sdaps/image/transform.c
@@ -0,0 +1,556 @@
+/* SDAPS
+ * Copyright (C) 2012  Benjamin Berg <benjamin at sipsolutions.net>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "transform.h"
+#include "surface.h"
+#include <string.h>
+#include <math.h>
+
+
+/* The following is an adaption from gamera. It is a modified
+ * kfill algorithm.
+ *
+ * The original code was released under the GPLv2 or any later version.
+ *
+ * The Gamera authors for the file are:
+ * Copyright (C) 2001-2005 Ichiro Fujinaga, Michael Droettboom, Karl MacMillan
+ *               2010      Christoph Dalitz, Oliver Christen
+ *               2011-2012 Christoph Dalitz, David Kolanus
+ *
+ * The same algorithm is used in queXF for character recognition. */
+
+void
+kfill_get_condition_variables(guint32 *pixels,
+                              guint    stride,
+                              guint    k,
+                              guint    x,
+                              guint    y,
+                              gint    *n,
+                              gint    *r,
+                              gint    *c)
+{
+	guint corner_pixel_count;
+	guint ccs;
+	guint pixel_count;
+	guint pixel, last_px;
+	guint _x, _y;
+
+
+	corner_pixel_count = GET_PIXEL(pixels, stride, x, y);
+	corner_pixel_count += GET_PIXEL(pixels, stride, x + k - 1, y);
+	corner_pixel_count += GET_PIXEL(pixels, stride, x, y + k - 1);
+	corner_pixel_count += GET_PIXEL(pixels, stride, x + k - 1, y + k - 1);
+
+	/* Count border pixels and number of color changes when walking around
+	 * the border. */
+	ccs = 0;
+	pixel_count = 0;
+	/* The last pixel is also the "first". */
+	last_px = GET_PIXEL(pixels, stride, x, y + 1);
+	_y = y;
+	_x = x;
+
+	for (; _x < x + k - 1; _x++) {
+		pixel = GET_PIXEL(pixels, stride, _x, _y);
+		if (last_px != pixel)
+			ccs += 1;
+		pixel_count += pixel;
+		last_px = pixel;
+	}
+
+	for (; _y < y + k - 1; _y++) {
+		pixel = GET_PIXEL(pixels, stride, _x, _y);
+		if (last_px != pixel)
+			ccs += 1;
+		pixel_count += pixel;
+		last_px = pixel;
+	}
+
+	for (; _x > x; _x--) {
+		pixel = GET_PIXEL(pixels, stride, _x, _y);
+		if (last_px != pixel)
+			ccs += 1;
+		pixel_count += pixel;
+		last_px = pixel;
+	}
+
+	for (; _y > y; _y--) {
+		pixel = GET_PIXEL(pixels, stride, _x, _y);
+		if (last_px != pixel)
+			ccs += 1;
+		pixel_count += pixel;
+		last_px = pixel;
+	}
+
+	*n = pixel_count;
+	*r = corner_pixel_count;
+	*c = ccs;
+}
+
+/* This implementation of kfill_modified works in place. */
+void
+kfill_modified(cairo_surface_t* surface, gint k)
+{
+	guint x, y;
+	guint img_width, img_height;
+	guint stride, tmp_stride;
+	guint32 *pixels, *tmp_pixels;
+	guint core_pixels;
+	guint core_color;
+
+	/* r: number of pixels in the neighborhood corners
+	 * n: number of pixels in the neighberhood
+	 * c: number of ccs in neighborhood
+	 */
+	gint r, n, c;
+
+	cairo_surface_t *tmp_surface;
+	tmp_surface = surface_copy(surface);
+
+	img_width = cairo_image_surface_get_width(surface);
+	img_height = cairo_image_surface_get_height(surface);
+
+	pixels = (guint32*) cairo_image_surface_get_data(surface);
+	stride = cairo_image_surface_get_stride(surface);
+
+	tmp_pixels = (guint32*) cairo_image_surface_get_data(tmp_surface);
+	tmp_stride = cairo_image_surface_get_stride(tmp_surface);
+
+	for (y = 0; y < img_height - k; y++) {
+		for (x = 0; x < img_width - k; x++) {
+			core_pixels = count_black_pixel_unchecked(tmp_pixels, tmp_stride, x+1, y+1, k-2, k-2);
+
+			kfill_get_condition_variables(tmp_pixels, tmp_stride, k, x, y, &n, &r, &c);
+
+			/* more than half black or white? This would be the new core color. */
+			core_color = core_pixels * 2 >= (k-2)*(k-2);
+
+			if (core_color) {
+				n = ( 4*(k-1) ) - n;
+				r = 4 - r;
+			}
+
+			if ((c <= 1) && ((n > 3*k - 4) || ((n == 3*k - 4) && (r == 2)))) {
+				set_pixels_unchecked(pixels, stride, x+1, y+1, k-2, k-2, !core_color);
+			} else {
+				set_pixels_unchecked(pixels, stride, x+1, y+1, k-2, k-2, core_color);
+			}
+		}
+	}
+}
+
+static void
+mark_pixel(cairo_surface_t *debug_surf, gint x, gint y)
+{
+	cairo_t *cr;
+
+	cr = cairo_create(debug_surf);
+	cairo_set_source_rgba(cr, 1, 0, 0, 0.5);
+	cairo_rectangle(cr, x-0.5, y-0.5, 1, 1);
+	cairo_fill(cr);
+	cairo_destroy(cr);
+}
+
+/* XXX: This needs rather a lot of stack space ...
+ * TODO: Rewrite in a saner and faster way.
+ * Returns the size of the filled area. */
+guint
+flood_fill(cairo_surface_t *surface, cairo_surface_t *debug_surf, gint x, gint y, gint orig_color)
+{
+	guint img_width, img_height;
+	guint stride;
+	guint32 *pixels;
+	guint result;
+
+	img_width = cairo_image_surface_get_width(surface);
+	img_height = cairo_image_surface_get_height(surface);
+
+	pixels = (guint32*) cairo_image_surface_get_data(surface);
+	stride = cairo_image_surface_get_stride(surface);
+
+	if (x < 0)
+		return 0;
+	if (y < 0)
+		return 0;
+
+	if (x >= img_width)
+		return 0;
+	if (y >= img_height)
+		return 0;
+
+	if (GET_PIXEL(pixels, stride, x, y) != orig_color)
+		return 0;
+
+	/* Swap pixel value. */
+	SET_PIXEL(pixels, stride, x, y, !orig_color);
+
+	result = 1;
+
+	result += flood_fill(surface, debug_surf, x+1, y, orig_color);
+	result += flood_fill(surface, debug_surf, x, y+1, orig_color);
+	result += flood_fill(surface, debug_surf, x-1, y, orig_color);
+	result += flood_fill(surface, debug_surf, x, y-1, orig_color);
+
+	if (debug_surf != NULL)
+		mark_pixel(debug_surf, x, y);
+
+	return result;
+}
+
+
+/*************************************************
+ * Hough transformation!
+ *************************************************/
+
+typedef struct {
+	guint32 *data;
+	guint angle_bins;
+	guint distance_bins;
+
+	guint max_distance;
+
+	gdouble *cos_table;
+	gdouble *sin_table;
+} hough_data;
+
+/* Adds a point to the hough transformation. A (gaussion) filter is applied
+ * at the same time.
+ * I guess this is slightly less efficient than filtering it later, but it
+ * means we do not require a larger result space for filtering. */
+void
+hough_add_point(hough_data *hough, guint x, guint y, guint filter_width, guint *filter_coff)
+{
+	guint angle_step;
+	gdouble r;
+	gint r_bin, filt_bin;
+	gint i;
+
+
+	for (angle_step = 0; angle_step < hough->angle_bins; angle_step++) {
+		r = x * hough->cos_table[angle_step] + y * hough->sin_table[angle_step];
+
+		/* Only add if r is larger than zero */
+		r_bin = (int)round(hough->distance_bins * r / hough->max_distance);
+
+		for (i = 0; i < filter_width; i++) {
+			filt_bin = r_bin + i - filter_width / 2;
+
+			if ((filt_bin >= 0) && (filt_bin < hough->distance_bins)) {
+				hough->data[angle_step*hough->distance_bins + filt_bin] += filter_coff[i];
+			}
+		}
+	}
+}
+
+static void
+hough_create_lut(hough_data *hough)
+{
+	guint angle_step;
+	gdouble angle;
+
+	hough->cos_table = g_new(gdouble, hough->angle_bins);
+	hough->sin_table = g_new(gdouble, hough->angle_bins);
+
+	for (angle_step = 0; angle_step < hough->angle_bins; angle_step++) {
+		angle = (2*G_PI * angle_step) / hough->angle_bins;
+
+		hough->cos_table[angle_step] = cos(angle);
+		hough->sin_table[angle_step] = sin(angle);
+	}
+}
+
+guint
+get_gaussion(gdouble sigma, guint **filter_coff)
+{
+	gint filt_length;
+	gint center;
+	gint i;
+	g_assert(filter_coff != NULL);
+
+	/* We just multiply the coefficients with 10, and we simply evaluate
+	 * the exponential function. */
+
+	/* Simply use two sigma for the filter width. */
+	center = (gint)ceil(sigma * 2);
+	filt_length = ((gint)ceil(sigma * 2)) * 2 + 1;
+
+	*filter_coff = g_new(guint, filt_length);
+	for (i = 0; i < center; i++) {
+		(*filter_coff)[i] = floor(10*exp(-(gdouble)((i - center)*(i - center)) / pow(sigma, 2) / 2.0));
+		(*filter_coff)[filt_length - i - 1] = (*filter_coff)[i];
+	}
+	(*filter_coff)[center] = 10;
+
+	return filt_length;
+}
+
+/* Hough transforms the image.
+ *
+ * */
+hough_data*
+hough_transform(cairo_surface_t *surface, guint angle_bins, guint distance_bins, gdouble sigma_px)
+{
+	guint img_width, img_height;
+	guint stride;
+	guint x, y;
+	guint32 *pixels;
+	guint filter_length;
+	guint *filter;
+	hough_data *result = g_malloc(sizeof(hough_data));
+	result->data = NULL;
+	result->cos_table = NULL;
+	result->sin_table = NULL;
+
+	img_width = cairo_image_surface_get_width(surface);
+	img_height = cairo_image_surface_get_height(surface);
+
+	result->angle_bins = angle_bins;
+	result->distance_bins = distance_bins;
+	result->max_distance = (guint) sqrt(img_width*img_width + img_height*img_height);
+
+	result->data = g_malloc0(sizeof(*result->data) * result->angle_bins*result->distance_bins);
+	hough_create_lut(result);
+
+	pixels = (guint32*) cairo_image_surface_get_data(surface);
+	stride = cairo_image_surface_get_stride(surface);
+
+	filter_length = get_gaussion(sigma_px * result->distance_bins / result->max_distance, &filter);
+
+	for (y = 0; y < img_height; y++) {
+		for (x = 0; x < img_width; x++) {
+			if (GET_PIXEL(pixels, stride, x, y))
+				hough_add_point(result, x, y, filter_length, filter);
+		}
+	}
+
+	g_free(filter);
+
+	return result;
+}
+
+void
+hough_data_free(hough_data *data)
+{
+	g_free(data->data);
+	g_free(data->cos_table);
+	g_free(data->sin_table);
+	g_free(data);
+}
+
+static void
+remove_line(cairo_surface_t *surface, gdouble width, gdouble r_max, gdouble phi_max, gboolean debug)
+{
+	guint img_width, img_height;
+	cairo_t *cr;
+
+	img_width = cairo_image_surface_get_width(surface);
+	img_height = cairo_image_surface_get_height(surface);
+
+	cr = cairo_create(surface);
+	cairo_set_line_cap(cr, CAIRO_LINE_CAP_SQUARE);
+	cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
+	if (!debug) {
+		cairo_set_source_rgba(cr, 0, 0, 0, 0);
+	} else {
+		cairo_set_source_rgba(cr, 1, 0, 0, 0.5);
+	}
+	cairo_set_line_width(cr, width);
+	if (sin(phi_max) > 0.1) {
+		cairo_move_to(cr, 0, r_max / sin(phi_max));
+		cairo_line_to(cr, img_width, (r_max - img_width * cos(phi_max)) / sin(phi_max));
+	} else {
+		cairo_move_to(cr, r_max / cos(phi_max), 0);
+		cairo_line_to(cr, (r_max - img_height * sin(phi_max)) / cos(phi_max), img_height);
+	}
+	cairo_stroke(cr);
+	cairo_destroy(cr);
+}
+
+void
+remove_maximum_line(cairo_surface_t *surface, cairo_surface_t *debug_surf, gdouble width)
+{
+	int r, phi;
+	gdouble r_max, phi_max, maximum;
+	hough_data *hough = hough_transform(surface, 60, 30, width / 2.0);
+
+	/* Shut up the compiler*/
+	r_max = 0;
+	phi_max = 0;
+	maximum = -1;
+
+	/* Find global maximum. */
+	for (phi = 0; phi < hough->angle_bins; phi++) {
+		for (r = 0; r < hough->distance_bins; r++) {
+			guint32 *cur = &hough->data[phi * hough->distance_bins + r];
+
+			if (*cur > maximum) {
+				maximum = *cur;
+				r_max = r / (gdouble) hough->distance_bins * hough->max_distance;
+				phi_max = phi / (gdouble) hough->angle_bins * G_PI*2;
+			}
+		}
+	}
+
+	remove_line(surface, width, r_max, phi_max, FALSE);
+	if (debug_surf != NULL)
+		remove_line(debug_surf, width, r_max, phi_max, TRUE);
+
+	hough_data_free(hough);
+
+	cairo_surface_flush(surface);
+}
+
+
+#if 0
+/* The following is an adaption from the Animal imaging library.
+ * The library was written by Ricardo Fabbri (2011) and is licensed under the
+ * GPLv2 or any later version there.
+ * The same algorithm is used in queXF for character recognition. */
+
+/* Offsets to the neighbors of a pixel. */
+static int n8[8][2] = {
+  { 0,  1},
+  {-1,  1},
+  {-1,  0},
+  {-1, -1},
+  { 0, -1},
+  { 1, -1},
+  { 1,  0},
+  { 1,  1}
+};
+
+/*
+	This function tells the number N of regions that would exist if the
+	pixel P(r,c) where changed from FG to BG. It returns 2*N, that is,
+	the number of pixel transitions in the neighbourhood.
+*/
+static int
+crossing_index_np(guint32 *pixels, int stride, int x, int y)
+{
+	guint n=0, idx;
+	guint current, next;
+
+	/* The last one (so we get the crossing from last to first pixel). */
+	current = GET_PIXEL(pixels, stride, x + n8[7][0], y + n8[7][1]);
+
+	for (idx = 0; idx < 8; idx++) {
+		next = GET_PIXEL(pixels, stride, x + n8[idx][0], y + n8[idx][1]);
+
+		if (next != current)
+			n++;
+
+		current = next;
+	}
+
+	return n;
+}
+
+int
+nh8count_np(guint32 *pixels, int stride, int x, int y, int val)
+{
+	guint i, n=0;
+
+	for (i=0; i < 8; i++)
+		if (GET_PIXEL(pixels, stride, x + n8[i][1], y + n8[i][0]) == val)
+			n++;
+
+	return n;
+}
+
+/*
+	In place Zhang-Suen thinning. The algorithm leaves a 1px border untouched.
+*/
+void
+thinzs_np(cairo_surface_t *surface)
+{
+	gboolean modified;
+	guint x, y, n;
+	guint img_width, img_height;
+	guint stride, tmp_stride;
+	guint32 *pixels, *tmp_pixels;
+	guint pixel;
+	guint FG=1, BG=0;
+
+	cairo_surface_t *tmp_surface;
+	tmp_surface = surface_copy(surface);
+
+	img_width = cairo_image_surface_get_width(surface);
+	img_height = cairo_image_surface_get_height(surface);
+
+	pixels = (guint32*) cairo_image_surface_get_data(surface);
+	stride = cairo_image_surface_get_stride(surface);
+
+	tmp_pixels = (guint32*) cairo_image_surface_get_data(tmp_surface);
+	tmp_stride = cairo_image_surface_get_stride(tmp_surface);
+
+
+	do {
+		modified = FALSE;
+
+		for (y=1; y < img_height-1; y++)  for (x=1; x < img_width-1; x++) {
+			pixel = GET_PIXEL(pixels, stride, x, y);
+
+			SET_PIXEL(tmp_pixels, tmp_stride, x, y, pixel);
+
+			if (pixel == FG) {
+				n = nh8count_np(pixels, stride, x, y, FG);
+
+				if ( n >= 2 && n <= 6 && crossing_index_np(pixels, stride, x, y) == 2) {
+					if((GET_PIXEL(pixels, stride, x, y-1) == BG ||
+					    GET_PIXEL(pixels, stride, x+1, y) == BG ||
+					    GET_PIXEL(pixels, stride, x, y+1) == BG)
+					&& (GET_PIXEL(pixels, stride, x, y-1) == BG ||
+					    GET_PIXEL(pixels, stride, x, y+1) == BG ||
+					    GET_PIXEL(pixels, stride, x-1, y) == BG))
+					{
+						SET_PIXEL(tmp_pixels, tmp_stride, x, y, BG);
+						modified = TRUE;
+					}
+				}
+			}
+		}
+
+		for (y=1; y < img_height-1; y++)  for (x=1; x < img_width-1; x++) {
+			pixel = GET_PIXEL(tmp_pixels, tmp_stride, x, y);
+
+			SET_PIXEL(pixels, stride, x, y, pixel);
+
+			if (pixel == FG) {
+				n = nh8count_np(tmp_pixels, tmp_stride, x, y, FG);
+
+				if ( n>=2 && n<=6 && crossing_index_np(tmp_pixels, tmp_stride, x, y) == 2) {
+					if((GET_PIXEL(pixels, stride, x+1, y) == BG ||
+					    GET_PIXEL(pixels, stride, x-1, y) == BG ||
+					    GET_PIXEL(pixels, stride, x, y-1) == BG)
+					&& (GET_PIXEL(pixels, stride, x, y-1) == BG ||
+					    GET_PIXEL(pixels, stride, x-1, y) == BG ||
+					    GET_PIXEL(pixels, stride, x, y+1) == BG))
+					{
+						SET_PIXEL(pixels, stride, x, y, BG);
+						modified = TRUE;
+					}
+				}
+			}
+		}
+	} while (modified);
+
+	cairo_surface_destroy(tmp_surface);
+
+	cairo_surface_mark_dirty(surface);
+}
+#endif
+
diff --git a/sdaps/image/transform.h b/sdaps/image/transform.h
new file mode 100644
index 0000000..548d6f3
--- /dev/null
+++ b/sdaps/image/transform.h
@@ -0,0 +1,36 @@
+/* SDAPS
+ * Copyright (C) 2012  Benjamin Berg <benjamin at sipsolutions.net>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <glib.h>
+#include <cairo.h>
+
+#if 0
+void
+thinzs_np(cairo_surface_t *surface);
+#endif
+
+void
+kfill_modified(cairo_surface_t* surface, gint k);
+
+guint
+flood_fill(cairo_surface_t *surface, cairo_surface_t *debug_surf, gint x, gint y, gint orig_color);
+
+void
+remove_maximum_line(cairo_surface_t *surface, cairo_surface_t *debug_surf, gdouble width);
+
+
diff --git a/sdaps/image/wrap_image.c b/sdaps/image/wrap_image.c
new file mode 100644
index 0000000..040f102
--- /dev/null
+++ b/sdaps/image/wrap_image.c
@@ -0,0 +1,490 @@
+/* SDAPS
+ * Copyright (C) 2008  Christoph Simon <post at christoph-simon.eu>
+ * Copyright (C) 2008  Benjamin Berg <benjamin at sipsolutions.net>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "image.h"
+#include "transform.h"
+#include "surface.h"
+#include <Python.h>
+#include <pycairo.h>
+#include <cairo.h>
+
+static PyObject *wrap_get_a1_from_tiff(PyObject *self, PyObject *args);
+static PyObject *wrap_write_a1_to_tiff(PyObject *self, PyObject *args);
+static PyObject *wrap_get_rgb24_from_tiff(PyObject *self, PyObject *args);
+static PyObject *wrap_find_corner_marker(PyObject *self, PyObject *args);
+static PyObject *wrap_calculate_matrix(PyObject *self, PyObject *args);
+static PyObject *wrap_calculate_correction_matrix_masked(PyObject *self, PyObject *args);
+static PyObject *wrap_find_box_corners(PyObject *self, PyObject *args);
+static PyObject *wrap_get_coverage(PyObject *self, PyObject *args);
+static PyObject *wrap_get_masked_coverage(PyObject *self, PyObject *args);
+static PyObject *wrap_get_masked_coverage_without_lines(PyObject *self, PyObject *args);
+static PyObject *wrap_get_masked_white_area_count(PyObject *self, PyObject *args);
+static PyObject *wrap_get_pbm(PyObject *self, PyObject *args);
+static PyObject *sdaps_set_magic_values(PyObject *self, PyObject *args);
+static PyObject *enable_debug_surface_creation(PyObject *self, PyObject *args);
+static PyObject *get_debug_surface(PyObject *self, PyObject *args);
+static PyObject *wrap_get_tiff_page_count(PyObject *self, PyObject *args);
+static PyObject *wrap_get_tiff_resolution(PyObject *self, PyObject *args);
+static PyObject *wrap_check_tiff_monochrome(PyObject *self, PyObject *args);
+static PyObject *wrap_kfill_modified(PyObject *self, PyObject *args);
+
+Pycairo_CAPI_t *Pycairo_CAPI;
+
+static PyMethodDef EvaluateMethods[] = {
+	{"get_a1_from_tiff",  wrap_get_a1_from_tiff, METH_VARARGS, "Creates a cairo A1 surface from a monochrome tiff file."},
+	{"write_a1_to_tiff",  wrap_write_a1_to_tiff, METH_VARARGS, "Appends a new page to an existing tiff file or create a new tiff file containing the pixel data from the surface."},
+	{"get_rgb24_from_tiff",  wrap_get_rgb24_from_tiff, METH_VARARGS, "Creates a cairo RGB24 surface from a (monochrome) tiff file."},
+	{"get_tiff_page_count",  wrap_get_tiff_page_count, METH_VARARGS, "Returns the number of pages a multipage tiff contains."},
+	{"get_tiff_resolution", wrap_get_tiff_resolution, METH_VARARGS, "Retrieves the resolution from the given page of the tiff file (in dots per mm)."},
+	{"check_tiff_monochrome",  wrap_check_tiff_monochrome, METH_VARARGS, "Check whether all pages of the tiff are monochrome."},
+	{"find_corner_marker",  wrap_find_corner_marker, METH_VARARGS, "Searches for a corner marker. The third parameter should be an integer specifying the corner (1: top left, 2: top right, 3: bottom right, 4: bottom left."},
+	{"calculate_matrix",  wrap_calculate_matrix, METH_VARARGS, "Calculates the transformation matrix transform the image into the survey coordinate system."},
+	{"calculate_correction_matrix_masked",  wrap_calculate_correction_matrix_masked, METH_VARARGS, "Calculates a corrected transformation matrix for the mask at the given the top left corner."},
+	{"find_box_corners",  wrap_find_box_corners, METH_VARARGS, "Tries to find the actuall corners of a box in the milimeter space."},
+	{"get_coverage",  wrap_get_coverage, METH_VARARGS, "Calculates the black coverage in the given area."},
+	{"get_masked_coverage",  wrap_get_masked_coverage, METH_VARARGS, "Calculates the black coverage in the given mask."},
+	{"get_masked_coverage_without_lines",  wrap_get_masked_coverage_without_lines, METH_VARARGS, "First removes the number of requested lines with the specified stroke width using a hough transformation. Then calculates the coverage. Works on the masked area."},
+	{"get_masked_white_area_count",  wrap_get_masked_white_area_count, METH_VARARGS, "Returns the number and overall size of white areas that are larger than the given percentage of the overall size. Works on the masked area."},
+	{"get_pbm",  wrap_get_pbm, METH_VARARGS, "Returns a string that contains a binary PBM data representation of the cairo A1 surface."},
+	{"set_magic_values",  sdaps_set_magic_values, METH_VARARGS, "Sets some magic values for recognition."},
+	{"enable_debug_surface_creation",  enable_debug_surface_creation, METH_VARARGS, "Sets whether debug images should be created."},
+	{"get_debug_surface",  get_debug_surface, METH_VARARGS, "Returns the last created debug surface. Call immediately after a function that may create such a surface."},
+	{"kfill_modified",  wrap_kfill_modified, METH_VARARGS, "Run the modified KFill algorithm over the given A1 surface."},
+	{NULL, NULL, 0, NULL} /* Sentinel */
+};
+
+static int
+initpycairo(void)
+{
+	Pycairo_IMPORT;
+	if (Pycairo_CAPI == NULL)
+		return 0;
+
+	return 1;
+}
+
+PyMODINIT_FUNC
+initimage(void)
+{
+	/* Return if pycairo cannot be initilized. */
+	if (!initpycairo())
+		return;
+
+	Py_InitModule("image", EvaluateMethods);
+
+	/* supress warnings from libtiff. */
+	disable_libtiff_warnings ();
+}
+
+
+static PyObject *
+wrap_get_a1_from_tiff(PyObject *self, PyObject *args)
+{
+	cairo_surface_t *surface;
+	const char *filename = NULL;
+	gboolean rotated;
+	gint page;
+
+	if (!PyArg_ParseTuple(args, "sii", &filename, &page, &rotated))
+		return NULL;
+
+	surface = get_a1_from_tiff(filename, page, rotated);
+
+	if (surface) {
+		return PycairoSurface_FromSurface(surface, NULL);
+	} else {
+		PyErr_SetString(PyExc_AssertionError, "The image surface could not be created! Broken or non 1bit tiff file?");
+		return NULL;
+	}
+}
+
+static PyObject *
+wrap_write_a1_to_tiff(PyObject *self, PyObject *args)
+{
+	PycairoSurface *py_surface;
+	const char *filename = NULL;
+
+	if (!PyArg_ParseTuple(args, "sO!", &filename,
+	                                   &PycairoImageSurface_Type, &py_surface))
+		return NULL;
+
+	if (!write_a1_to_tiff(filename, py_surface->surface)) {
+		PyErr_SetString(PyExc_AssertionError, "Error writing new page to TIFF file (append/create)!");
+		return NULL;
+	}
+
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
+static PyObject *
+wrap_get_rgb24_from_tiff(PyObject *self, PyObject *args)
+{
+	cairo_surface_t *surface;
+	const char *filename = NULL;
+	gboolean rotated;
+	gint page;
+
+	if (!PyArg_ParseTuple(args, "sii", &filename, &page, &rotated))
+		return NULL;
+
+	surface = get_rgb24_from_tiff(filename, page, rotated);
+
+	if (surface) {
+		return PycairoSurface_FromSurface(surface, NULL);
+	} else {
+		PyErr_SetString(PyExc_AssertionError, "The image surface could not be created! Broken or non 1bit tiff file?");
+		return NULL;
+	}
+}
+
+static PyObject *
+wrap_get_tiff_page_count(PyObject *self, PyObject *args)
+{
+	const char *filename = NULL;
+	gint pages;
+
+	if (!PyArg_ParseTuple(args, "s", &filename))
+		return NULL;
+
+	pages = get_tiff_page_count(filename);
+
+	if (pages >= 1) {
+		return Py_BuildValue("i", pages);
+	} else {
+		PyErr_SetString(PyExc_AssertionError, "Could not retrieve the page count of the tiff image.");
+		return NULL;
+	}
+}
+
+static PyObject *
+wrap_get_tiff_resolution(PyObject *self, PyObject *args)
+{
+	const char *filename = NULL;
+	gint page;
+	gdouble xresolution, yresolution;
+
+	if (!PyArg_ParseTuple(args, "si", &filename, &page))
+		return NULL;
+
+	if (get_tiff_resolution(filename, page, &xresolution, &yresolution)) {
+		return Py_BuildValue("dd", xresolution, yresolution);
+	} else {
+		PyErr_SetString(PyExc_AssertionError, "Could not retrieve the resolution for the tiff file and page.");
+		return NULL;
+	}
+}
+
+static PyObject *
+wrap_check_tiff_monochrome(PyObject *self, PyObject *args)
+{
+	const char *filename = NULL;
+	gboolean monochrome;
+
+	if (!PyArg_ParseTuple(args, "s", &filename))
+		return NULL;
+
+	monochrome = check_tiff_monochrome(filename);
+
+	return Py_BuildValue("i", monochrome);
+}
+
+static PyObject *
+wrap_find_corner_marker(PyObject *self, PyObject *args)
+{
+	PyObject *result;
+	PycairoSurface *py_surface;
+	PycairoMatrix *py_matrix;
+	gint corner;
+	gdouble corner_x, corner_y;
+	gboolean success;
+
+	if (!PyArg_ParseTuple(args, "O!O!i",
+	                      &PycairoImageSurface_Type, &py_surface,
+	                      &PycairoMatrix_Type, &py_matrix, &corner))
+		return NULL;
+
+	success = find_corner_marker(py_surface->surface, &py_matrix->matrix, corner, &corner_x, &corner_y);
+
+	if (success) {
+		result = Py_BuildValue("dd", corner_x, corner_y);
+		return result;
+	} else {
+		PyErr_SetString(PyExc_AssertionError, "Could not find corner marker!");
+		return NULL;
+	}
+}
+
+static PyObject *
+wrap_calculate_matrix(PyObject *self, PyObject *args)
+{
+	PyObject *result;
+	PycairoSurface *py_surface;
+	PycairoMatrix *py_matrix;
+	cairo_matrix_t *matrix;
+	float mm_x, mm_y, mm_width, mm_height;
+
+	if (!PyArg_ParseTuple(args, "O!O!ffff",
+	                      &PycairoImageSurface_Type, &py_surface,
+	                      &PycairoMatrix_Type, &py_matrix,
+	                      &mm_x, &mm_y, &mm_width, &mm_height))
+		return NULL;
+
+	matrix = calculate_matrix(py_surface->surface, &py_matrix->matrix, mm_x, mm_y, mm_width, mm_height);
+
+	if (matrix) {
+		result = PycairoMatrix_FromMatrix(matrix);
+		g_free(matrix);
+		return result;
+	} else {
+		PyErr_SetString(PyExc_AssertionError, "Could not calculate the matrix!");
+		return NULL;
+	}
+}
+
+static PyObject *
+wrap_calculate_correction_matrix_masked(PyObject *self, PyObject *args)
+{
+	PyObject *result;
+	PycairoSurface *py_surface;
+	PycairoSurface *py_mask;
+	PycairoMatrix *py_matrix;
+	cairo_matrix_t *correction_matrix;
+	float mm_x, mm_y;
+	gdouble covered;
+
+	if (!PyArg_ParseTuple(args, "O!O!O!ff",
+	                      &PycairoImageSurface_Type, &py_surface,
+	                      &PycairoImageSurface_Type, &py_mask,
+	                      &PycairoMatrix_Type, &py_matrix,
+	                      &mm_x, &mm_y))
+		return NULL;
+
+	correction_matrix = calculate_correction_matrix_masked(py_surface->surface, py_mask->surface, &py_matrix->matrix, mm_x, mm_y, &covered);
+
+	if (correction_matrix) {
+		result = PycairoMatrix_FromMatrix(correction_matrix);
+		g_free(correction_matrix);
+		return Py_BuildValue("Nd", result, covered);
+	} else {
+		PyErr_SetString(PyExc_AssertionError, "Could not calculate the corrected matrix!");
+		return NULL;
+	}
+}
+
+static PyObject *
+wrap_find_box_corners(PyObject *self, PyObject *args)
+{
+	PycairoSurface *py_surface;
+	PycairoMatrix *py_matrix;
+	gdouble mm_x, mm_y, mm_width, mm_height;
+	gdouble mm_x1, mm_y1, mm_x2, mm_y2, mm_x3, mm_y3, mm_x4, mm_y4;
+	gboolean success;
+
+	if (!PyArg_ParseTuple(args, "O!O!dddd",
+	                      &PycairoImageSurface_Type, &py_surface,
+	                      &PycairoMatrix_Type, &py_matrix,
+	                      &mm_x, &mm_y, &mm_width, &mm_height))
+		return NULL;
+
+	success = find_box_corners(py_surface->surface, &py_matrix->matrix, mm_x, mm_y, mm_width, mm_height,
+	                           &mm_x1, &mm_y1, &mm_x2, &mm_y2, &mm_x3, &mm_y3, &mm_x4, &mm_y4);
+
+	if (success) {
+		return Py_BuildValue("(dd)(dd)(dd)(dd)", mm_x1, mm_y1, mm_x2, mm_y2, mm_x3, mm_y3, mm_x4, mm_y4);
+	} else {
+		PyErr_SetString(PyExc_AssertionError, "Could not find all the corners!");
+		return NULL;
+	}
+}
+
+static PyObject *
+wrap_get_coverage(PyObject *self, PyObject *args)
+{
+	PycairoSurface *py_surface;
+	PycairoMatrix *py_matrix;
+	gdouble mm_x, mm_y, mm_width, mm_height;
+	gdouble coverage;
+
+	if (!PyArg_ParseTuple(args, "O!O!dddd",
+	                      &PycairoImageSurface_Type, &py_surface,
+	                      &PycairoMatrix_Type, &py_matrix,
+	                      &mm_x, &mm_y, &mm_width, &mm_height))
+		return NULL;
+
+	coverage = get_coverage(py_surface->surface, &py_matrix->matrix, mm_x, mm_y, mm_width, mm_height);
+
+	return Py_BuildValue("d", coverage);
+}
+
+static PyObject *
+wrap_get_masked_coverage(PyObject *self, PyObject *args)
+{
+	PycairoSurface *py_surface;
+	PycairoSurface *py_mask;
+	gint x, y;
+	gdouble coverage;
+
+	if (!PyArg_ParseTuple(args, "O!O!ii",
+	                      &PycairoImageSurface_Type, &py_surface,
+	                      &PycairoImageSurface_Type, &py_mask,
+	                      &x, &y))
+		return NULL;
+
+	coverage = get_masked_coverage(py_surface->surface, py_mask->surface, x, y);
+
+	return Py_BuildValue("d", coverage);
+}
+
+static PyObject *
+wrap_get_masked_coverage_without_lines(PyObject *self, PyObject *args)
+{
+	PycairoSurface *py_surface;
+	PycairoSurface *py_mask;
+	gint x, y;
+	gdouble line_width;
+	gint line_count;
+	gdouble coverage;
+
+	if (!PyArg_ParseTuple(args, "O!O!iidi",
+	                      &PycairoImageSurface_Type, &py_surface,
+	                      &PycairoImageSurface_Type, &py_mask,
+	                      &x, &y, &line_width, &line_count))
+		return NULL;
+
+	coverage = get_masked_coverage_without_lines(py_surface->surface, py_mask->surface, x, y, line_width, line_count);
+
+	return Py_BuildValue("d", coverage);
+}
+
+static PyObject *
+wrap_get_masked_white_area_count(PyObject *self, PyObject *args)
+{
+	PycairoSurface *py_surface;
+	PycairoSurface *py_mask;
+	gint x, y;
+	gdouble min_size, max_size;
+	gdouble filled_area;
+	int count;
+
+	if (!PyArg_ParseTuple(args, "O!O!iidd",
+	                      &PycairoImageSurface_Type, &py_surface,
+	                      &PycairoImageSurface_Type, &py_mask,
+	                      &x, &y, &min_size, &max_size))
+		return NULL;
+
+	count = get_masked_white_area_count(py_surface->surface, py_mask->surface, x, y, min_size, max_size, &filled_area);
+
+	return Py_BuildValue("id", count, filled_area);
+}
+
+static PyObject *
+wrap_get_pbm(PyObject *self, PyObject *args)
+{
+	PycairoSurface *py_surface;
+	PyObject* result;
+	int length = 0;
+	void *data = NULL;
+
+	if (!PyArg_ParseTuple(args, "O!",
+	                      &PycairoImageSurface_Type, &py_surface))
+		return NULL;
+
+	get_pbm(py_surface->surface, &data, &length);
+
+	result = Py_BuildValue("s#", data, length);
+	g_free (data);
+	return result;
+}
+
+static PyObject *sdaps_set_magic_values(PyObject *self, PyObject *args)
+{
+	if (!PyArg_ParseTuple(args, "ddddd",
+	                      &sdaps_line_min_length,
+	                      &sdaps_line_max_length,
+	                      &sdaps_line_width,
+	                      &sdaps_corner_mark_search_distance,
+	                      &sdaps_line_coverage))
+		return NULL;
+
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
+static PyObject *enable_debug_surface_creation(PyObject *self, PyObject *args)
+{
+	if (!PyArg_ParseTuple(args, "i",
+	                      &sdaps_create_debug_surface))
+		return NULL;
+
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
+static PyObject *get_debug_surface(PyObject *self, PyObject *args)
+{
+	if (!PyArg_ParseTuple(args, ""))
+		return NULL;
+
+	if ((!sdaps_create_debug_surface) || (sdaps_debug_surface == NULL)) {
+		Py_INCREF(Py_None);
+		return Py_None;
+	} else {
+		PyObject *result;
+		PyObject *pysurface;
+
+		cairo_surface_reference(sdaps_debug_surface);
+		pysurface = (PyObject*) PycairoSurface_FromSurface(sdaps_debug_surface, NULL);
+
+		if (pysurface == NULL)
+			return NULL;
+
+		result =  Py_BuildValue("Nii",
+		                        pysurface,
+		                        sdaps_debug_surface_ox,
+		                        sdaps_debug_surface_oy);
+
+		if (result == NULL)
+			Py_DECREF(pysurface);
+
+		return result;
+	}
+}
+
+
+static PyObject *
+wrap_kfill_modified(PyObject *self, PyObject *args)
+{
+	PycairoSurface *py_surface;
+	gint k;
+
+	if (!PyArg_ParseTuple(args, "O!i",
+	                      &PycairoImageSurface_Type, &py_surface, &k))
+		return NULL;
+
+	if (cairo_image_surface_get_format (py_surface->surface) != CAIRO_FORMAT_A1) {
+		PyErr_SetString(PyExc_AssertionError, "This function only works with A1 surfaces currently!");
+		return NULL;
+	}
+
+	kfill_modified(py_surface->surface, k);
+
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
diff --git a/sdaps/log.py b/sdaps/log.py
new file mode 100644
index 0000000..42e64bb
--- /dev/null
+++ b/sdaps/log.py
@@ -0,0 +1,208 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+u'''
+This module alters sys.stdout and sys.stderr to copy all output into a logfile.
+
+Note that some magic is done when the output stream is not a tty. In that
+case the progress bar and messages done using the :py:meth:`interactive`
+function will be suppressed. The reason to do this is to allow commands to
+output data to `sys.stdout`.
+'''
+
+import sys
+import time
+import StringIO
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+
+def warn(msg):
+    sys.stderr.write(_('Warning: ') + msg + '\n')
+
+
+def error(msg):
+    sys.stderr.write(_('Error: ') + msg + '\n')
+
+def interactive(msg):
+    """Only forward the message to stdout if it is a terminal."""
+    if hasattr(sys.stdout, "interactive"):
+        sys.stdout.interactive(msg)
+    else:
+        if sys.stdout.isatty():
+            sys.stdout.write(msg)
+
+class Copier(object):
+    '''copy all data going through the pipe into a logfile'''
+
+    def __init__(self, pipe, logfile):
+        self.pipe = pipe
+        self.logfile = logfile
+
+    def write(self, data):
+        self.pipe.write(data)
+        self.logfile.write(data)
+
+    def interactive(self, data):
+        if self.pipe.isatty():
+            self.pipe.write(data)
+        self.logfile.write(data)
+
+    def isatty(self):
+        return self.pipe.isatty()
+
+    def flush(self):
+        self.pipe.flush()
+        self.logfile.flush()
+
+    def fileno(self):
+        return self.pipe.fileno()
+
+class Wiper(object):
+    '''wipe out the progressbar before forwarding data through the pipe'''
+
+    def __init__(self, pipe, progressbar):
+        self.pipe = pipe
+        self.progressbar = progressbar
+
+    def write(self, data):
+        if self.progressbar.visible:
+            self.pipe.write(' ' * 80)
+            self.pipe.write('\r')
+            self.progressbar.visible = 0
+        self.pipe.write(data)
+
+    def isatty(self):
+        return self.pipe.isatty()
+
+    def flush(self):
+        self.pipe.flush()
+
+    def fileno(self):
+        return self.pipe.fileno()
+
+class Encoder(object):
+    '''Encode data going through the pipe to utf-8'''
+
+    def __init__(self, pipe):
+        self.pipe = pipe
+
+    def write(self, data):
+        if isinstance(data, unicode):
+            self.pipe.write(data.encode('utf-8'))
+        else:
+            self.pipe.write(data)
+
+    def close(self):
+        self.pipe.close()
+
+    def isatty(self):
+        return self.pipe.isatty()
+
+    def flush(self):
+        self.pipe.flush()
+
+    def fileno(self):
+        return self.pipe.fileno()
+
+class Logfile(object):
+
+    def __init__(self):
+        self.logfile = StringIO.StringIO()
+
+    def write(self, data):
+        self.logfile.write(data)
+
+    def open(self, filename):
+        logfile = Encoder(file(filename, 'a', buffering=0))
+        logfile.write(self.logfile.getvalue())
+        self.logfile = logfile
+
+    def close(self):
+        self.logfile.close()
+        self.logfile = StringIO.StringIO()
+
+    def isatty(self):
+        return False
+
+    def flush(self):
+        self.logfile.flush()
+
+    def fileno(self):
+        return self.logfile.fileno()
+
+class ProgressBar(object):
+
+    def __init__(self, pipe):
+        self.pipe = pipe
+        self.visible = 0
+
+    def start(self, max_value):
+        self.max_value = max_value
+        self.start_time = time.time()
+        self.update(0)
+
+    def update(self, value):
+        self.elapsed_time = time.time() - self.start_time
+
+        # Don't display anything if the output is a pipe
+        if not self.pipe.isatty():
+            return
+
+        progress = float(value) / float(self.max_value)
+        self.pipe.write('|')
+        self.pipe.write(('#' * int(round(progress * 64))).ljust(64))
+        self.pipe.write('| ')
+        self.pipe.write(('%i%% ' % int(round(progress * 100))).rjust(5))
+        if progress == 0:
+            self.pipe.write('--:--:--')
+        elif progress == 1:
+            self.pipe.write(time.strftime('%H:%M:%S', time.gmtime(self.elapsed_time)))
+            self.pipe.write('\n')
+        else:
+            remaining_time = self.elapsed_time * (1 / progress - 1)
+            self.pipe.write(time.strftime('%H:%M:%S', time.gmtime(remaining_time)))
+        self.pipe.write('\r')
+        self.pipe.flush()
+        self.visible = 1
+
+    def isatty(self):
+        return self.pipe.isatty()
+
+    def flush(self):
+        self.pipe.flush()
+
+progressbar = ProgressBar(sys.stdout)
+logfile = Logfile()
+
+redirects_activated = False
+def activate_redirects():
+    global redirects_activated
+
+    if redirects_activated is not False:
+        return
+    redirects_activated = True
+
+    sys.stdout = Encoder(sys.stdout)
+    sys.stderr = Encoder(sys.stderr)
+    sys.stdout = Wiper(sys.stdout, progressbar)
+    sys.stderr = Wiper(sys.stderr, progressbar)
+    sys.stdout = Copier(sys.stdout, logfile)
+    sys.stderr = Copier(sys.stderr, logfile)
+
diff --git a/sdaps/matrix.py b/sdaps/matrix.py
new file mode 100644
index 0000000..7b4c8be
--- /dev/null
+++ b/sdaps/matrix.py
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+u"""
+The matrix module adds support to find out rotation matrix for a scanned
+page. After loading this module you can access the transformation matrices
+(cairo.Martrix instances) via two functions:
+
+  :py:meth:`model.sheet.Image.matrix.mm_to_px` and
+  :py:meth:`model.sheet.Image.matrix.px_to_mm`
+
+The return value of :py:meth:`mm_to_px` can be used to convert millimeter
+values to pixel on the scanned page. :py:meth:`px_to_mm` returns the inverse
+matrix to convert on page pixels to millimeter values of the original document.
+
+Both the millimeter and pixel spaces start at the top left corner of the page.
+
+Warning: The correct values are only available after "recognize" has been run!
+"""
+
+import cairo
+
+from sdaps import model
+from sdaps import surface
+from sdaps import image
+
+
+class Image(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'matrix'
+    obj_class = model.sheet.Image
+
+    def mm_to_px(self, fallback=True):
+        u"""Return the matrix to convert mm to pixel space. If no matrix
+        information is available and `fallback` is set to `True` then a matrix
+        will be calculated from the size of the image."""
+        matrix = self.px_to_mm(fallback)
+        if matrix is not None:
+            matrix.invert()
+        return matrix
+
+    def px_to_mm(self, fallback=True):
+        u"""Return the matrix to convert pixel to mm space. If no matrix
+        information is available and `fallback` is set to `True` then a matrix
+        will be calculated from the size of the image."""
+        if self.obj.raw_matrix is not None:
+            return cairo.Matrix(*self.obj.raw_matrix)
+        elif fallback:
+            # Return a dummy matrix ... that maps the image to the page size
+            width, height = self.obj.surface.get_size()
+            xres, yres = image.get_tiff_resolution(
+                self.obj.sheet.survey.path(self.obj.filename),
+                self.obj.tiff_page)
+
+            if xres == 0 or yres == 0:
+                xres = width / self.obj.sheet.survey.defs.paper_width
+                yres = height / self.obj.sheet.survey.defs.paper_height
+
+            scan_width = width / xres
+            scan_height = height / yres
+
+            dx = (self.obj.sheet.survey.defs.paper_width - scan_width) / 2.0
+            dy = (self.obj.sheet.survey.defs.paper_height - scan_height) / 2.0
+
+            matrix = cairo.Matrix()
+
+            # Center the image
+            matrix.translate(dx, dy)
+            matrix.scale(1.0 / xres, 1.0 / yres)
+
+            return matrix
+        else:
+            return None
+
+    def matrix_valid(self):
+        u"""Checks whether the proper transformation matrix is known."""
+        return self.obj.raw_matrix != None
+
+    def set_px_to_mm(self, matrix):
+        u"""Set the stored matrix for the image. You need to pass in the pixel
+        to mm space conversion matrix. Unsetting can be done by passing `None`.
+        """
+        if matrix is not None:
+            self.obj.raw_matrix = tuple(matrix)
+        else:
+            self.obj.raw_matrix = None
+
diff --git a/sdaps/model/__init__.py b/sdaps/model/__init__.py
new file mode 100644
index 0000000..4df96e8
--- /dev/null
+++ b/sdaps/model/__init__.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+u'''data model for sdaps
+
+Survey
+======
+
+survey ::
+
+    +-- questionnaire
+    |        +-- qobjects ---------------+
+    |                +-- boxes ----------+
+    +-- sheets                           |
+            +-- data dictionary          |
+            |        +-- data objects <--+
+            +-- images
+
+
+The questionnaire tree resembles the structer of the questionnaire and the
+internal structure of the programm. It is set up by the setup script and not
+altered(much) later.
+
+The sheets tree holds the data of the answered questionnaires. The sheet
+objects are created while the scanned images are added to the project.
+
+Iterating through sheets and jumping
+====================================
+
+Survey provides a function iterate. It iterates through the sheets and calls a
+function for each. Inside this function, the sheet is accessible through
+survey.sheet(a property, pointing to the actual sheet)
+The functions goto_something jump the survey.sheet pointer to the specified
+sheet.
+
+Buddies
+=======
+
+A script may define a buddy class for a class in the model. Then each object of
+that model class is accompanied by an object of the buddy class.
+The buddy object is instanciated automatically upon first access and then
+cached. It is not saved on disc.
+
+The buddy system allows you to define additional methods and attributes for
+the objects in the model.
+
+'''
+
+import buddy
+import data
+import questionnaire
+import sheet
+import survey
+
+
diff --git a/sdaps/model/buddy.py b/sdaps/model/buddy.py
new file mode 100644
index 0000000..b44a4e9
--- /dev/null
+++ b/sdaps/model/buddy.py
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+u'''
+    sdaps - scripts for data acquisition with paper based surveys
+
+    model/buddy - Registration center for buddies
+
+
+Defining a buddy
+================
+
+    from sdaps import model
+
+    class QObject(model.buddy.Buddy):
+
+        __metaclass__ = model.buddy.Register
+        obj_class = object class
+        name = 'my_buddy'
+
+    The buddy will be available as object.my_buddy
+
+    Inside buddy class, the object is available as self.obj
+
+'''
+
+
+class Object(object):
+    u'''class which can have buddies'''
+
+    def get_buddy(self, name):
+        try:
+            return getattr(self, '_%s_object_' % name)
+        except AttributeError:
+            setattr(self, '_%s_object_' % name, getattr(self, '_%s_class_' % name)(self))
+            return getattr(self, '_%s_object_' % name)
+
+    def __getstate__(self):
+        u'''do not pickle pickle any "private" data.
+        '''
+        dict = self.__dict__.copy()
+        keys = dict.keys()
+        for key in keys:
+            if key.startswith('_'):
+                del dict[key]
+        return dict
+
+
+class Register(type):
+    u'''metaclass to register the class as a buddy'''
+
+    def __init__(cls, name, bases, dict):
+        type.__init__(cls, name, bases, dict)
+        assert issubclass(cls.obj_class, Object)
+        setattr(
+            cls.obj_class,
+            '_%s_class_' % cls.name,
+            cls
+        )
+        setattr(
+            cls.obj_class,
+            cls.name,
+            property(lambda self: self.get_buddy(cls.name))
+        )
+
+
+class Buddy(object):
+    u'''base class for buddies'''
+
+    __metaclass__ = Register
+    obj_class = Object
+    name = 'my_buddy'
+
+    def __init__(self, obj):
+        if 0:
+            assert isinstance(obj, self.obj_class)
+        self.obj = obj
+
diff --git a/sdaps/model/data.py b/sdaps/model/data.py
new file mode 100644
index 0000000..994396c
--- /dev/null
+++ b/sdaps/model/data.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+
+class Box(object):
+
+    def __init__(self, parent):
+        self.state = 0
+        self.metrics = dict()
+        self.quality = 1
+        self.x = parent.x
+        self.y = parent.y
+        self.width = parent.width
+        self.height = parent.height
+
+        # Set _parent last, so that we don't trigger notifications
+        # during __init__
+        self._parent = parent
+
+    def __setattr__(self, name, value):
+        if hasattr(self, name):
+            old_value = getattr(self, name)
+        else:
+            old_value = None
+
+        object.__setattr__(self, name, value)
+        # private
+        if name.startswith('_'):
+            return
+
+        if value != old_value and hasattr(self, '_parent') and self._parent is not None:
+            self._parent.question.questionnaire.notify_data_changed(self._parent, self, name, old_value)
+
+    def __getstate__(self):
+        u'''Only pickle non-private attributes
+        '''
+        dict = self.__dict__.copy()
+        keys = dict.keys()
+        for key in keys:
+            if key.startswith('_'):
+                del dict[key]
+        return dict
+
+    @property
+    def empty(self):
+        return not self.state
+
+class Checkbox(Box):
+
+    pass
+
+
+class Textbox(Box):
+
+    def __init__(self, parent):
+        self.text = unicode()
+
+        Box.__init__(self, parent)
+
+
+class Additional_Mark(object):
+
+    def __init__(self, parent):
+        self.value = 0
+
+    @property
+    def empty(self):
+        return self.value == 0
+
diff --git a/sdaps/model/questionnaire.py b/sdaps/model/questionnaire.py
new file mode 100644
index 0000000..4ddc1af
--- /dev/null
+++ b/sdaps/model/questionnaire.py
@@ -0,0 +1,400 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+
+# The code uses multiple inheritance; however, most of the additional base
+# classes are solely used as mixins. This means they must not contain any
+# __init__ functions, as that could cause trouble.
+
+
+import buddy
+import data
+import struct
+
+
+class DataObject(object):
+    u'''Mixin
+    '''
+
+    def get_data(self):
+        if not self.id in self.sheet.data:
+            self.sheet.data[self.id] = getattr(data, self.__class__.__name__)(self)
+        return self.sheet.data[self.id]
+
+    data = property(get_data)
+
+
+class Questionnaire(buddy.Object):
+    '''
+    Identification: There is only one.
+    Reference: survey.questionnaire
+    Parent: self.survey
+    '''
+
+    def __init__(self):
+        self.survey = None
+        self.qobjects = list()
+        self.last_id = (0, 0)
+        self.init_attributes()
+        self._notify_changed_list = list()
+
+    def init_attributes(self):
+        self.page_count = 0
+
+    def add_qobject(self, qobject, new_id=None):
+        qobject.questionnaire = self
+        # XXX: Is this any good?
+        if new_id is not None:
+            assert new_id > self.last_id
+            self.last_id = new_id
+            qobject.id = new_id
+        else:
+            self.last_id = qobject.init_id(self.last_id)
+        self.qobjects.append(qobject)
+
+    def get_sheet(self):
+        return self.survey.sheet
+
+    sheet = property(get_sheet)
+
+    def notify_data_changed(self, qobj, dobj, name, old_value):
+        for func in self._notify_changed_list:
+            func(self, qobj, dobj, name, old_value)
+
+    def connect_data_changed(self, func):
+        self._notify_changed_list.append(func)
+
+    def disconnect_data_changed(self, func):
+        self._notify_changed_list.remove(func)
+
+    def __unicode__(self):
+        return unicode().join(
+            [u'%s\n' % self.__class__.__name__] +
+            [unicode(qobject) for qobject in self.qobjects]
+        )
+
+    def find_object(self, oid):
+        for qobject in self.qobjects:
+            obj = qobject.find_object(oid)
+
+            if obj is not None:
+                return obj
+
+    def reinit_state(self):
+        self._notify_changed_list = list()
+
+class QObject(buddy.Object):
+    '''
+    Identification: id ==(major, minor)
+    Reference: survey.questionnaire.qobjects[i](i != id)
+    Parent: self.questionnaire
+    '''
+
+    def __init__(self):
+        self.questionnaire = None
+        self.boxes = list()
+        self.last_id = -1
+        self.init_attributes()
+
+    def init_attributes(self):
+        pass
+
+    def init_id(self, id):
+        self.id = id[:-1] + (id[-1] + 1,)
+        return self.id
+
+    def add_box(self, box):
+        box.question = self
+        self.last_id = box.init_id(self.last_id)
+        self.boxes.append(box)
+
+    def get_sheet(self):
+        return self.questionnaire.sheet
+
+    sheet = property(get_sheet)
+
+    def calculate_survey_id(self, md5):
+        pass
+
+    def id_str(self):
+        ids = [str(x) for x in self.id]
+        return u'.'.join(ids)
+
+    def id_csv(self, theid=None):
+        if theid is None:
+            theid = self.id
+        ids = [str(x) for x in theid]
+        return u'_'.join(ids)
+
+    def id_filter(self):
+        ids = [str(x) for x in self.id]
+        return u'_' + u'_'.join(ids)
+
+    def __unicode__(self):
+        return u'(%s)\n' % (
+            self.__class__.__name__,
+        )
+
+    def find_object(self, oid):
+        if self.id == oid:
+            return self
+
+        for box in self.boxes:
+            obj = box.find_object(oid)
+
+            if obj is not None:
+                return obj
+
+        return None
+
+
+class Head(QObject):
+
+    def init_attributes(self):
+        QObject.init_attributes(self)
+        self.title = unicode()
+
+    def init_id(self, id):
+        self.id = (id[0] + 1, ) + (0,)*(len(id)-1)
+        return self.id
+
+    def __unicode__(self):
+        return u'%s(%s) %s\n' % (
+            self.id_str(),
+            self.__class__.__name__,
+            self.title,
+        )
+
+
+class Question(QObject):
+
+    def init_attributes(self):
+        QObject.init_attributes(self)
+        self.page_number = 0
+        self.question = unicode()
+
+    def calculate_survey_id(self, md5):
+        for box in self.boxes:
+            box.calculate_survey_id(md5)
+
+    def __unicode__(self):
+        return u'%s(%s) %s {%i}\n' % (
+            self.id_str(),
+            self.__class__.__name__,
+            self.question,
+            self.page_number
+        )
+
+
+class Choice(Question):
+
+    def __unicode__(self):
+        return unicode().join(
+            [Question.__unicode__(self)] +
+            [unicode(box) for box in self.boxes]
+        )
+
+    def get_answer(self):
+        '''it's a list containing all selected values
+        '''
+        answer = list()
+        for box in self.boxes:
+            if box.data.state:
+                answer.append(box.value)
+        return answer
+
+
+class Mark(Question):
+
+    def init_attributes(self):
+        Question.init_attributes(self)
+        self.answers = list()
+
+    def __unicode__(self):
+        if len(self.answers) == 2:
+            return unicode().join(
+                [Question.__unicode__(self)] +
+                [u'\t%s - %s\n' % tuple(self.answers)] +
+                [unicode(box) for box in self.boxes]
+            )
+        else:
+            return unicode().join(
+                [Question.__unicode__(self)] +
+                [u'\t? - ?\n'] +
+                [unicode(box) for box in self.boxes]
+            )
+
+    def get_answer(self):
+        '''it's an integer between 0 and 5
+        1 till 5 are valid marks, 0 is returned if there's something wrong
+        '''
+        # box.value is zero based, a mark is based 1
+        answer = list()
+        for box in self.boxes:
+            if box.data.state:
+                answer.append(box.value)
+        if len(answer) == 1:
+            return answer[0] + 1
+        else:
+            return 0
+
+    def set_answer(self, answer):
+        for box in self.boxes:
+            box.data.state = box.value == answer - 1
+
+
+class Text(Question):
+
+    def __unicode__(self):
+        return unicode().join(
+            [Question.__unicode__(self)] +
+            [unicode(box) for box in self.boxes]
+        )
+
+    def get_answer(self):
+        '''it's a bool, wether there is content in the textbox
+        '''
+        assert len(self.boxes) == 1
+        text = ""
+        for box in self.boxes:
+            text = text + box.data.text
+        if text:
+            return text
+        else:
+            return self.boxes[0].data.state
+
+
+class Additional_Head(Head):
+
+    pass
+
+
+class Additional_Mark(Question, DataObject):
+
+    def init_attributes(self):
+        Question.init_attributes(self)
+        self.answers = list()
+
+    def __unicode__(self):
+        return unicode().join(
+            [Question.__unicode__(self)] +
+            [u'\t%s - %s\n' % tuple(self.answers)]
+        )
+
+    def get_answer(self):
+        return self.data.value
+
+    def set_answer(self, answer):
+        self.data.value = answer
+
+
+class Additional_FilterHistogram(Question, DataObject):
+
+    def init_attributes(self):
+        Question.init_attributes(self)
+        self.answers = list()
+        self.filters = list()
+
+    def __unicode__(self):
+        result = []
+        result.append(Question.__unicode__(self))
+        for i in xrange(len(self.answers)):
+            result.append(u'\t%s - %s\n' % (self.answers[i], self.filters[i]))
+        return unicode().join(result)
+
+    def get_answer(self):
+        return self.data.value
+
+    def set_answer(self, answer):
+        raise NotImplemented()
+
+
+class Box(buddy.Object, DataObject):
+    '''
+    Identification: id of the parent and value of the box ::
+
+        id == (major, minor, value)
+
+    Reference: survey.questionnaire.qobjects[i].boxes[j]
+    Parent: self.question
+    '''
+
+    def __init__(self):
+        self.question = None
+        self.init_attributes()
+
+    def init_attributes(self):
+        self.page_number = 0
+        self.x = 0
+        self.y = 0
+        self.width = 0
+        self.height = 0
+        self.text = unicode()
+
+    def init_id(self, id):
+        self.value = id + 1
+        self.id = self.question.id + (self.value,)
+        return self.value
+
+    def id_str(self):
+        ids = [str(x) for x in self.id]
+        return u'.'.join(ids)
+
+    def id_csv(self):
+        ids = [str(x) for x in self.id]
+        return u'_'.join(ids)
+
+    def get_sheet(self):
+        return self.question.sheet
+
+    sheet = property(get_sheet)
+
+    def calculate_survey_id(self, md5):
+        tmp = struct.pack('!ffff', self.x, self.y, self.width, self.height)
+        md5.update(tmp)
+
+    def __unicode__(self):
+        return u'\t%i(%s) %s %s %s %s %s\n' % (
+            self.value,
+            (self.__class__.__name__).ljust(8),
+            (u'%.1f' % self.x).rjust(5),
+            (u'%.1f' % self.y).rjust(5),
+            (u'%.1f' % self.width).rjust(5),
+            (u'%.1f' % self.height).rjust(5),
+            self.text
+        )
+
+    def find_object(self, oid):
+        if self.id == oid:
+            return self
+
+
+class Checkbox(Box):
+
+    def init_attributes(self):
+        Box.init_attributes(self)
+        self.form = "box"
+
+    def calculate_survey_id(self, md5):
+        Box.calculate_survey_id(self, md5)
+        md5.update(self.form)
+
+class Textbox(Box):
+
+    pass
+
diff --git a/sdaps/model/sheet.py b/sdaps/model/sheet.py
new file mode 100644
index 0000000..3f40d07
--- /dev/null
+++ b/sdaps/model/sheet.py
@@ -0,0 +1,118 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import buddy
+
+
+class Sheet(buddy.Object):
+
+    _pickled_attrs = {'survey', 'data', 'images', 'survey_id',
+                      'questionnaire_id', 'global_id', 'valid',
+                      'quality', 'recognized', 'verified'}
+
+    def __init__(self):
+        self.survey = None
+        self.data = dict()
+        self.images = list()
+        self.survey_id = None
+        self.questionnaire_id = None
+        self.global_id = None
+        self.valid = 1
+        self.quality = 1
+
+        self.recognized = False
+        self.verified = False
+
+    def add_image(self, image):
+        self.images.append(image)
+        image.sheet = self
+
+    def get_page_image(self, page):
+        # Simply return the image for the requested page.
+        # Note: We return the first one we find; this means in the error case
+        #       that a page exists twice, we return the first one.
+        for image in self.images:
+            if image.page_number == page and image.survey_id == self.survey.survey_id:
+                return image
+        return None
+
+    def reinit_state(self):
+        for k, v in self.data.iteritems():
+            obj = self.survey.questionnaire.find_object(k)
+
+            v._parent = obj
+
+    @property
+    def empty(self):
+        for k, v in self.data.iteritems():
+            if not v.empty:
+                return False
+
+        return True
+
+    @property
+    def complete(self):
+        """A boolean whether this sheet is complete in the sense that
+        every page of the questionnaire has been identified.
+        ie. it is false if there are missing pages"""
+
+        # Simply retrieve every page, and see if it is not None
+        for page in xrange(self.survey.questionnaire.page_count):
+            if self.get_page_image(page + 1) is None:
+                return False
+        return True
+
+    def __setattr__(self, attr, value):
+        # Nonexisting attributes should never be set.
+        if attr.startswith('_'):
+            object.__setattr__(self, attr, value)
+            return
+
+        assert attr in self._pickled_attrs
+
+        # We need to fall back to "None" for __init__ to work.
+        try:
+            old_value = getattr(self, attr)
+            force = False
+        except AttributeError:
+            old_value = None
+            force = True
+
+        if force or value != old_value:
+            object.__setattr__(self, attr, value)
+            # survey may be None if the sheet does not belong to a survey yet.
+            if self.survey is not None:
+                self.survey.questionnaire.notify_data_changed(None, None, attr, old_value)
+
+
+class Image(buddy.Object):
+
+    def __init__(self):
+        self.sheet = None
+        self.filename = str()
+        self.tiff_page = 0
+        self.rotated = 0
+        self.raw_matrix = None
+        self.page_number = None
+        self.survey_id = None
+        self.global_id = None
+        self.questionnaire_id = None
+        #: Whether the page should be ignored (because it is a blank back side)
+        self.ignored = False
+
+
diff --git a/sdaps/model/survey.py b/sdaps/model/survey.py
new file mode 100644
index 0000000..068dd14
--- /dev/null
+++ b/sdaps/model/survey.py
@@ -0,0 +1,410 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import bz2
+import cPickle
+import os
+import sys
+from sdaps import defs
+
+from sdaps import log
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+valid_styles = ['classic', 'code128', 'custom', 'qr']
+valid_checkmodes = ['checkcorrect', 'check', 'fill']
+
+class Defs(object):
+    """General definitions that are valid for this survey.
+
+    :ivar paper_width: Width of the paper in mm.
+    :ivar paper_height: Height of the paper in mm.
+    :ivar print_questionnaire_id: Whether a questionnaire ID is printed on each sheet.
+    :ivar print_survey_id: Whether a survey ID is printed on each sheet.
+    :ivar style: The style that is used for ID marking.
+    :ivar duplex: Whether the questionnaire is duplex or not.
+    """
+
+    # Force a certain set of options using slots
+    __slots__ = ['paper_width', 'paper_height', 'print_questionnaire_id',
+                 'print_survey_id', 'style', 'duplex', 'checkmode']
+
+    def get_survey_id_pos(self):
+        assert(self.style == 'classic')
+
+        y_pos = self.paper_height - defs.corner_mark_bottom - defs.corner_box_padding
+        y_pos -= defs.codebox_height
+
+        left_padding = defs.corner_mark_left + 2 * defs.corner_box_padding + defs.corner_box_width
+        right_padding = defs.corner_mark_right + 2 * defs.corner_box_padding + defs.corner_box_width
+
+        text_y_pos = y_pos + defs.codebox_text_baseline_shift
+        x_center = left_padding + (self.paper_width - left_padding - right_padding) / 2.0
+
+        msb_box_x = left_padding
+        lsb_box_x = self.paper_width - right_padding - defs.codebox_width
+
+        text_x_pos = left_padding + (self.paper_width - right_padding - left_padding) / 2
+
+        return msb_box_x, lsb_box_x, y_pos, text_x_pos, text_y_pos
+
+    def get_questionnaire_id_pos(self):
+        assert(self.style == 'classic')
+
+        msb_box_x, lsb_box_x, y_pos, text_x_pos, text_y_pos = self.get_survey_id_pos()
+
+        if self.print_survey_id:
+            # Just move the y positions up if neccessary
+            y_pos -= defs.codebox_height + defs.corner_box_padding
+            text_y_pos -= defs.codebox_height + defs.corner_box_padding
+
+        return msb_box_x, lsb_box_x, y_pos, text_x_pos, text_y_pos
+
+
+class Survey(object):
+
+    """The main survey object.
+
+    :ivar defs: The :py:class:`model.survey.Defs` instance for this survey.
+    :ivar survey_id: The survey ID of this survey.
+    :ivar global_id: The global ID set for this survey. It is used during the "stamp" step.
+    :ivar questionnaire: The py:class:`model.questionnaire.Questionnaire` instance representing the questionnaire.
+    :ivar title: The title of the survey.
+    :ivar info: Dictionary with general information about the survey.
+    :ivar questionnaire_ids: A List of used questionnaire IDs.
+    """
+
+    pickled_attrs = set(('sheets', 'defs', 'survey_id', 'questionnaire_ids', 'questionnaire', 'version'))
+
+    def __init__(self):
+        self.questionnaire = None
+        self.sheets = list()
+        self.title = unicode()
+        self.info = dict()
+        self.survey_id = 0
+        self.global_id = None
+        self.questionnaire_ids = list()
+        self.index = 0
+        self.version = 4
+        self.defs = Defs()
+
+    def add_questionnaire(self, questionnaire):
+        self.questionnaire = questionnaire
+        questionnaire.survey = self
+
+    def add_sheet(self, sheet):
+        self.sheets.append(sheet)
+        sheet.survey = self
+        # Select the newly added sheet
+        self.index = len(self.sheets) - 1
+
+    def calculate_survey_id(self):
+        u"""Calculate the unique survey ID from the surveys settings and boxes.
+
+        The ID only includes the boxes positions, which means that simple typo
+        fixes will not change the ID most of the time."""
+        import hashlib
+        md5 = hashlib.new('md5')
+
+        for qobject in self.questionnaire.qobjects:
+            qobject.calculate_survey_id(md5)
+
+        for defs_slot in self.defs.__slots__:
+            # Backward compatibility
+            if defs_slot == 'checkmode' and self.defs.checkmode == "checkcorrect":
+                continue
+
+            if isinstance(self.defs.__getattribute__(defs_slot), float):
+                md5.update(str(round(self.defs.__getattribute__(defs_slot), 1)))
+            else:
+                md5.update(str(self.defs.__getattribute__(defs_slot)))
+
+        self.survey_id = 0
+        # This compresses the md5 hash to a 32 bit unsigned value, by
+        # taking the lower two bits of each byte.
+        for i, c in enumerate(md5.digest()):
+            self.survey_id += bool(ord(c) & 1) << (2 * i)
+            self.survey_id += bool(ord(c) & 2) << (2 * i + 1)
+
+    @staticmethod
+    def load(survey_dir):
+        import ConfigParser
+        file = bz2.BZ2File(os.path.join(survey_dir, 'survey'), 'r')
+        survey = cPickle.load(file)
+        file.close()
+        survey.survey_dir = survey_dir
+
+        config = ConfigParser.SafeConfigParser()
+        config.optionxform = str
+        config.read(os.path.join(survey_dir, 'info'))
+        survey.title = config.get('sdaps', 'title').decode('utf-8')
+
+        survey.global_id = config.get('sdaps', 'global_id').decode('utf-8')
+        if survey.global_id == '' or survey.global_id == 'None':
+            survey.global_id = None
+
+        survey.info = dict()
+        for key, value in config.items('info'):
+            survey.info[key.decode('utf-8')] = value.decode('utf-8')
+
+        # Early versions of SDAPS 1.0 did not have the file version number
+        if not hasattr(survey, 'version'):
+            survey.version = 1
+
+        # Before upgrading, reinit states, so events are "fired" correctly.
+        survey.questionnaire.reinit_state()
+        for sheet in survey.sheets:
+            sheet.reinit_state()
+
+        # Run any upgrade routine (if necessary)
+        survey.upgrade()
+
+        return survey
+
+    @staticmethod
+    def new(survey_dir):
+        survey = Survey()
+        survey.survey_dir = survey_dir
+        return survey
+
+    def save(self):
+        import ConfigParser
+        file = bz2.BZ2File(os.path.join(self.survey_dir, '.survey.tmp'), 'w')
+        cPickle.dump(self, file, 2)
+        file.close()
+
+        # Hack to include comments. Set allow_no_value here, and add keys
+        # with a '#' in the front and no value.
+        config = ConfigParser.SafeConfigParser(allow_no_value=True)
+
+        config.optionxform = str
+        config.add_section('sdaps')
+        config.add_section('info')
+        config.add_section('defs')
+        config.add_section('questionnaire')
+        config.set('sdaps', 'title', self.title.encode('utf-8'))
+        if self.global_id is not None:
+            config.set('sdaps', 'global_id', self.global_id.encode('utf-8'))
+        else:
+            config.set('sdaps', 'global_id', '')
+
+        for key, value in sorted(self.info.iteritems()):
+            config.set('info', key.encode('utf-8'), value.encode('utf-8'))
+
+        config.set('defs', '# These values are not read back, they exist for information only!')
+        for attr in self.defs.__slots__:
+            config.set('defs', attr, str(getattr(self.defs, attr)).encode('utf-8'))
+
+        config.set('questionnaire', '# These values are not read back, they exist for information only!')
+        config.set('questionnaire', 'page_count', str(self.questionnaire.page_count))
+        # Put the survey ID into "questionnaire". This seems sane even though
+        # it is not stored there internally..
+        config.set('questionnaire', 'survey_id', str(self.survey_id))
+
+        info_fd = open(os.path.join(self.survey_dir, '.info.tmp'), 'w')
+        config.write(info_fd)
+
+        # Does that work with the fsync on a different FD?
+        os.fsync(open(os.path.join(self.survey_dir, '.survey.tmp'), 'a'))
+        os.fsync(info_fd)
+        info_fd.close()
+
+        # Now move the new files over, saving the old ones, ignore error to move old files away
+        # as the may not exist.
+        try:
+            os.rename(os.path.join(self.survey_dir, 'survey'), os.path.join(self.survey_dir, 'survey~'))
+            os.rename(os.path.join(self.survey_dir, 'info'), os.path.join(self.survey_dir, 'info~'))
+        except OSError:
+            pass
+
+        os.rename(os.path.join(self.survey_dir, '.survey.tmp'), os.path.join(self.survey_dir, 'survey'))
+        os.rename(os.path.join(self.survey_dir, '.info.tmp'), os.path.join(self.survey_dir, 'info'))
+
+
+    def path(self, *path):
+        return os.path.join(self.survey_dir, *path)
+
+    def new_path(self, path):
+        content = os.listdir(self.path())
+        i = 1
+        while path % i in content:
+            i += 1
+        return os.path.join(self.survey_dir, path % i)
+
+    def get_sheet(self):
+        return self.sheets[self.index]
+
+    #: The currently selected sheet. Usually it will be changed by :py:meth:`iterate` or similar.
+    sheet = property(get_sheet)
+
+    def iterate(self, function, filter=lambda: True, *args, **kwargs):
+        '''call function once for each sheet
+        '''
+        for self.index in range(len(self.sheets)):
+            if filter():
+                function(*args, **kwargs)
+
+    def iterate_progressbar(self, function, filter=lambda: True):
+        '''call function once for each sheet and display a progressbar
+        '''
+        count = 0
+        for self.index in range(len(self.sheets)):
+            if filter():
+                count += 1
+
+        print ungettext('%i sheet', '%i sheets', count) % count
+        if count == 0:
+            return
+
+        log.progressbar.start(len(self.sheets))
+
+        for self.index in range(len(self.sheets)):
+            if filter():
+                function()
+            log.progressbar.update(self.index + 1)
+
+        print _('%f seconds per sheet') % (
+            float(log.progressbar.elapsed_time) /
+            float(log.progressbar.max_value)
+        )
+
+    def goto_sheet(self, sheet):
+        u'''goto the specified sheet object
+        '''
+        self.index = self.sheets.index(sheet)
+
+    def goto_questionnaire_id(self, questionnaire_id):
+        u'''goto the sheet object specified by its questionnaire_id
+        '''
+
+        qids = set()
+        try:
+            qids.add(int(questionnaire_id))
+        except ValueError:
+            pass
+
+        sheets = filter(
+            lambda sheet: sheet.questionnaire_id in qids,
+            self.sheets
+        )
+        if len(sheets) == 1:
+            self.goto_sheet(sheets[0])
+        else:
+            raise ValueError
+
+    def check_settings(self):
+        u'''Do sanity checks on the different settings.'''
+
+        if self.defs.duplex and self.questionnaire.page_count % 2 != 0:
+            print _("A questionnaire that is printed in duplex needs an even amount of pages!")
+            return False
+
+        if self.defs.style == 'classic' and self.questionnaire.page_count > 6:
+            print _("The 'classic' style only supports a maximum of six pages! Use the 'code128' style if you require more pages.")
+            return False
+
+        return True
+
+    def validate_questionnaire_id(self, qid):
+        """Do style specific sanity checks on the questionnaire ID."""
+
+        if self.defs.style == "classic":
+            # The ID needs to be an integer
+            try:
+                return int(qid)
+            except ValueError:
+                log.error(_("IDs need to be integers in \"classic\" style!"))
+                sys.exit(1)
+        elif self.defs.style == "code128":
+            # Check each character for validity
+            for c in unicode(qid):
+                if not c in defs.c128_chars:
+                    log.error(_("Invalid character %s in questionnaire ID \"%s\" in \"code128\" style!") % (c, qid))
+                    sys.exit(1)
+            return qid
+        elif self.defs.style == "custom":
+            log.error(_("SDAPS cannot draw a questionnaire ID with the \"custom\" style. Do this yourself somehow!"))
+            sys.exit(1)
+        elif self.defs.style == "qr":
+          return qid
+        else:
+            AssertionError()
+
+    def __getstate__(self):
+        u'''Only pickle attributes that are in the pickled_attrs set.
+        '''
+        dict = self.__dict__.copy()
+        keys = dict.keys()
+        for key in keys:
+            if not key in self.pickled_attrs:
+                del dict[key]
+        return dict
+
+    def upgrade(self):
+        """Ensure that all data structures conform to this version of SDAPS."""
+
+        msg = _('Running upgrade routines for file format version %i')
+        if self.version < 2:
+            log.warn(msg % (1))
+            # Changes between version 1 and 2:
+            #  * Simplex surveys get a dummy page added for every image. This
+            #    way they can be handled in the same way as "duplex" mode
+            #    (and duplex scan can be supported).
+            #  * The data for "Textbox" has a string. This will be used in the
+            #    report if it contains data.
+
+            # Insert dummy images.
+            if not self.defs.duplex:
+                from sdaps.model.sheet import Image
+
+                for sheet in self.sheets:
+                    images = sheet.images
+
+                    # And re-add
+                    sheet.images = list()
+                    for img in images:
+                        sheet.add_image(img)
+                        img.ignored = False
+
+                        dummy = Image()
+                        dummy.filename = "DUMMY"
+                        dummy.tiff_page = -1
+                        dummy.ignored = True
+
+                        sheet.add_image(dummy)
+
+            # Add the "text" attribute to Textbox.
+            from sdaps.model.data import Textbox
+            for sheet in self.sheets:
+                for data in sheet.data.itervalues():
+                    if isinstance(data, Textbox):
+                        data.text = unicode()
+
+        if self.version < 3:
+            log.warn(msg % (3))
+            for sheet in self.sheets:
+                sheet.recognized = False
+                sheet.verified = False
+
+        if self.version < 4:
+            log.warn(msg % (4))
+            self.defs.checkmode = "checkcorrect"
+
+        self.version = 4
+
diff --git a/sdaps/paths.py b/sdaps/paths.py
new file mode 100644
index 0000000..6d2b67f
--- /dev/null
+++ b/sdaps/paths.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+u'''Some path values, used to find and load program components.
+'''
+
+import os
+
+
+import gettext
+import locale
+
+
+local_run = False
+
+# required if local_run == True
+build_dir = str()
+
+# required if local_run == True
+lib_build_dir = str()
+
+# required if local_run == True
+source_dir = str()
+
+# required if local_run == False
+prefix = str()
+
+
+def init(local_run_value, package_path):
+    u'''Initialize path values for sdaps
+    '''
+    global local_run, build_dir, lib_build_dir, source_dir, prefix
+
+    # Initialize local_run
+    local_run = local_run_value
+
+    base_dir = os.path.split(os.path.abspath(package_path))[0]
+
+    if local_run:
+        source_dir = base_dir
+
+        from pkg_resources import get_build_platform
+        from distutils.sysconfig import get_python_version
+
+        # Initialize gettext
+        init_gettext(os.path.join(
+            base_dir,
+            'build',
+            'mo'))
+
+        # Initialize build_dir
+        build_dir = os.path.join(base_dir, 'build', 'share', 'sdaps')
+
+        # Initialize build_dir
+        lib_build_dir = os.path.join(
+            base_dir,
+            'build', 'lib.%s-%s' % (get_build_platform(), get_python_version()),
+            'sdaps')
+    else:
+        # Look for the data in the parent directories
+        path = base_dir
+        while True:
+            if os.path.exists(os.path.join(path, 'share', 'sdaps')):
+                prefix = path
+                break
+            new_path = os.path.split(path)[0]
+            assert not path == new_path, "could not find locales" # Wir wären oben angekommen
+            path = new_path
+
+        # Initialize gettext
+        init_gettext(os.path.join(prefix, 'share', 'locale'))
+
+
+def init_gettext(locale_dir):
+    u'''Initialize gettext using the given directory containing the l10n data.
+    '''
+    gettext.bindtextdomain('sdaps', locale_dir)
+
+    if hasattr(gettext, 'bind_textdomain_codeset'):
+        gettext.bind_textdomain_codeset('sdaps', 'UTF-8')
+        gettext.textdomain('sdaps')
+    if hasattr(locale, 'bind_textdomain_codeset'):
+        locale.bindtextdomain('sdaps', locale_dir)
+        locale.bind_textdomain_codeset('sdaps', 'UTF-8')
+        locale.textdomain('sdaps')
+
diff --git a/sdaps/recognize/__init__.py b/sdaps/recognize/__init__.py
new file mode 100644
index 0000000..9a86a02
--- /dev/null
+++ b/sdaps/recognize/__init__.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+u"""
+This module adds the functionality to automatically recognize the scanned image
+data. It reads the image data and tries to guess whether a checkbox is
+empty/checked/filled and finds the written area in a textfield.
+"""
+
+from sdaps import model
+
+from . import buddies
+
+
+def recognize(survey, filter):
+    # iterate over sheets
+    survey.iterate_progressbar(survey.questionnaire.recognize.recognize, filter)
+    survey.save()
+
+def identify(survey, filter):
+    # iterate over sheets
+    survey.iterate_progressbar(survey.questionnaire.recognize.identify, filter)
+    survey.save()
+
diff --git a/sdaps/recognize/blank.py b/sdaps/recognize/blank.py
new file mode 100644
index 0000000..fe29238
--- /dev/null
+++ b/sdaps/recognize/blank.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008,2013, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+# Example of using this to preload your own custom buddy. This first sets up the
+# paths, below the call into sdaps happens
+if __name__ == '__main__':
+    import sys
+    import os
+    sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
+
+from sdaps import defs
+
+from sdaps import model
+from sdaps.utils.exceptions import RecognitionError
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+
+class Image(model.buddy.Buddy):
+    """Example buddy class for a "blank" style. It simply assumes
+    that everything is good about the page. ie. first page, no rotation,
+    correct survey ID, and no other IDs."""
+
+    __metaclass__ = model.buddy.Register
+    name = 'style'
+    obj_class = model.sheet.Image
+
+    def get_page_rotation(self):
+        """Return the rotation of the passed image."""
+        # Assume not rotated
+        return False
+
+    def get_page_number(self):
+        """Return page number or None if not known."""
+        # Assume only one page
+        return 1
+
+    def get_survey_id(self):
+        """Return the survey ID as read from the image, or None."""
+        # Return the expected survey ID
+        return self.obj.sheet.survey.survey_id
+
+    def get_questionnaire_id(self):
+        """Return the questionnaire ID as read from the image."""
+        # Assume no (useful) questionnaire ID exists
+        return None
+
+    def get_global_id(self):
+        """Return the global ID as read from the image."""
+        # Assume no (useful) global ID exists
+        return None
+
+# Example of using this to preload your own custom buddy. This assume a local run.
+# You can of course use the API directly too.
+if __name__ == '__main__':
+    import sdaps
+    sys.exit(sdaps.main(local_run = True))
+
diff --git a/sdaps/recognize/buddies.py b/sdaps/recognize/buddies.py
new file mode 100644
index 0000000..9e1cd7a
--- /dev/null
+++ b/sdaps/recognize/buddies.py
@@ -0,0 +1,911 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008,2011, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import cairo
+import math
+
+from sdaps import model
+from sdaps import matrix
+from sdaps import surface
+from sdaps import image
+from sdaps import defs
+from sdaps.utils.exceptions import RecognitionError
+from sdaps import log
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+pt_to_mm = 25.4 / 72.0
+
+warned_multipage_not_correctly_scanned = False
+
+
+class Sheet(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'recognize'
+    obj_class = model.sheet.Sheet
+
+    def recognize(self):
+        global warned_multipage_not_correctly_scanned
+
+        self.obj.valid = 1
+
+        duplex_mode = self.obj.survey.defs.duplex
+
+        # Load all images of this sheet
+        for image in self.obj.images:
+            if not image.ignored:
+                image.rotated = 0
+                image.surface.load()
+
+        failed_pages = set()
+
+        # Matrix recognition for all of them
+        matrix_errors = set()
+        for page, image in enumerate(self.obj.images):
+            try:
+                image.recognize.calculate_matrix()
+            except RecognitionError:
+                matrix_errors.add(page)
+
+        # We need to check the matrix_errors. Some are expected in simplex mode
+        for page in matrix_errors:
+            # in simplex mode every page will have a matrix; it might be a None
+            # matrix though
+
+            log.warn(_('%s, %i: Matrix not recognized.') % (self.obj.images[page].filename, self.obj.images[page].tiff_page))
+            failed_pages.add(page)
+
+        # Rotation for all of them
+        for page, image in enumerate(self.obj.images):
+            try:
+                # This may set the rotation to "None" for unknown
+                image.recognize.calculate_rotation()
+            except RecognitionError:
+                log.warn(_('%s, %i: Rotation not found.') % (image.filename, image.tiff_page))
+                failed_pages.add(page)
+
+        # Copy the rotation over (if required) and print warning if the rotation is unknown
+        self.duplex_copy_image_attr(failed_pages, 'rotated', _("Neither %s, %i or %s, %i has a known rotation!"))
+
+        # Reload any image that is rotated.
+        for page, image in enumerate(self.obj.images):
+            if image.rotated and not image.ignored:
+                image.surface.load()
+                # And redo the whole matrix stuff ...
+                # XXX: It would be better to manipulate the matrix instead.
+                try:
+                    image.recognize.calculate_matrix()
+                except RecognitionError:
+                    if duplex_mode:
+                        log.warn(_('%s, %i: Matrix not recognized (again).') % (image.filename, image.tiff_page))
+                        failed_pages.add(page)
+
+        ############
+        # At this point we can extract the page numbers and IDs as neccessary.
+        ############
+
+        # Figure out the page numbers
+        # ***************************
+        for page, image in enumerate(self.obj.images):
+            try:
+                # This may set the page_number to "None" for unknown
+                image.recognize.calculate_page_number()
+            except RecognitionError:
+                log.warn(_('%s, %i: Could not get page number.') % (image.filename, image.tiff_page))
+                image.page_number = None
+                failed_pages.add(page)
+
+        i = 0
+        while i < len(self.obj.images):
+            # We try to recover at least the page number of failed pages
+            # this way.
+            # NOTE: In simplex mode dummy pages will be inserted, so one page
+            # always has no page number, and the other one has one.
+            # This is exactly what we want, so we don't need to do anything
+            # (except warn if we did not find any page!)
+            failed = (i in failed_pages or i + 1 in failed_pages)
+
+            first = self.obj.images[i]
+            second = self.obj.images[i + 1]
+
+            if first.page_number is None and second.page_number is None:
+                if not failed:
+                    # Whoa, that should not happen.
+                    log.warn(_("Neither %s, %i or %s, %i has a known page number!" %
+                             (first.filename, first.tiff_page, second.filename, second.tiff_page)))
+                    failed_pages.add(i)
+                    failed_pages.add(i + 1)
+
+            elif duplex_mode == False:
+                # Simplex mode is special, as we know that one has to be unreadable
+                # we need to ensure one of the page numbers is None
+                if first.page_number is not None and second.page_number is not None:
+                    # We don't touch the ignore flag in this case
+                    # Simply print a message as this should *never* happen
+                    log.error(_("Got a simplex document where two adjacent pages had a known page number. This should never happen as even simplex scans are converted to duplex by inserting dummy pages. Maybe you did a simplex scan but added it in duplex mode? The pages in question are %s, %i and %s, %i.") % (first.filename, first.tiff_page, second.filename, second.tiff_page))
+
+                # Set the ignored flag for the unreadable page. This is a valid
+                # operation as the back side of a readable page is known to be
+                # empty.
+                elif first.page_number is None:
+                    first.ignored = True
+                else:
+                    second.ignored = True
+
+            elif first.page_number is None:
+                # One based, odd -> +1, even -> -1
+                first.page_number = second.page_number - 1 + 2 * (second.page_number % 2)
+            elif second.page_number is None:
+                second.page_number = first.page_number - 1 + 2 * (first.page_number % 2)
+            elif first.page_number != (second.page_number - 1 + 2 * (second.page_number % 2)):
+                if not failed:
+                    log.warn(_("Images %s, %i and %s, %i do not have consecutive page numbers!" %
+                             (first.filename, first.tiff_page, second.filename, second.tiff_page)))
+
+                    failed_pages.add(i)
+                    failed_pages.add(i + 1)
+
+            i += 2
+
+        # Check that every page has a non None value, and each page exists once.
+        pages = set()
+        for i, image in enumerate(self.obj.images):
+            # Ignore known blank pages
+            if image.ignored:
+                continue
+
+            if image.page_number is None:
+                log.warn(_("No page number for page %s, %i exists." % (image.filename, image.tiff_page)))
+                failed_pages.add(i)
+                continue
+
+            if image.page_number in pages:
+                log.warn(_("Page number for page %s, %i already used by another image.") %
+                         (image.filename, image.tiff_page))
+                failed_pages.add(i)
+                continue
+
+            if image.page_number <= 0 or image.page_number > self.obj.survey.questionnaire.page_count:
+                log.warn(_("Page number %i for page %s, %i is out of range.") %
+                         (image.page_number, image.filename, image.tiff_page))
+                failed_pages.add(i)
+                continue
+
+            pages.add(image.page_number)
+
+        # Figure out the suvey ID if neccessary
+        # *************************************
+        if self.obj.survey.defs.print_survey_id:
+            for page, image in enumerate(self.obj.images):
+                try:
+                    if not duplex_mode or (image.page_number is not None and image.page_number % 2 == 0):
+                        image.recognize.calculate_survey_id()
+                    else:
+                        image.survey_id = None
+                except RecognitionError:
+                    log.warn(_('%s, %i: Could not read survey ID, but should be able to.') %
+                             (image.filename, image.tiff_page))
+                    failed_pages.add(page)
+
+            self.duplex_copy_image_attr(failed_pages, "survey_id", _("Could not read survey ID of either %s, %i or %s, %i!"))
+
+            # Simply use the survey ID from the first image globally
+            self.obj.survey_id = self.obj.images[0].survey_id
+
+            if self.obj.survey_id != self.obj.survey.survey_id:
+                # Broken survey ID ...
+                log.warn(_("Got a wrong survey ID (%s, %i)! It is %s, but should be %i.") %
+                         (self.obj.images[0].filename,
+                          self.obj.images[0].tiff_page,
+                          self.obj.survey_id,
+                          self.obj.survey.survey_id))
+                self.obj.valid = 0
+        else:
+            # Assume that the data is from the correct survey
+            self.obj.survey_id = self.obj.survey.survey_id
+            for image in self.obj.images:
+                image.survey_id = self.obj.survey.survey_id
+
+        # Figure out the questionnaire ID if neccessary
+        # *********************************************
+        if self.obj.survey.defs.print_questionnaire_id:
+            questionnaire_ids = []
+
+            for page, image in enumerate(self.obj.images):
+                try:
+                    if not duplex_mode or (image.page_number is not None and image.page_number % 2 == 0):
+                        image.recognize.calculate_questionnaire_id()
+                except RecognitionError:
+                    log.warn(_('%s, %i: Could not read questionnaire ID, but should be able to.') % \
+                             (image.filename, image.tiff_page))
+                    failed_pages.add(page)
+                if image.questionnaire_id is not None:
+                    questionnaire_ids.append(image.questionnaire_id)
+
+            self.duplex_copy_image_attr(failed_pages, "questionnaire_id", _("Could not read questionnaire ID of either %s, %i or %s, %i!"))
+
+            if len(questionnaire_ids):
+                self.obj.questionnaire_id = questionnaire_ids[0]
+            else:
+                self.obj.questionnaire_id = self.obj.images[0].questionnaire_id
+
+        # Try to load the global ID. If it does not exist we will get None, if
+        # it does, then it will be non-None. We don't care much about it
+        # internally anyways.
+        # However, we do want to ensure that it is the same everywhere if it
+        # can be read in.
+        # *********************************************
+        for page, image in enumerate(self.obj.images):
+            try:
+                if not duplex_mode or (image.page_number is not None and image.page_number % 2 == 0):
+                    image.recognize.calculate_global_id()
+            except RecognitionError:
+                pass
+
+        self.duplex_copy_image_attr(failed_pages, "global_id")
+
+        self.obj.global_id = self.obj.images[0].global_id
+
+        for image in self.obj.images:
+            if self.obj.global_id != image.global_id or \
+                self.obj.survey_id != image.survey_id or \
+                self.obj.questionnaire_id != image.questionnaire_id:
+
+                if not warned_multipage_not_correctly_scanned:
+                    log.warn(_("Got different IDs on different pages for at least one sheet! Do *NOT* try to use filters with this survey! You have to run a \"reorder\" step for this to work properly!"))
+
+                    warned_multipage_not_correctly_scanned = True
+
+        # Done
+        if failed_pages:
+            self.obj.valid = 0
+
+    def clean(self):
+        for image in self.obj.images:
+            image.recognize.clean()
+
+    def duplex_copy_image_attr(self, failed_pages, attr, error_msg=None):
+        u"""If in duplex mode, this function will copy the given attribute
+        from the image that defines it over to the one that does not.
+        ie. if the attribute is None in one and differently in the other image
+        it is copied.
+
+        """
+
+        i = 0
+        while i < len(self.obj.images):
+            failed = (i in failed_pages or i + 1 in failed_pages)
+
+            first = self.obj.images[i]
+            second = self.obj.images[i + 1]
+
+            if getattr(first, attr) is None and getattr(second, attr) is None:
+                if error_msg is not None and not failed:
+                    log.warn(error_msg % (first.filename, first.tiff_page, second.filename, second.tiff_page))
+            elif getattr(first, attr) is None:
+                setattr(first, attr, getattr(second, attr))
+            elif getattr(second, attr) is None:
+                setattr(second, attr, getattr(first, attr))
+
+            i += 2
+
+
+class Image(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'recognize'
+    obj_class = model.sheet.Image
+
+    def __init__(self, *args):
+        model.buddy.Buddy.__init__(self, *args)
+
+        if self.obj.sheet.survey.defs.style == "classic":
+            import classic
+        elif self.obj.sheet.survey.defs.style == "code128":
+            import code128
+        elif self.obj.sheet.survey.defs.style == "qr":
+            import qrcode
+        elif self.obj.sheet.survey.defs.style == "custom":
+            if not hasattr(self.obj, "style"):
+                import sys
+                log.error(_("No style buddy loaded. This needs to be done for the \"custom\" style!"))
+                sys.exit(1)
+        else:
+            raise AssertionError
+
+    def calculate_rotation(self):
+        if self.obj.ignored:
+            self.obj.rotated = None
+        else:
+            self.obj.rotated = self.obj.style.get_page_rotation()
+
+    def calculate_page_number(self):
+        if self.obj.ignored:
+            self.obj.page_number = None
+        else:
+            self.obj.page_number = self.obj.style.get_page_number()
+
+    def calculate_survey_id(self):
+        if self.obj.ignored:
+            self.obj.survey_id = None
+        else:
+            self.obj.survey_id = self.obj.style.get_survey_id()
+
+    def calculate_questionnaire_id(self):
+        if self.obj.ignored:
+            self.obj.questionnaire_id = None
+        else:
+            self.obj.questionnaire_id = self.obj.style.get_questionnaire_id()
+
+    def calculate_global_id(self):
+        if self.obj.ignored:
+            self.obj.global_id = None
+        else:
+            self.obj.global_id = self.obj.style.get_global_id()
+
+    def clean(self):
+        self.obj.surface.clean()
+
+    def calculate_matrix(self):
+        if self.obj.ignored:
+            self.obj.matrix.set_px_to_mm(None)
+            return
+
+        try:
+            # Reset matrix, so that we do not use some existing (and broken)
+            # matrix for the resolution estimation.
+            self.obj.matrix.set_px_to_mm(None)
+
+            matrix = image.calculate_matrix(
+                self.obj.surface.surface,
+                self.obj.matrix.mm_to_px(),
+                defs.corner_mark_left, defs.corner_mark_top,
+                self.obj.sheet.survey.defs.paper_width - defs.corner_mark_left - defs.corner_mark_right,
+                self.obj.sheet.survey.defs.paper_height - defs.corner_mark_top - defs.corner_mark_bottom,
+            )
+        except AssertionError:
+            self.obj.matrix.set_px_to_mm(None)
+            raise RecognitionError
+        else:
+            self.obj.matrix.set_px_to_mm(matrix)
+
+    def get_coverage(self, x, y, width, height):
+        assert(not self.obj.ignored)
+
+        return image.get_coverage(
+            self.obj.surface.surface,
+            self.matrix,
+            x, y, width, height
+        )
+
+    def get_masked_coverage(self, mask, x, y):
+        assert(not self.obj.ignored)
+
+        return image.get_masked_coverage(
+            self.obj.surface.surface,
+            mask,
+            x, y
+        )
+
+    def get_masked_coverage_without_lines(self, mask, x, y, line_width, line_count):
+        assert(not self.obj.ignored)
+
+        return image.get_masked_coverage_without_lines(
+            self.obj.surface.surface,
+            mask, x, y,
+            line_width, line_count
+        )
+
+    def get_masked_white_area_count(self, mask, x, y, min_size, max_size):
+        assert(not self.obj.ignored)
+
+        return image.get_masked_white_area_count(
+            self.obj.surface.surface,
+            mask, x, y,
+            min_size, max_size
+        )
+
+    def correction_matrix_masked(self, x, y, mask):
+        assert(not self.obj.ignored)
+
+        return image.calculate_correction_matrix_masked(
+            self.obj.surface.surface,
+            mask,
+            self.matrix,
+            x, y
+        )
+
+    def find_box_corners(self, x, y, width, height):
+        assert(not self.obj.ignored)
+
+        tl, tr, br, bl = image.find_box_corners(
+            self.obj.surface.surface,
+            self.matrix,
+            x, y,
+            width, height)
+
+        tolerance = defs.find_box_corners_tolerance
+        if(abs(x - tl[0]) > tolerance or
+            abs(y - tl[1]) > tolerance or
+            abs(x + width - tr[0]) > tolerance or
+            abs(y - tr[1]) > tolerance or
+            abs(x + width - br[0]) > tolerance or
+            abs(y + height - br[1]) > tolerance or
+            abs(x - bl[0]) > tolerance or
+            abs(y + height - bl[1]) > tolerance
+           ):
+            raise AssertionError("The found values differ too much from where the box should be.")
+        return tl, tr, br, bl
+
+    @property
+    def matrix(self):
+        return self.obj.matrix.mm_to_px(fallback=False)
+
+class Questionnaire(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'recognize'
+    obj_class = model.questionnaire.Questionnaire
+
+    def identify(self, clean=True):
+        # recognize image
+        try:
+            self.obj.sheet.recognize.recognize()
+            result = True
+        except RecognitionError:
+            self.obj.sheet.quality = 0
+            result = False
+
+        # clean up
+        if clean:
+            self.obj.sheet.recognize.clean()
+
+        return result
+
+    def recognize(self):
+        # recognize image
+        res = self.identify(clean=False)
+        if res:
+            # iterate over qobjects
+            for qobject in self.obj.qobjects:
+                qobject.recognize.recognize()
+
+            quality = 1
+            for qobject in self.obj.qobjects:
+                quality = min(quality, qobject.recognize.get_quality())
+            self.obj.sheet.quality = quality
+
+        # Mark the image as "recognized". It might have failed, but even if that
+        # happened, we don't want to retry all the time.
+        self.obj.sheet.recognized = True
+        # Any newly recognized sheet is definately not verified.
+        # This is relevant for reruns.
+        self.obj.sheet.verified = False
+
+        # clean up
+        self.obj.sheet.recognize.clean()
+
+
+class QObject(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'recognize'
+    obj_class = model.questionnaire.QObject
+
+    def recognize(self):
+        pass
+
+    def get_quality(self):
+        return 1
+
+
+class Question(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'recognize'
+    obj_class = model.questionnaire.Question
+
+    def recognize(self):
+        # iterate over boxes
+        for box in self.obj.boxes:
+            box.recognize.recognize()
+
+    def get_quality(self):
+        result = 1
+        for box in self.obj.boxes:
+            result = min(result, box.data.quality)
+        return result
+
+#class Choice(Question):
+
+    #__metaclass__ = model.buddy.Register
+    #name = 'recognize'
+    #obj_class = model.questionnaire.Choice
+
+
+#class Mark(Question):
+
+    #__metaclass__ = model.buddy.Register
+    #name = 'recognize'
+    #obj_class = model.questionnaire.Mark
+
+
+class Box(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'recognize'
+    obj_class = model.questionnaire.Box
+
+    def recognize(self):
+        pass
+
+
+class Checkbox(Box):
+
+    __metaclass__ = model.buddy.Register
+    name = 'recognize'
+    obj_class = model.questionnaire.Checkbox
+
+    def prepare_mask(self):
+        img = self.obj.sheet.get_page_image(self.obj.page_number)
+        width, height = self.obj.width, self.obj.height
+
+        matrix = list(img.recognize.matrix)
+        # Remove any offset from the matrix
+        matrix[4] = 0
+        matrix[5] = 0
+        matrix = cairo.Matrix(*matrix)
+
+        px_width, px_height = matrix.transform_distance(width, height)
+        px_width, px_height = int(math.ceil(px_width)), int(math.ceil(px_height))
+
+        surf = cairo.ImageSurface(cairo.FORMAT_A1, px_width, px_height)
+        cr = cairo.Context(surf)
+        cr.set_source_rgba(0, 0, 0, 0)
+        cr.set_operator(cairo.OPERATOR_SOURCE)
+        cr.paint()
+
+        # Move to center and apply matrix
+        cr.translate(0.5 * px_width, 0.5 * px_height)
+        cr.transform(matrix)
+
+        cr.set_source_rgba(0, 0, 0, 1)
+
+        line_width = 1 / 72.0 * 25.4
+        cr.set_line_width(line_width)
+
+        matrix.invert()
+        xoff, yoff = matrix.transform_distance(px_width / 2.0, px_height / 2.0)
+        xoff = xoff - width / 2
+        yoff = yoff - height / 2
+
+        return cr, surf, line_width, width, height, xoff, yoff
+
+    def get_outline_mask(self):
+        cr, surf, line_width, width, height, xoff, yoff = self.prepare_mask()
+
+        if self.obj.form == "ellipse":
+            cr.save()
+
+            cr.scale((width - line_width) / 2.0, (height - line_width) / 2.0)
+            cr.arc(0, 0, 1.0, 0, 2*math.pi)
+
+            # Restore old matrix (without removing the current path)
+            cr.restore()
+        else:
+            cr.translate(-0.5 * width, -0.5 * height)
+            cr.rectangle(line_width / 2, line_width / 2, width - line_width / 2, height - line_width / 2)
+
+        cr.stroke()
+        surf.flush()
+        del cr
+
+        return surf, xoff, yoff
+
+    def get_inner_mask(self):
+        cr, surf, line_width, width, height, xoff, yoff = self.prepare_mask()
+
+        if self.obj.form == "ellipse":
+            cr.save()
+
+            cr.scale((width - 3*line_width) / 2.0, (height - 3*line_width) / 2.0)
+            cr.arc(0, 0, 1.0, 0, 2*math.pi)
+
+            # Restore old matrix (without removing the current path)
+            cr.restore()
+        else:
+            cr.translate(-0.5 * width, -0.5 * height)
+            cr.rectangle(1.5 * line_width, 1.5 * line_width, width - 3 * line_width, height - 3 * line_width)
+
+        cr.fill()
+        surf.flush()
+        del cr
+
+        return surf, xoff, yoff
+
+
+    def recognize(self):
+        img = self.obj.sheet.get_page_image(self.obj.page_number)
+
+        if img is None or img.recognize.matrix is None:
+            self.obj.sheet.valid = 0
+            return
+
+        surf, xoff, yoff = self.get_outline_mask()
+
+        matrix, covered = img.recognize.correction_matrix_masked(
+            self.obj.x, self.obj.y,
+            surf
+        )
+        # Calculate some sort of quality for the checkbox position
+        if covered < defs.image_line_coverage:
+            pos_quality = 0
+        else:
+            pos_quality = min(covered + 0.2, 1)
+
+        x, y = matrix.transform_point(self.obj.x, self.obj.y)
+        width, height = matrix.transform_distance(self.obj.width, self.obj.height)
+        self.obj.data.x = x + xoff
+        self.obj.data.y = y + yoff
+        self.obj.data.width = width
+        self.obj.data.height = height
+
+        # The debug struct will be filled in if debugging is enabled in the
+        # C library. This is done by the boxgallery script currently.
+        self.debug = {}
+
+        mask, xoff, yoff = self.get_inner_mask()
+        x, y = self.obj.data.x, self.obj.data.y
+        x, y = x + xoff, y + yoff
+
+        x, y = img.recognize.matrix.transform_point(x, y)
+        x, y = int(x), int(y)
+
+        remove_line_width = 1.2 * 25.4 / 72.0
+        remove_line_width_px = max(img.recognize.matrix.transform_distance(remove_line_width, remove_line_width))
+
+        coverage = img.recognize.get_masked_coverage(mask, x, y)
+        self.obj.data.metrics['coverage'] = coverage
+        self.debug['coverage'] = image.get_debug_surface()
+
+        # Remove 3 lines with width 1.2pt(about 5px).
+        coverage = img.recognize.get_masked_coverage_without_lines(mask, x, y, remove_line_width_px, 3)
+        self.obj.data.metrics['cov-lines-removed'] = coverage
+        self.debug['cov-lines-removed'] = image.get_debug_surface()
+
+        count, coverage = img.recognize.get_masked_white_area_count(mask, x, y, 0.05, 1.0)
+        self.obj.data.metrics['cov-min-size'] = coverage
+        self.debug['cov-min-size'] = image.get_debug_surface()
+
+        state = 0
+        quality = -1
+        # Iterate the ranges
+        for metric, value in self.obj.data.metrics.iteritems():
+            metric = defs.checkbox_metrics[self.obj.sheet.survey.defs.checkmode][metric]
+
+            for lower, upper in zip(metric[:-1], metric[1:]):
+                if value >= lower[0] and value <= upper[0]:
+                    # Interpolate quality value
+                    if lower[0] != upper[0]:
+                        metric_quality = lower[2] + (upper[2] - lower[2]) * (value - lower[0]) / (upper[0] - lower[0])
+                    else:
+                        metric_quality = lower[2]
+
+                    if metric_quality > quality:
+                        state = lower[1]
+                        quality = metric_quality
+
+        self.obj.data.state = state
+        self.obj.data.quality = min(quality, pos_quality)
+
+
+class Textbox(Box):
+
+    __metaclass__ = model.buddy.Register
+    name = 'recognize'
+    obj_class = model.questionnaire.Textbox
+
+    def recognize(self):
+        class Quadrilateral():
+            """This class iterates through small box areas in a quadriliteral.
+            This is usefull because some scanners have trapezoidal distortions."""
+            # Assumes top left, top right, bottom right, bottom left
+            # corner.
+            def __init__(self, p0, p1, p2, p3):
+                self.x0 = p0[0]
+                self.y0 = p0[1]
+                self.x1 = p1[0]
+                self.y1 = p1[1]
+                self.x2 = p2[0]
+                self.y2 = p2[1]
+                self.x3 = p3[0]
+                self.y3 = p3[1]
+
+                # 0 -> 1
+                self.m0 = (self.y1 - self.y0) / (self.x1 - self.x0)
+                self.m1 = (self.x2 - self.x1) / (self.y2 - self.y1)
+                self.m2 = (self.y3 - self.y2) / (self.x3 - self.x2)
+                self.m3 = (self.x0 - self.x3) / (self.y0 - self.y3)
+
+                self.top = min(self.y0, self.y1)
+                self.bottom = max(self.y2, self.y3)
+                self.left = min(self.x0, self.x3)
+                self.right = max(self.x1, self.x2)
+
+            def iterate_bb(self, step_x, step_y, test_width, test_height, padding):
+                y = self.top
+                while y + test_height < self.bottom:
+                    x = self.left
+                    while x + test_width < self.right:
+                        yield x, y
+                        x += step_x
+
+                    y += step_y
+
+            def iterate_outline(self, step_x, step_y, test_width, test_height, padding):
+                # Top
+                x, y = self.x0, self.y0
+                x += padding
+                y += padding
+
+                dest_x = self.x1 - padding - test_width
+                dest_y = self.y1 + padding
+
+                dist_x = dest_x - x
+                dist_y = dest_y - y
+
+                length = math.sqrt(dist_x ** 2 + dist_y ** 2)
+                for step in xrange(int(length / step_x)):
+                    yield x + dist_x * step / (length / step_x), y + dist_y * step / (length / step_x)
+                yield dest_x, dest_y
+
+                # Bottom
+                x, y = self.x3, self.y3
+                x = x + padding
+                y = y - padding - test_height
+
+                dest_x = self.x2 - padding - test_width
+                dest_y = self.y2 - padding - test_height
+
+                dist_x = dest_x - x
+                dist_y = dest_y - y
+
+                length = math.sqrt(dist_x ** 2 + dist_y ** 2)
+                for step in xrange(int(length / step_x)):
+                    yield x + dist_x * step / (length / step_x), y + dist_y * step / (length / step_x)
+                yield dest_x, dest_y
+
+                # Left
+                x, y = self.x0, self.y0
+                x += padding
+                y += padding
+
+                dest_x = self.x3 + padding
+                dest_y = self.y3 - padding - test_height
+
+                dist_x = dest_x - x
+                dist_y = dest_y - y
+
+                length = math.sqrt(dist_x ** 2 + dist_y ** 2)
+                for step in xrange(int(length / step_y)):
+                    yield x + dist_x * step / (length / step_y), y + dist_y * step / (length / step_y)
+                yield dest_x, dest_y
+
+                # Right
+                x, y = self.x1, self.y1
+                x = x - padding - test_width
+                y = y + padding
+
+                dest_x = self.x2 - padding - test_width
+                dest_y = self.y2 - padding - test_height
+
+                dist_x = dest_x - x
+                dist_y = dest_y - y
+
+                length = math.sqrt(dist_x ** 2 + dist_y ** 2)
+                for step in xrange(int(length / step_y)):
+                    yield x + dist_x * step / (length / step_y), y + dist_y * step / (length / step_y)
+                yield dest_x, dest_y
+
+            def iterate(self, step_x, step_y, test_width, test_height, padding):
+                for x, y in self.iterate_bb(step_x, step_y, test_width, test_height, padding):
+                    ly = self.y0 + self.m0 * (x - self.x0)
+                    if not ly + padding < y:
+                        continue
+
+                    ly = self.y2 + self.m2 * (x - self.x2)
+                    if not ly - padding > y + test_height:
+                        continue
+
+                    lx = self.x1 + self.m1 * (y - self.y1)
+                    if not lx - padding > x + test_width:
+                        continue
+
+                    lx = self.x3 + self.m3 * (y - self.y3)
+                    if not lx + padding < x:
+                        continue
+
+                    yield x, y
+
+                for x, y in self.iterate_outline(step_x, step_y, test_width, test_height, padding):
+                    yield x, y
+
+        bbox = None
+        img = self.obj.sheet.get_page_image(self.obj.page_number)
+
+        if img is None or img.recognize.matrix is None:
+            self.obj.sheet.valid = 0
+            return
+
+        x = self.obj.x
+        y = self.obj.y
+        width = self.obj.width
+        height = self.obj.height
+
+        # Scanning area and stepping
+        step_x = defs.textbox_scan_step_x
+        step_y = defs.textbox_scan_step_x
+        test_width = defs.textbox_scan_width
+        test_height = defs.textbox_scan_height
+
+        # extra_padding is always added to the box side at the end.
+        extra_padding = defs.textbox_extra_padding
+        scan_padding = defs.textbox_scan_uncorrected_padding
+
+        quad = Quadrilateral((x, y), (x + width, y), (x + width, y + height), (x, y + height))
+        try:
+            quad = Quadrilateral(*img.recognize.find_box_corners(x, y, width, height))
+            # Lower padding, as we found the corners and are therefore more acurate
+            scan_padding = defs.textbox_scan_padding
+        except AssertionError:
+            pass
+
+        surface = img.surface.surface
+        matrix = img.recognize.matrix
+        for x, y in quad.iterate(step_x, step_y, test_width, test_height, scan_padding):
+            # Use the image module directly as we are calling in *a lot*
+            coverage = image.get_coverage(surface, matrix, x, y, test_width, test_height)
+            if coverage > defs.textbox_scan_coverage:
+                if not bbox:
+                    bbox = [x, y, test_width, test_height]
+                else:
+                    bbox_x = min(bbox[0], x)
+                    bbox_y = min(bbox[1], y)
+                    bbox[2] = max(bbox[0] + bbox[2], x + test_width) - bbox_x
+                    bbox[3] = max(bbox[1] + bbox[3], y + test_height) - bbox_y
+                    bbox[0] = bbox_x
+                    bbox[1] = bbox_y
+
+        if bbox and (bbox[2] > defs.textbox_minimum_writing_width or
+                     bbox[3] > defs.textbox_minimum_writing_height):
+            # Do not accept very small bounding boxes.
+            self.obj.data.state = True
+
+            self.obj.data.x = bbox[0] - (scan_padding + extra_padding)
+            self.obj.data.y = bbox[1] - (scan_padding + extra_padding)
+            self.obj.data.width = bbox[2] + 2 * (scan_padding + extra_padding)
+            self.obj.data.height = bbox[3] + 2 * (scan_padding + extra_padding)
+        else:
+            self.obj.data.state = False
+
+            self.obj.data.x = self.obj.x
+            self.obj.data.y = self.obj.y
+            self.obj.data.width = self.obj.width
+            self.obj.data.height = self.obj.height
diff --git a/sdaps/recognize/classic.py b/sdaps/recognize/classic.py
new file mode 100644
index 0000000..b5353a0
--- /dev/null
+++ b/sdaps/recognize/classic.py
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008,2011, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+from sdaps import defs
+
+from sdaps import model
+from sdaps.utils.exceptions import RecognitionError
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+# Note, while it is possible to cache data in the image object, this data
+# will not change when the image is switched to the next sheet. So one needs
+# to be careful when to invalidate it.
+
+
+# All of these functions can assume that the matrix has been recognized
+# for the image.
+# They may throw a RecognitionError if there was an unrecoverable error.
+# If no data can be retrieved(because eg. it is not printed on that
+# page) they may return None to indicate this.
+
+class Image(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'style'
+    obj_class = model.sheet.Image
+
+    def get_page_rotation(self):
+        # Returns page rotation or "None" if it cannot be retrieved
+
+        res = get_pagenumber_and_rotation(self)
+        if res is None:
+            return None
+
+        return res[0]
+
+    def get_page_number(self):
+        # Returns page number or "None" if it cannot be retrieved
+        # Returns page number rotation or "None" if it cannot be retrieved
+
+        res = get_pagenumber_and_rotation(self)
+        if res is None:
+            return None
+        # The page may not be rotated at this point
+        if res[0]:
+            raise RecognitionError
+
+        return res[1]
+
+    def get_survey_id(self):
+        # Returns the survey ID or "None" if it cannot be retrieved
+
+        if self.obj.page_number % 2 == 0 or \
+           self.obj.sheet.survey.questionnaire.page_count == 1:
+            pos = self.obj.sheet.survey.defs.get_survey_id_pos()
+
+            survey_id = read_codebox(self,
+                                     pos[0], pos[2])
+            survey_id = read_codebox(self,
+                                     pos[1], pos[2],
+                                     survey_id)
+
+            return survey_id
+        else:
+            return None
+
+    def get_questionnaire_id(self):
+        # Returns the questionnaire ID or "None" if it cannot be retrieved
+
+        if self.obj.page_number % 2 == 0 or \
+           self.obj.sheet.survey.questionnaire.page_count == 1:
+            pos = self.obj.sheet.survey.defs.get_questionnaire_id_pos()
+
+            questionnaire_id = read_codebox(self,
+                                            pos[0], pos[2])
+
+            return questionnaire_id
+        else:
+            return None
+
+    def get_global_id(self):
+        # The classic style does not support a global ID property.
+        return None
+
+
+############################
+# Internal Helpers
+############################
+
+def read_codebox(self, x, y, code=0):
+    for i in range(defs.codebox_length):
+        code <<= 1
+        coverage = self.obj.recognize.get_coverage(
+            x + (i * defs.codebox_step) + defs.codebox_offset,
+            y + defs.codebox_offset,
+            defs.codebox_step - 2 * defs.codebox_offset,
+            defs.codebox_height - 2 * defs.codebox_offset
+        )
+        if coverage > defs.codebox_on_coverage:
+            code += 1
+    return code
+
+
+def get_pagenumber_and_rotation(self):
+    # The coordinates in defs are the center of the line, not the bounding box of the box ...
+    # Its a bug im stamp
+    # So we need to adjust them
+    half_pt = 0.5 / 72.0 * 25.4
+    pt = 1.0 / 72.0 * 25.4
+
+    # Check whether there is a valid transformation matrix. If not simply return.
+    if self.obj.matrix.mm_to_px(False) is None:
+        return
+
+    width = defs.corner_box_width
+    height = defs.corner_box_height
+    padding = defs.corner_box_padding
+    survey = self.obj.sheet.survey
+    corner_boxes_positions = [
+        (defs.corner_mark_left + padding, defs.corner_mark_top + padding),
+        (survey.defs.paper_width - defs.corner_mark_right - padding - width, defs.corner_mark_top + padding),
+        (defs.corner_mark_left + padding, survey.defs.paper_height - defs.corner_mark_bottom - padding - height),
+        (survey.defs.paper_width - defs.corner_mark_right - padding - width,
+         survey.defs.paper_height - defs.corner_mark_bottom - padding - height)
+    ]
+    corners = [
+        int(self.obj.recognize.get_coverage(
+            corner[0] - half_pt,
+            corner[1] - half_pt,
+            width + pt,
+            height + pt
+        ) > defs.cornerbox_on_coverage)
+        for corner in corner_boxes_positions
+    ]
+
+    try:
+        page_number = defs.corner_boxes.index(corners) + 1
+        rotated = False
+    except ValueError:
+        try:
+            page_number = defs.corner_boxes.index(corners[::-1]) + 1
+            rotated = True
+        except ValueError:
+            raise RecognitionError
+
+    return rotated, page_number
+
diff --git a/sdaps/recognize/code128.py b/sdaps/recognize/code128.py
new file mode 100644
index 0000000..8841354
--- /dev/null
+++ b/sdaps/recognize/code128.py
@@ -0,0 +1,149 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2012 Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+from sdaps import defs
+
+from sdaps import model
+from sdaps.utils.exceptions import RecognitionError
+from sdaps.utils.barcode import read_barcode
+
+
+# Reading the metainformation of CODE-128 style questionnaires. See classic.py
+# for some more information.
+
+class Image(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'style'
+    obj_class = model.sheet.Image
+
+    def get_page_rotation(self):
+        # Returns page rotation or "None" if it cannot be retrieved
+
+        # Figure out wether the page is rotated by looking for a barcode first
+        # at the bottom right, then at the top left.
+        # Note that we do not care about the value of the barcode, we are happy
+        # to simply know that it exists.
+
+        paper_width = self.obj.sheet.survey.defs.paper_width
+        paper_height = self.obj.sheet.survey.defs.paper_height
+
+        # Search for the barcode in the lower right corner.
+        # Note that we cannot find another barcode this way, because the one in the
+        # center of the page is not complete
+        code = \
+            read_barcode(self.obj.surface.surface, self.obj.matrix.mm_to_px(),
+                         paper_width / 2,
+                         paper_height - defs.corner_mark_bottom - defs.code128_vpad - defs.code128_height - 5,
+                         paper_width / 2,
+                         defs.corner_mark_bottom + defs.code128_vpad + defs.code128_height + 5)
+
+        if code is None:
+            # Well, that failed, so try to search the upper left corner instead
+            code = \
+                read_barcode(self.obj.surface.surface, self.obj.matrix.mm_to_px(),
+                             0, 0,
+                             paper_width / 2,
+                             defs.corner_mark_bottom + defs.code128_vpad + defs.code128_height + 5)
+
+            if code is not None:
+                return True
+            else:
+                return None
+        else:
+            return False
+
+
+    def get_page_number(self):
+        # Returns page number or "None" if it cannot be retrieved
+
+        # In this function assume that the rotation is correct already.
+        paper_width = self.obj.sheet.survey.defs.paper_width
+        paper_height = self.obj.sheet.survey.defs.paper_height
+
+        # Search for the barcode in the lower right corner.
+        code = \
+            read_barcode(self.obj.surface.surface, self.obj.matrix.mm_to_px(),
+                         paper_width / 2,
+                         paper_height - defs.corner_mark_bottom - defs.code128_vpad - defs.code128_height - 5,
+                         paper_width / 2,
+                         defs.corner_mark_bottom + defs.code128_vpad + defs.code128_height + 5)
+
+        # The code needs to be entirely numeric and at least 4 characters for the page
+        if code is None or(not code.isdigit() and len(code) < 4):
+            return None
+
+        # The page number is in the lower four digits, simply extract it and convert
+        # to integer
+        return int(code[-4:])
+
+    def get_survey_id(self):
+        # Returns the survey ID or "None" if it cannot be retrieved
+
+        # In this function assume that the rotation is correct already.
+        paper_width = self.obj.sheet.survey.defs.paper_width
+        paper_height = self.obj.sheet.survey.defs.paper_height
+
+        # Search for the barcode in the lower left corner.
+        code = \
+            read_barcode(self.obj.surface.surface, self.obj.matrix.mm_to_px(),
+                         paper_width / 2,
+                         paper_height - defs.corner_mark_bottom - defs.code128_vpad - defs.code128_height - 5,
+                         paper_width / 2,
+                         defs.corner_mark_bottom + defs.code128_vpad + defs.code128_height + 5)
+
+        if code is None or not code.isdigit() or len(code) <= 4:
+            return None
+
+        return int(code[:-4])
+
+    def get_questionnaire_id(self):
+        # Returns the questionnaire ID or "None" if it cannot be retrieved
+
+        # In this function assume that the rotation is correct already.
+        paper_width = self.obj.sheet.survey.defs.paper_width
+        paper_height = self.obj.sheet.survey.defs.paper_height
+
+        # Search for the barcode on the bottom left of the page
+        code = \
+            read_barcode(self.obj.surface.surface, self.obj.matrix.mm_to_px(),
+                         0,
+                         paper_height - defs.corner_mark_bottom - defs.code128_vpad - defs.code128_height - 5,
+                         paper_width / 2,
+                         defs.corner_mark_bottom + defs.code128_vpad + defs.code128_height + 5)
+
+        # Simply return the code, it may be alphanumeric, we don't care here
+        # XXX: Is that assumption sane?
+        return code
+
+    def get_global_id(self):
+        # Returns the global ID or "None" if it cannot be retrieved
+
+        # In this function assume that the rotation is correct already.
+        paper_width = self.obj.sheet.survey.defs.paper_width
+        paper_height = self.obj.sheet.survey.defs.paper_height
+
+        # Search for the barcode in the bottom center of the page
+        code = \
+            read_barcode(self.obj.surface.surface, self.obj.matrix.mm_to_px(),
+                         paper_width / 4,
+                         paper_height - defs.corner_mark_bottom - defs.code128_vpad - defs.code128_height - 5,
+                         paper_width / 2,
+                         defs.corner_mark_bottom + defs.code128_vpad + defs.code128_height + 5)
+
+        # Simply return the code, it may be alphanumeric, we don't care here
+        return code
diff --git a/sdaps/recognize/qrcode.py b/sdaps/recognize/qrcode.py
new file mode 100644
index 0000000..58aae29
--- /dev/null
+++ b/sdaps/recognize/qrcode.py
@@ -0,0 +1,103 @@
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2014, Michael Batchelor <batchelor at nationbuilder.com>
+# Copyright(C) 2014, Chuck Collins <chuck at nationbuilder.com>
+# Copyright(C) 2014, Jacob Green <jacob at nationbuilder.com>
+# Copyright(C) 2014, Dustin Ngo <dustin at nationbuilder.com>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+from sdaps import defs
+
+from sdaps import model
+from sdaps.utils.exceptions import RecognitionError
+from sdaps.utils.barcode import read_barcode
+
+
+class Image(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'style'
+    obj_class = model.sheet.Image
+
+    def get_page_rotation(self):
+        code = self.find_bottom_right_barcode()
+        if code is None:
+            # Well, that failed, so try to search the upper left corner instead
+            code = self.find_top_left_barcode()
+
+            if code is not None:
+                return True
+            else:
+                return None
+        else:
+            return False
+
+    def get_page_number(self):
+        code = self.find_bottom_right_barcode()
+
+        if code is None or (not code.isdigit() and len(code) < 4):
+            return None
+
+        return int(code[-4:])
+
+    def get_survey_id(self):
+        code = self.find_bottom_right_barcode()
+
+        if code is None or not code.isdigit() or len(code) <= 4:
+            return None
+
+        return int(code[:-4])
+
+    def get_questionnaire_id(self):
+        return self.find_bottom_left_barcode()
+
+    def get_global_id(self):
+        return self.find_bottom_center_barcode()
+
+    def find_bottom_right_barcode(self):
+      return read_barcode(self.obj.surface.surface, self.obj.matrix.mm_to_px(),
+                 self.paper_width() * 0.75,
+                 self.paper_height() * 0.75,
+                 self.paper_width() * 0.25,
+                 self.paper_height() * 0.25,
+                 "QRCODE")
+
+    def find_top_left_barcode(self):
+      return read_barcode(self.obj.surface.surface, self.obj.matrix.mm_to_px(),
+                     0, 0,
+                     self.paper_width() * 0.25,
+                     self.paper_height() * 0.25,
+                     "QRCODE")
+
+    def find_bottom_left_barcode(self):
+      return read_barcode(self.obj.surface.surface, self.obj.matrix.mm_to_px(),
+                     0,
+                     self.paper_height() * 0.75,
+                     self.paper_width() * 0.25,
+                     self.paper_height() * 0.25,
+                     "QRCODE")
+
+    def find_bottom_center_barcode(self):
+      return read_barcode(self.obj.surface.surface, self.obj.matrix.mm_to_px(),
+                     self.paper_width() * 0.375,
+                     self.paper_height() * 0.75,
+                     self.paper_width() * 0.25,
+                     self.paper_height() * 0.25,
+                     "QRCODE")
+
+    def paper_width(self):
+      return self.obj.sheet.survey.defs.paper_width
+
+    def paper_height(self):
+      return self.obj.sheet.survey.defs.paper_height
diff --git a/sdaps/reorder/__init__.py b/sdaps/reorder/__init__.py
new file mode 100644
index 0000000..ab127d7
--- /dev/null
+++ b/sdaps/reorder/__init__.py
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2012, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+u"""
+This module reorders already recognized data according to the questionnaire IDs.
+"""
+
+from collections import defaultdict
+from sdaps import model
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+def reorder(survey):
+    """We can assume quite some things in this function, because recognize
+    properly handles it. ie.
+
+     * Every image will be tagged correctly (as long as the data is known)
+     * in duplex mode both the front/back image will be tagged, so we don't
+       need to care about that here!
+    """
+
+    image_count = survey.questionnaire.page_count
+    # We have two images per page in simplex mode!
+    if not survey.defs.duplex:
+        image_count = image_count * 2
+
+    # First, go over all sheets and figure out which ones need reordering.
+    # For every sheet that isn't quite right, we extract the images, and delete
+    # the sheet.
+    # The images are put into a dictionnary using the questionnaire ID.
+    # Each entry in the dictionary is a list.
+    images = defaultdict(lambda : [])
+    for sheet in survey.sheets[:]: # Use a flat copy to iterate over
+        broken = False
+        pages = set()
+        for image in sheet.images:
+            if sheet.questionnaire_id != image.questionnaire_id:
+                broken = True
+            if sheet.global_id != image.global_id:
+                broken = True
+
+            # Check that no page exists twice
+            if image.page_number is not None and image.page_number in pages:
+                broken = True
+            pages.add(image.page_number)
+
+        # Also consider incomplete sets broken, so that hopefully they will
+        # be filled up with the correct page.
+        if len(sheet.images) != image_count:
+            broken = True
+
+        if broken:
+            # Drop from the list of sheets
+            survey.sheets.remove(sheet)
+
+            for image in sheet.images:
+                images[(image.questionnaire_id, image.global_id)].append(image)
+
+    # We have dictionnary of lists of images that needs to be put into sheets
+    # again.
+    # This could probably be more robust. We don't care about the questionnaire
+    # ID itself here, just put each list into sheets, splitting it into many
+    # if there are too many images.
+    for img_list in images.itervalues():
+
+        while len(img_list) > 0:
+            sheet = model.sheet.Sheet()
+            survey.add_sheet(sheet)
+
+            while len(img_list) > 0 and len(sheet.images) < image_count:
+                sheet.add_image(img_list.pop(0))
+
+
+    survey.save()
+
diff --git a/sdaps/report/__init__.py b/sdaps/report/__init__.py
new file mode 100644
index 0000000..ea33f3c
--- /dev/null
+++ b/sdaps/report/__init__.py
@@ -0,0 +1,123 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+u"""
+This modules contains the functionality to create PDF based reports.
+"""
+
+import os
+
+from reportlab import platypus
+
+from sdaps import model
+
+from sdaps import clifilter
+from sdaps import template
+from sdaps import matrix
+from sdaps.utils import paper
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+from . import buddies
+
+
+def report(survey, filter, filename=None, papersize=None, small=0, suppress=None):
+    assert isinstance(survey, model.survey.Survey)
+
+    # compile clifilter
+    filter = clifilter.clifilter(survey, filter)
+
+    # First: calculate buddies
+
+    # init buddies
+    survey.questionnaire.calculate.init()
+
+    # iterate over sheets
+    survey.iterate(
+        survey.questionnaire.calculate.read,
+        lambda: survey.sheet.valid and not survey.sheet.empty and filter()
+    )
+
+    # do calculations
+    survey.questionnaire.calculate.calculate()
+
+    # Second: report buddies
+
+    # init buddies
+    survey.questionnaire.report.init(small, suppress)
+
+    # iterate over sheets
+    survey.iterate(
+        survey.questionnaire.report.report,
+        lambda: survey.sheet.valid and filter()
+    )
+
+    # create story
+    story = template.story_title(
+        survey,
+        {
+            _(u'Turned in Questionnaires'): survey.questionnaire.calculate.count,
+        }
+    )
+    story.extend(survey.questionnaire.report.story())
+
+    # create report(out of story)
+    if filename is None:
+        filename = survey.new_path('report_%i.pdf')
+    subject = []
+    for key, value in survey.info.iteritems():
+        subject.append(u'%(key)s: %(value)s' % {'key': key, 'value': value})
+    subject = u'\n'.join(subject)
+
+    papersize = paper.get_reportlab_papersize(papersize)
+
+    doc = template.DocTemplate(
+        filename,
+        _(u'sdaps report'),
+        {
+            'title': survey.title,
+            'subject': subject,
+        },
+        papersize=papersize
+    )
+    doc.build(story)
+
+
+def stats(survey, filter, filename=None, small=0, suppress=None):
+    if filename is None:
+        filename = survey.new_path('report_%i.pdf')
+
+    # do a report
+    report(survey, filter, filename=filename, small=small)
+
+    # save reference (to highlight large differences)
+    survey.questionnaire.calculate.reference()
+
+    # do a report for every filter
+    for i, subfilter in enumerate(survey.questionnaire.report.filters()):
+        if filter is None:
+            filt = subfilter
+        else:
+            filt = "(%s) and (%s)" % (filter, subfilter)
+        report(
+            survey, filt,
+            filename='%s_%i %s.pdf' % (filename.split('.')[0], i, subfilter),
+            small=small,
+            suppress=suppress)
+
diff --git a/sdaps/report/answers.py b/sdaps/report/answers.py
new file mode 100644
index 0000000..919d262
--- /dev/null
+++ b/sdaps/report/answers.py
@@ -0,0 +1,333 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import StringIO
+from PIL import Image
+
+from reportlab import pdfgen
+from reportlab import platypus
+from reportlab.lib import styles
+from reportlab.lib import units
+from reportlab.lib import pagesizes
+from reportlab.lib import enums
+from reportlab.lib import colors
+from xml.sax.saxutils import escape
+
+from sdaps import template
+from sdaps import model
+from sdaps import image
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+import flowables
+
+
+mm = units.mm
+
+stylesheet = dict(template.stylesheet)
+
+stylesheet['Right'] = styles.ParagraphStyle(
+    'Right',
+    parent=stylesheet['Normal'],
+    alignment=enums.TA_RIGHT,
+)
+
+stylesheet['Right_Highlight'] = styles.ParagraphStyle(
+    'Right_Highlight',
+    parent=stylesheet['Right'],
+    textColor=colors.Color(255, 0, 0)
+)
+
+stylesheet['Normal_Highlight'] = styles.ParagraphStyle(
+    'Normal_Highlight',
+    parent=stylesheet['Normal'],
+    textColor=colors.Color(255, 0, 0)
+)
+
+
+class Choice(platypus.Flowable):
+    u'''One answer of a choice
+    '''
+
+    box_width = 200
+    box_height = 9
+    box_depth = 5
+    box_margin = 1
+    value_width = 45
+    gap = 6
+
+    def __init__(self, answer, value, significant=0):
+        platypus.Flowable.__init__(self)
+        if significant:
+            stylesheet_name = 'Right_Highlight'
+        else:
+            stylesheet_name = 'Right'
+        self.answer = platypus.Paragraph(escape(answer), stylesheet[stylesheet_name])
+        self.value = platypus.Paragraph(
+            u'%.2f %%' % (value * 100), stylesheet[stylesheet_name]
+        )
+        self.black_box = flowables.Box(
+            value * self.box_width, self.box_height, self.box_depth,
+            self.box_margin
+        )
+        self.black_box.transparent = 0
+        self.black_box.fill = 1
+        self.black_box.fill_color = (0.6, 0.6, 0.6)
+        self.white_box = flowables.Box(
+            (1 - value) * self.box_width, self.box_height, self.box_depth,
+            self.box_margin
+        )
+
+    def wrap(self, available_width, available_height):
+        self.width = available_width
+        self.white_box.wrap(available_width, available_height)
+        available_width -= self.white_box.width - self.white_box.cx
+        self.black_box.wrap(available_width, available_height)
+        available_width -= self.black_box.width
+        available_width -= self.gap
+        self.value.wrap(self.value_width, available_height)
+        available_width -= self.value.width
+        self.answer.wrap(available_width, available_height)
+        self.height = max(
+            self.answer.height, self.value.height,
+            self.black_box.height + self.box_margin,
+            self.white_box.height + self.box_margin
+        )
+        return self.width, self.height
+
+    def draw(self):
+        if 0:
+            assert isinstance(self.canv, pdfgen.canvas.Canvas)
+        self.answer.drawOn(
+            self.canv,
+            0,
+            self.height / 2.0 - self.answer.height / 2.0
+        )
+        self.value.drawOn(
+            self.canv,
+            self.answer.width,
+            self.height / 2.0 - self.value.height / 2.0
+        )
+        self.black_box.drawOn(
+            self.canv,
+            self.answer.width + self.value.width + self.gap,
+            self.height / 2.0 - self.black_box.height / 2.0
+        )
+        self.white_box.drawOn(
+            self.canv,
+            self.answer.width + self.value.width + self.gap + self.black_box.width - self.black_box.cx,
+            self.height / 2.0 - self.white_box.height / 2.0
+        )
+
+
+class Mark(platypus.Flowable):
+    '''
+    -----                self.top_margin
+    values(percent)    self.values_height
+    -----                self.values_gap
+    values(bars)        self.bars_height
+    -----
+    skala with mean        self.skala_height
+    -----
+    marks(1 - range)        self.marks_height
+    -----
+    '''
+
+    margin = 6
+    top_margin = 0
+    left_margin = 12
+
+    def __init__(self, values, answers, mean, standard_deviation, count, significant=0):
+        platypus.Flowable.__init__(self)
+
+        self.values = values
+        self.mean = mean
+        self.standard_deviation = standard_deviation
+        self.count = count
+
+        self.box_width = 40
+        self.box_height = 60
+        self.box_depth = 6
+
+        self.mean_width = 2
+        self.mean_height = 6
+
+        self.values_height = 10
+        self.values_gap = self.box_depth
+        self.bars_height = max(self.values) * self.box_height
+        self.skala_height = self.mean_height
+        self.marks_height = 10
+
+        if significant:
+            stylesheet_name = 'Normal_Highlight'
+        else:
+            stylesheet_name = 'Normal'
+
+        self.answers_paragraph = \
+            platypus.Paragraph(escape(u' - '.join(answers)), stylesheet[stylesheet_name])
+        self.count_paragraph = \
+            platypus.Paragraph(_(u'Answers: %i') % self.count, stylesheet['Normal'])
+        self.mean_paragraph = \
+            platypus.Paragraph(_(u'Mean: %.2f') % self.mean, stylesheet['Normal'])
+        self.stdd_paragraph = \
+            platypus.Paragraph(_(u'Standard Deviation: %.2f') % self.standard_deviation, stylesheet['Normal'])
+
+    def wrap(self, available_width, available_height):
+        self.answers_paragraph.wrap(available_width, available_height)
+        self.count_paragraph.wrap(available_width, available_height)
+        self.mean_paragraph.wrap(available_width, available_height)
+        self.stdd_paragraph.wrap(available_width, available_height)
+        self.width = available_width # self.box_width * 5
+        self.offset = self.width - self.box_width * len(self.values) - self.margin
+        self.height = self.top_margin + self.values_height + self.values_gap + \
+                      self.bars_height + self.skala_height + self.marks_height
+        return self.width, self.height
+
+    def draw(self):
+        if 0:
+            assert isinstance(self.canv, pdfgen.canvas.Canvas)
+        self.canv.setFont("Times-Roman", 10)
+        # mean
+        mean = flowables.Box(self.mean_width, self.mean_height, self.box_depth)
+        mean.transparent = 0
+        mean.fill = 1
+        mean.fill_color = (0.6, 0.6, 0.6)
+        mean.drawOn(self.canv,
+                    self.offset + (self.mean - 0.5) * self.box_width - self.mean_width / 2.0,
+                    self.marks_height)
+        # values
+        for i, value in enumerate(self.values):
+            self.canv.drawCentredString(
+                self.offset + (i + 0.5) * self.box_width + self.box_depth * 0.5,
+                self.marks_height + self.skala_height + self.bars_height + self.values_gap,
+                '%.2f %%' % (value * 100)
+            )
+        # bars
+        for i, value in enumerate(self.values):
+            box = flowables.Box(self.box_width, value * self.box_height, self.box_depth)
+            box.transparent = 0
+            box.fill = 1
+            box.fill_color = (0.6, 0.6, 0.6)
+            box.drawOn(self.canv,
+                       self.offset + i * self.box_width, self.marks_height + self.skala_height)
+        # skala
+        for i in range( (len(self.values) - 1) * 10 + 1):
+            if i % 10 == 0:
+                self.canv.setLineWidth(0.2)
+                self.canv.line(
+                    self.offset + (i / 10.0 + 0.5) * self.box_width,
+                    1 + self.marks_height,
+                    self.offset + (i / 10.0 + 0.5) * self.box_width,
+                    5 + self.marks_height
+                )
+            elif i % 5 == 0:
+                self.canv.line(
+                    self.offset + (i / 10.0 + 0.5) * self.box_width,
+                    1.5 + self.marks_height,
+                    self.offset + (i / 10.0 + 0.5) * self.box_width,
+                    4.5 + self.marks_height
+                )
+            else:
+                self.canv.line(
+                    self.offset + (i / 10.0 + 0.5) * self.box_width,
+                    2 + self.marks_height,
+                    self.offset + (i / 10.0 + 0.5) * self.box_width,
+                    4 + self.marks_height
+                )
+            if i % 10 == 0:
+                self.canv.setLineWidth(0.1)
+        # marks
+        for i in range(1, len(self.values)+1):
+            self.canv.drawCentredString(
+                self.offset + (i - 0.5) * self.box_width, 0,
+                '%i' % i
+            )
+
+        # statistics
+        y_pos = self.marks_height + self.skala_height + self.bars_height + \
+                self.values_gap + self.values_height
+        self.answers_paragraph.drawOn(self.canv, self.left_margin, y_pos - 15)
+        self.count_paragraph.drawOn(self.canv, self.left_margin, y_pos - 27)
+        self.mean_paragraph.drawOn(self.canv, self.left_margin, y_pos - 39)
+        self.stdd_paragraph.drawOn(self.canv, self.left_margin, y_pos - 51)
+
+
+class Freeform(platypus.Flowable):
+
+    cache = dict()
+
+    def __init__(self, box):
+        platypus.Flowable.__init__(self)
+
+        assert isinstance(box, model.questionnaire.Textbox)
+        assert box.data.state
+
+        image = box.sheet.get_page_image(box.question.page_number)
+
+        self.filename = box.question.questionnaire.survey.path(image.filename)
+        self.tiff_page = image.tiff_page
+        self.rotated = image.rotated
+
+        mm_to_px = image.matrix.mm_to_px()
+        x0, y0 = mm_to_px.transform_point(box.data.x, box.data.y)
+        x1, y1 = mm_to_px.transform_point(box.data.x + box.data.width, box.data.y + box.data.height)
+
+        self.bbox = (int(x0), int(y0), int(x1), int(y1))
+
+        self.width = box.data.width * mm
+        self.height = box.data.height * mm
+
+    def wrap(self, available_width, available_height):
+        self.available_width = available_width
+        return self.available_width, self.height
+
+    def draw(self):
+        if 0:
+            assert isinstance(self.canv, pdfgen.canvas.Canvas)
+        if(self.filename, self.tiff_page, self.bbox) in self.cache:
+            img = self.cache[(self.filename, self.tiff_page, self.bbox)]
+        else:
+            img = StringIO.StringIO(image.get_pbm(
+                image.get_a1_from_tiff(
+                    self.filename,
+                    self.tiff_page,
+                    self.rotated if self.rotated else False
+                )
+            ))
+            img = Image.open(img).crop(self.bbox)
+            self.cache[(self.filename, self.bbox)] = img
+        self.canv.drawInlineImage(img, 0, 0, self.width, self.height)
+        self.canv.setStrokeColorRGB(0.6, 0.6, 0.6)
+        self.canv.line(0, 0, self.available_width, 0)
+        self.canv.line(0, self.height, self.available_width, self.height)
+
+
+class RawText(platypus.Paragraph):
+
+    def __init__(self, text, *args, **kwargs):
+
+        # Replace things like 
+
+        text = escape(text)
+        text = text.replace('\n', u'<br/>')
+
+        text = text
+
+        platypus.Paragraph.__init__(self, text, *args, bulletText=u'•', **kwargs)
+
diff --git a/sdaps/report/buddies.py b/sdaps/report/buddies.py
new file mode 100644
index 0000000..118594c
--- /dev/null
+++ b/sdaps/report/buddies.py
@@ -0,0 +1,288 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import math
+
+from reportlab import platypus
+from reportlab.lib import styles
+from reportlab.lib import colors
+from reportlab.lib import units
+
+from sdaps import clifilter
+from sdaps import template
+from sdaps import model
+
+from xml.sax.saxutils import escape
+
+import flowables
+import answers
+
+from sdaps import calculate
+
+
+mm = units.mm
+
+
+stylesheet = dict(template.stylesheet)
+
+stylesheet['Head'] = styles.ParagraphStyle(
+    'Head',
+    stylesheet['Normal'],
+    fontSize=12,
+    leading=17,
+    backColor=colors.lightgrey,
+    spaceBefore=5 * mm,
+)
+
+stylesheet['Question'] = styles.ParagraphStyle(
+    'Question',
+    stylesheet['Normal'],
+    spaceBefore=3 * mm,
+    fontName='Times-Bold',
+)
+
+stylesheet['Text'] = styles.ParagraphStyle(
+    'Text',
+    stylesheet['Normal'],
+    spaceBefore=1 * mm,
+    spaceAfter=1 * mm,
+    rightIndent=5 * mm,
+    bulletIndent=2 * mm,
+    leftIndent=5 * mm,
+)
+
+
+class Questionnaire(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'report'
+    obj_class = model.questionnaire.Questionnaire
+
+    def init(self, small=0, suppress=None):
+        self.small = small
+        # iterate over qobjects
+        for qobject in self.obj.qobjects:
+            qobject.report.init(small, suppress)
+
+    def report(self):
+        # iterate over qobjects
+        for qobject in self.obj.qobjects:
+            qobject.report.report()
+
+    def story(self):
+        story = list()
+        # iterate over qobjects
+        keeptogether_list = []
+        for qobject in self.obj.qobjects:
+            new, keeptogether = qobject.report.story()
+            new = list(new)
+
+            if len(new) == 0:
+                continue
+
+            if keeptogether:
+                keeptogether_list.extend(new)
+            else:
+                if len(keeptogether_list):
+                    add = new.pop(0)
+                    if isinstance(add, platypus.KeepTogether):
+                        keeptogether_list.extend(add._content)
+                    else:
+                        keeptogether_list.append(add)
+
+                    story.append(platypus.KeepTogether(keeptogether_list))
+                    keeptogether_list = []
+
+                story.extend(new)
+
+        story.extend(keeptogether_list)
+        return story
+
+    def filters(self):
+        filters = list()
+        # iterate over qobjects
+        for qobject in self.obj.qobjects:
+            filters.extend(list(qobject.report.filters()))
+        return filters
+
+
+class QObject(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'report'
+    obj_class = model.questionnaire.QObject
+
+    def init(self, small, suppress):
+        self.small = small
+
+    def report(self):
+        pass
+
+    def story(self):
+        return [], False
+
+    def filters(self):
+        return []
+
+
+class Head(QObject):
+
+    __metaclass__ = model.buddy.Register
+    name = 'report'
+    obj_class = model.questionnaire.Head
+
+    def story(self):
+        return [
+            platypus.Paragraph(
+                u'%s %s' % (self.obj.id_str(), escape(self.obj.title)),
+                stylesheet['Head'])], True
+
+
+class Question(QObject):
+
+    __metaclass__ = model.buddy.Register
+    name = 'report'
+    obj_class = model.questionnaire.Question
+
+    def story(self):
+        return [
+            platypus.Paragraph(
+                u'%s %s' % (
+                    self.obj.id_str(), escape(self.obj.question)),
+                stylesheet['Question'])], True
+
+
+class Choice(Question):
+
+    __metaclass__ = model.buddy.Register
+    name = 'report'
+    obj_class = model.questionnaire.Choice
+
+    def init(self, small, suppress):
+        self.small = small
+        self.suppress = suppress
+        self.text = list()
+
+    def report(self):
+        if not self.small:
+            for box in self.obj.boxes:
+                if (isinstance(box, model.questionnaire.Textbox) and
+                        box.data.state):
+
+                    if box.data.text and self.suppress != 'substitutions':
+                        self.text.append(answers.RawText(box.data.text,
+                                                         stylesheet['Text']))
+                    elif self.suppress != 'images':
+                        self.text.append(answers.Freeform(box))
+
+    def story(self):
+        story, tmp = Question.story(self)
+        if self.obj.calculate.count:
+            for box in self.obj.boxes:
+                story.append(
+                    answers.Choice(
+                        box.text,
+                        self.obj.calculate.values[box.value],
+                        self.obj.calculate.significant[box.value]
+                    )
+                )
+            story = [platypus.KeepTogether(story)]
+            if len(self.text) > 0:
+                story.append(platypus.Spacer(0, 3 * mm))
+                story.extend(self.text)
+        return story, False
+
+    def filters(self):
+        for box in self.obj.boxes:
+            yield u'%i in %s' % (box.value, self.obj.id_filter())
+
+
+class Mark(Question):
+
+    __metaclass__ = model.buddy.Register
+    name = 'report'
+    obj_class = model.questionnaire.Mark
+
+    def story(self):
+        story, tmp = Question.story(self)
+        if self.obj.calculate.count:
+            story.append(answers.Mark(
+                self.obj.calculate.values.values(),
+                self.obj.answers,
+                self.obj.calculate.mean,
+                self.obj.calculate.standard_deviation,
+                self.obj.calculate.count,
+                self.obj.calculate.significant))
+            story = [platypus.KeepTogether(story)]
+        return story, False
+
+    def filters(self):
+        for x in range(len(self.obj.boxes)+1):
+            yield u'%i == %s' % (x, self.obj.id_filter())
+
+
+class Text(Question):
+
+    __metaclass__ = model.buddy.Register
+    name = 'report'
+    obj_class = model.questionnaire.Text
+
+    def init(self, small, suppress):
+        self.small = small
+        self.suppress = suppress
+        self.text = list()
+
+    def report(self):
+        if not self.small:
+            for box in self.obj.boxes:
+                if box.data.state:
+                    if box.data.text and self.suppress != 'substitutions':
+                        self.text.append(answers.RawText(box.data.text,
+                                                         stylesheet['Text']))
+                    elif self.suppress != 'images':
+                        self.text.append(answers.Freeform(box))
+
+    def story(self):
+        story, tmp = Question.story(self)
+        if len(self.text) > 0:
+            story.append(self.text[0])
+            story = [platypus.KeepTogether(story)]
+        if len(self.text) > 1:
+            story.extend(self.text[1:])
+        return story, False
+
+
+class Additional_FilterHistogram(Question):
+
+    __metaclass__ = model.buddy.Register
+    name = 'report'
+    obj_class = model.questionnaire.Additional_FilterHistogram
+
+    def story(self):
+        story, tmp = Question.story(self)
+        if self.obj.calculate.count:
+            for i in range(len(self.obj.calculate.values)):
+                story.append(
+                    answers.Choice(
+                        self.obj.answers[i],
+                        self.obj.calculate.values[i],
+                        self.obj.calculate.significant[i]
+                    )
+                )
+            story = [platypus.KeepTogether(story)]
+        return story, False
diff --git a/sdaps/report/flowables.py b/sdaps/report/flowables.py
new file mode 100644
index 0000000..005056e
--- /dev/null
+++ b/sdaps/report/flowables.py
@@ -0,0 +1,245 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import math
+
+from reportlab import pdfgen
+from reportlab import platypus
+from reportlab.lib import styles
+from reportlab.lib import units
+from reportlab.lib import pagesizes
+from reportlab.lib import enums
+
+from sdaps import template
+
+
+mm = units.mm
+
+stylesheet = template.stylesheet
+
+stylesheet['Right'] = styles.ParagraphStyle(
+    'Right',
+    parent=stylesheet['Normal'],
+    alignment=enums.TA_RIGHT,
+)
+
+
+class Box(platypus.Flowable):
+    u'''3d box
+    '''
+
+    def __init__(self, a, b, c, margin=0):
+        platypus.Flowable.__init__(self)
+        self.a = float(a)
+        self.b = float(b)
+        self.c = float(c)
+        self.margin = float(margin)
+        self.alpha = 1.0 / 3.0 * math.pi
+        self.cx = math.sin(self.alpha) * self.c
+        self.cy = math.cos(self.alpha) * self.c
+        self.fill = 0
+        self.transparent = 1
+        self.fill_color = (255, 255, 255)
+
+    def wrap(self, available_width, available_height):
+        self.width = self.a + self.cx
+        self.height = self.b + self.cy + 2 * self.margin
+        return self.width, self.height
+
+    def draw(self):
+        if 0:
+            assert isinstance(self.canv, pdfgen.canvas.Canvas)
+        self.canv.setLineJoin(1)
+        self.canv.setLineWidth(0.2)
+        self.canv.setFillColorRGB(*self.fill_color)
+        # back
+        if self.transparent:
+            self.draw_front(self.cx, self.margin + self.cy)
+        # left side
+        if self.transparent:
+            self.draw_side(0, self.margin)
+        # bottom
+        if self.transparent:
+            self.draw_top(0, self.margin)
+        # right side
+        self.draw_side(self.a, self.margin)
+        # top
+        self.draw_top(0, self.margin + self.b)
+        # front
+        self.draw_front(0, self.margin)
+
+    def draw_side(self, x, y):
+        path = self.canv.beginPath()
+        path.moveTo(x, y)
+        path.lineTo(x + self.cx, y + self.cy)
+        path.lineTo(x + self.cx, y + self.cy + self.b)
+        path.lineTo(x, y + self.b)
+        path.lineTo(x, y)
+        self.canv.drawPath(path, fill=self.fill)
+
+    def draw_top(self, x, y):
+        path = self.canv.beginPath()
+        path.moveTo(x, y)
+        path.lineTo(x + self.a, y)
+        path.lineTo(x + self.a + self.cx, y + self.cy)
+        path.lineTo(x + self.cx, y + self.cy)
+        path.lineTo(x, y)
+        self.canv.drawPath(path, fill=self.fill)
+
+    def draw_front(self, x, y):
+        path = self.canv.beginPath()
+        path.moveTo(x, y)
+        path.lineTo(x + self.a, y)
+        path.lineTo(x + self.a, y + self.b)
+        path.lineTo(x, y + self.b)
+        path.lineTo(x, y)
+        self.canv.drawPath(path, fill=self.fill)
+
+
+#class ChoiceBar(platypus.Flowable):
+
+    #def __init__(self, value):
+        #platypus.Flowable.__init__(self)
+        #self.a = 200
+        #self.b = 12
+        #self.depth_x = 6
+        #self.depth_y = 3
+        #self.value = value
+
+    #def wrap(self, available_width, available_height):
+        #self.width = self.a + self.depth_x
+        #self.height = self.height + self.depth_y
+        #return self.width, self.height
+
+    #def draw(self):
+        #if 0: assert isinstance(self.canv, pdfgen.canvas.Canvas)
+        #self.canv.setLineJoin(1)
+        #self.canv.setLineWidth(0.1)
+        #self.canv.setFillGray(0.5)
+        ## filling
+        #self.draw_side(self.value, 1)
+        #self.draw_top(self.value, 1)
+        #self.draw_front(self.value, 1)
+        ## line
+        #self.canv.line(self.a * self.value,                0,            self.a,                0)
+        #self.canv.line(self.a * self.value + self.depth_x, self.depth_y, self.a + self.depth_x, self.depth_y)
+        #self.canv.line(self.a * self.value,                self.b,                self.a,                self.b)
+        #self.canv.line(self.a * self.value + self.depth_x, self.b + self.depth_y, self.a + self.depth_x, self.b + self.depth_y)
+        ## bar
+        #self.draw_side(1, 0)
+
+    #def draw_front(self, value, fill):
+        #path = self.canv.beginPath()
+        #path.moveTo(0, 0)
+        #path.lineTo(self.a * value, 0)
+        #path.lineTo(self.a * value, self.b)
+        #path.lineTo(0, self.b)
+        #path.lineTo(0, 0)
+        #self.canv.drawPath(path, fill=fill)
+
+    #def draw_top(self, value, fill):
+        #path = self.canv.beginPath()
+        #path.moveTo(0, self.b)
+        #path.lineTo(self.a * value, self.b)
+        #path.lineTo(self.a * value + self.depth_x, self.b + self.depth_y)
+        #path.lineTo(self.depth_x, self.b + self.depth_y)
+        #path.lineTo(0, self.b)
+        #self.canv.drawPath(path, fill=fill)
+
+    #def draw_side(self, value, fill):
+        #path = self.canv.beginPath()
+        #path.moveTo(self.a * value, 0)
+        #path.lineTo(self.a * value, self.b)
+        #path.lineTo(self.a * value + self.depth_x, self.b + self.depth_y)
+        #path.lineTo(self.a * value + self.depth_x, self.depth_y)
+        #path.lineTo(self.a * value, 0)
+        #self.canv.drawPath(path, fill=fill)
+
+
+#class ChoiceAnswer(platypus.Flowable):
+
+    #def __init__(self, answer, value):
+        #platypus.Flowable.__init__(self)
+        #self.answer = platypus.Paragraph(answer, template.stylesheet['Right'])
+        #self.value = platypus.Paragraph(u'%.2f %%' %(value * 100), template.stylesheet['Right'])
+        #self.bar = ChoiceBar(value)
+        #self.gap = 3
+        #self.value_width = 42
+
+    #def wrap(self, available_width, available_height):
+        #self.width = available_width
+        #self.bar.wrap(self.width, available_height)
+        #available_width -= self.bar.width
+        #available_width -= self.gap
+        #self.value.wrap(self.value_width, available_height)
+        #available_width -= self.value.width
+        #self.answer.wrap(available_width, available_height)
+        #self.height = max(self.answer.height, self.bar.height, self.value.height)
+        #return self.width, self.height
+
+
+    #def draw(self):
+        #if 0: assert isinstance(self.canv, pdfgen.canvas.Canvas)
+        #self.answer.drawOn(self.canv, 0, 0)
+        #self.value.drawOn(self.canv, self.answer.width, 0)
+        #self.bar.drawOn(self.canv, self.answer.width + self.value.width + self.gap, 0)
+
+#class MarkAnswer(platypus.Flowable):
+
+    #def __init__(self):
+        #platypus.Flowable.__init__(self)
+        #self.box_width = 40
+        #self.box_height = 60
+        #self.box_depth = 6
+        #self.mean_width = 2
+        #self.mean_height = 6
+        #self.margin = 6
+        ##self.values = [0.2, 0.20, 0.2, 0.20, 0.20]
+        #self.values = [0.25, 0.10, 0.35, 0.20, 0.10]
+        #self.mean = sum([(mark + 1) * value for mark, value in enumerate(self.values)])
+
+
+    #def wrap(self, available_width, available_height):
+        #self.width = available_width #self.box_width * 5
+        #self.offset = self.width - self.box_width * 5 - self.margin
+        #self.height = max(self.values) * self.box_height + self.mean_height
+        #return self.width, self.height
+
+    #def draw(self):
+        #if 0: assert isinstance(self.canv, pdfgen.canvas.Canvas)
+        #self.canv.setLineJoin(1)
+        #self.canv.setLineWidth(0.1)
+        #self.canv.setFillGray(0.5)
+        ## mean
+        #Box(self.mean_width, self.mean_height, self.box_depth).drawOn(self.canv, self.offset +(self.mean - 0.5) * self.box_width - self.mean_width / 2.0, 0)
+        ## boxes
+        #for i, value in enumerate(self.values):
+            #Box(self.box_width, value * self.box_height, self.box_depth).drawOn(self.canv, self.offset + i * self.box_width, self.mean_height)
+        ## skala
+        #for i in range(41):
+            #if i % 10 == 0:
+                #self.canv.setLineWidth(0.2)
+                #self.canv.line(self.offset +(i / 10.0 + 0.5) * self.box_width, 1, self.offset +(i / 10.0 + 0.5) * self.box_width, 5)
+            #elif i % 5 == 0:
+                #self.canv.line(self.offset +(i / 10.0 + 0.5) * self.box_width, 1.5, self.offset +(i / 10.0 + 0.5) * self.box_width, 4.5)
+            #else:
+                #self.canv.line(self.offset +(i / 10.0 + 0.5) * self.box_width, 2, self.offset +(i / 10.0 + 0.5) * self.box_width, 4)
+            #if i % 10 == 0:
+                #self.canv.setLineWidth(0.1)
+
+
diff --git a/sdaps/reporttex/__init__.py b/sdaps/reporttex/__init__.py
new file mode 100644
index 0000000..3f1dbfe
--- /dev/null
+++ b/sdaps/reporttex/__init__.py
@@ -0,0 +1,172 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, 2011, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+u"""
+This modules contains the functionality to create reports using LaTeX.
+"""
+
+import os
+import tempfile
+import shutil
+import glob
+
+from sdaps import model
+
+from sdaps import clifilter
+from sdaps import template
+from sdaps import matrix
+from sdaps import paths
+from sdaps import defs
+from sdaps.utils import latex
+
+from sdaps.utils import paper
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+from . import buddies
+import codecs
+
+
+def report(survey, filter, filename=None, papersize=None, small=0, suppress=None, tex_only=False):
+    assert isinstance(survey, model.survey.Survey)
+
+    # compile clifilter
+    filter = clifilter.clifilter(survey, filter)
+
+    # First: calculate buddies
+
+    # init buddies
+    survey.questionnaire.calculate.init()
+
+    # iterate over sheets
+    survey.iterate(
+        survey.questionnaire.calculate.read,
+        lambda: survey.sheet.valid and filter()
+    )
+
+    # do calculations
+    survey.questionnaire.calculate.calculate()
+
+    # Temporary directory for TeX files.
+    if tex_only and filename:
+        tmpdir = filename
+
+        # Create directory
+        os.makedirs(tmpdir)
+    else:
+        tmpdir = tempfile.mkdtemp()
+
+    # Second: report buddies
+
+    # init buddies
+    survey.questionnaire.report.init(tmpdir, small, suppress)
+
+    # Filename of output
+    if filename is None and tex_only == False:
+        filename = survey.new_path('report_%i.pdf')
+
+    try:
+        # iterate over sheets
+        survey.iterate(
+            survey.questionnaire.report.report,
+            lambda: survey.sheet.valid and filter(),
+            tmpdir
+        )
+
+        # Copy class and dictionary files
+        if paths.local_run:
+            cls_file = os.path.join(paths.source_dir, 'tex', 'sdapsreport.cls')
+            dict_files = os.path.join(paths.build_dir, 'tex', '*.dict')
+            dict_files = glob.glob(dict_files)
+        else:
+            cls_file = os.path.join(paths.prefix, 'share', 'sdaps', 'tex', 'sdapsreport.cls')
+            dict_files = os.path.join(paths.prefix, 'share', 'sdaps', 'tex', '*.dict')
+            dict_files = glob.glob(dict_files)
+
+        shutil.copyfile(cls_file, os.path.join(tmpdir, 'sdapsreport.cls'))
+        for dict_file in dict_files:
+            shutil.copyfile(dict_file, os.path.join(tmpdir, os.path.basename(dict_file)))
+
+        texfile = codecs.open(os.path.join(tmpdir, 'report.tex'), 'w', 'utf-8')
+
+        author = _('author|Unknown')
+
+        extra_info = []
+        for key, value in survey.info.iteritems():
+            if key == 'Author':
+                author = value
+                continue
+
+            extra_info.append(u'\\addextrainfo{%(key)s}{%(value)s}' % {'key': key, 'value': value})
+
+        extra_info = u'\n'.join(extra_info)
+        texfile.write(r"""\documentclass[%(language)s,%(papersize)s]{sdapsreport}
+
+    \usepackage{ifxetex}
+    \ifxetex
+    \else
+      \usepackage[utf8]{inputenc}
+    \fi
+    \usepackage[%(language)s]{babel}
+
+    \title{%(title)s}
+    \subject{%(title)s}
+    \author{%(author)s}
+
+    \addextrainfo{%(turned_in)s}{%(count)i}
+    %(extra_info)s
+
+    \begin{document}
+
+    \maketitle
+
+    """ % {'language': _('tex language|english'),
+           'title': _(u'sdaps report'),
+           'turned_in': _('Turned in Questionnaires'),
+           'title': survey.title,
+           'author': author,
+           'extra_info': extra_info,
+           'count': survey.questionnaire.calculate.count,
+           'papersize' : paper.get_tex_papersize(papersize)})
+
+        survey.questionnaire.report.write(texfile, tmpdir)
+
+        texfile.write(r"""
+    \end{document}
+    """)
+
+        if tex_only:
+            print _("The TeX project with the report data is located at '%s'.") % tmpdir
+            return
+
+        print _("Running %s now twice to generate the report.") % defs.latex_engine
+        latex.compile('report.tex', cwd=tmpdir)
+
+        if not os.path.exists(os.path.join(tmpdir, 'report.pdf')):
+            print _("Error running \"%s\" to compile the LaTeX file.") % defs.latex_engine
+            raise AssertionError('PDF file not generated')
+
+        shutil.move(os.path.join(tmpdir, 'report.pdf'), filename)
+
+    except:
+        print _("An occured during creation of the report. Temporary files left in '%s'." % tmpdir)
+
+        raise
+
+    shutil.rmtree(tmpdir)
+
diff --git a/sdaps/reporttex/buddies.py b/sdaps/reporttex/buddies.py
new file mode 100644
index 0000000..c03d0a6
--- /dev/null
+++ b/sdaps/reporttex/buddies.py
@@ -0,0 +1,237 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, 2011, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import cairo
+import math
+import os
+
+from sdaps import clifilter
+from sdaps import template
+from sdaps import model
+from sdaps import image
+
+from sdaps import calculate
+
+from sdaps.utils.latex import raw_unicode_to_latex, unicode_to_latex
+
+from sdaps.utils.image import ImageWriter
+
+
+def format_raw_text(text):
+    from sdaps.setuptex import latexmap
+
+class Questionnaire(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'report'
+    obj_class = model.questionnaire.Questionnaire
+
+    def init(self, img_dir, small=0, suppress=None):
+        self.small = small
+
+        self.textbox_writer = ImageWriter(img_dir, 'textbox-')
+
+        # iterate over qobjects
+        for qobject in self.obj.qobjects:
+            qobject.report.init(small, suppress)
+
+    def report(self, tmpdir):
+        # iterate over qobjects
+        for qobject in self.obj.qobjects:
+            qobject.report.report(tmpdir)
+
+    def write(self, out, tmpdir):
+        # iterate over qobjects
+        for qobject in self.obj.qobjects:
+            qobject.report.write(out, tmpdir)
+
+    def filters(self):
+        filters = list()
+        # iterate over qobjects
+        for qobject in self.obj.qobjects:
+            filters.extend(list(qobject.report.filters()))
+        return filters
+
+
+class QObject(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    name = 'report'
+    obj_class = model.questionnaire.QObject
+
+    def init(self, small, suppress):
+        self.small = small
+
+    def report(self, tmpdir):
+        pass
+
+    def write(self, out, tmpdir):
+        pass
+
+    def filters(self):
+        return []
+
+
+class Head(QObject):
+
+    __metaclass__ = model.buddy.Register
+    name = 'report'
+    obj_class = model.questionnaire.Head
+
+    def write(self, out, tmpdir):
+        # Smarter numbering handling?
+        out.write('\\section*{%s %s}\n' % (self.obj.id_str(), unicode_to_latex(self.obj.title)))
+
+
+class Question(QObject):
+
+    __metaclass__ = model.buddy.Register
+    name = 'report'
+    obj_class = model.questionnaire.Question
+
+    def write_begin(self, out):
+        # Smarter numbering handling?
+        out.write('\\begin{question}{%s %s}\n' % (self.obj.id_str(), unicode_to_latex(self.obj.question)))
+
+    def write_end(self, out):
+        # Smarter numbering handling?
+        out.write('\\end{question}\n\n')
+
+    def write(self, out, tmpdir):
+        self.write_begin(out)
+        self.write_end(out)
+
+
+class Choice(Question):
+
+    __metaclass__ = model.buddy.Register
+    name = 'report'
+    obj_class = model.questionnaire.Choice
+
+    def init(self, small, suppress):
+        self.small = small
+        self.suppress = suppress
+        self.text = ""
+
+    def report(self, tmpdir):
+        if not self.small:
+            for box in self.obj.boxes:
+                if (isinstance(box, model.questionnaire.Textbox) and
+                        box.data.state):
+                    if box.data.text and self.suppress != 'substitutions':
+                        text = raw_unicode_to_latex(box.data.text)
+                        self.text += '\\freeformtext{%s}\n' % (text)
+                    elif self.suppress != 'images':
+                        img_file = self.obj.questionnaire.report.textbox_writer.output_box(box)
+                        self.text += '\\freeform{%fmm}{%s}\n' % (box.data.width, img_file)
+
+    def write_begin(self, out):
+        # Smarter numbering handling?
+        out.write('\\begin{choicequestion}{%s %s}\n' % (self.obj.id_str(), unicode_to_latex(self.obj.question)))
+
+    def write_end(self, out):
+        # Smarter numbering handling?
+        out.write('\\end{choicequestion}\n\n')
+
+    def write(self, out, tmpdir):
+        self.write_begin(out)
+        if self.obj.calculate.count:
+            for box in self.obj.boxes:
+                out.write('''\\choiceanswer{%s}{%.3f}\n''' % (unicode_to_latex(box.text), self.obj.calculate.values[box.value]))
+        self.write_end(out)
+
+        out.write(self.text)
+
+    def filters(self):
+        for box in self.obj.boxes:
+            yield u'%i in %s' % (box.value, self.obj.id_filter())
+
+
+class Mark(Question):
+
+    __metaclass__ = model.buddy.Register
+    name = 'report'
+    obj_class = model.questionnaire.Mark
+
+    def write(self, out, tmpdir):
+        Question.write_begin(self, out)
+
+        if self.obj.calculate.count:
+            out.write('\\pgfkeyssetvalue{/sdaps/mark/range}{%s}\n' % (len(self.obj.boxes)))
+            out.write('\\pgfkeyssetvalue{/sdaps/mark/lower}{%s}\n' % (unicode_to_latex(self.obj.answers[0])))
+            out.write('\\pgfkeyssetvalue{/sdaps/mark/upper}{%s}\n' % (unicode_to_latex(self.obj.answers[1])))
+            out.write('\\pgfkeyssetvalue{/sdaps/mark/count}{%i}\n' % (self.obj.calculate.count))
+            out.write('\\pgfkeyssetvalue{/sdaps/mark/stddev}{%.1f}\n' % (self.obj.calculate.standard_deviation))
+            for i, fraction in sorted(self.obj.calculate.values.iteritems()):
+                out.write('\\pgfkeyssetvalue{/sdaps/mark/%i/fraction}{%.3f}\n' % (i, fraction))
+            out.write('\\pgfkeyssetvalue{/sdaps/mark/mean}{%.1f}\n' % (self.obj.calculate.mean))
+            out.write('\n\\markanswer\n')
+
+        Question.write_end(self, out)
+
+    def filters(self):
+        for x in range(len(self.obj.boxes)+1):
+            yield u'%i == %s' % (x, self.obj.id_filter())
+
+
+class Text(Question):
+
+    __metaclass__ = model.buddy.Register
+    name = 'report'
+    obj_class = model.questionnaire.Text
+
+    def init(self, small, suppress):
+        self.small = small
+        self.suppress = suppress
+        self.text = ""
+
+    def report(self, tmpdir):
+        if not self.small:
+            for box in self.obj.boxes:
+                if box.data.state:
+                    if box.data.text and self.suppress != 'substitutions':
+                        text = raw_unicode_to_latex(box.data.text)
+                        self.text += '\\freeformtext{%s}\n' % (text)
+                    elif self.suppress != 'images':
+                        img_file = self.obj.questionnaire.report.textbox_writer.output_box(box)
+                        self.text += '\\freeform{%fmm}{%s}\n' % (box.data.width, img_file)
+
+    def write(self, out, tmpdir):
+        Question.write_begin(self, out)
+
+        out.write(self.text)
+
+        Question.write_end(self, out)
+
+
+class Additional_FilterHistogram(Question):
+
+    __metaclass__ = model.buddy.Register
+    name = 'report'
+    obj_class = model.questionnaire.Additional_FilterHistogram
+
+    def write(self, tmpdir):
+        Question.write_begin(self, out)
+
+        if self.obj.calculate.count:
+            for i in range(len(self.obj.calculate.values)):
+                out.write('''\\choiceanswer{%s}{%.3f}{%.3f}\n''' %
+                          (unicode_to_latex(self.obj.answers[i]), self.obj.calculate.values[i], self.obj.calculate.significant[i]))
+
+        Question.write_end(self, out)
+
diff --git a/sdaps/script.py b/sdaps/script.py
new file mode 100644
index 0000000..d69a411
--- /dev/null
+++ b/sdaps/script.py
@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+u'''
+This module defines some decorators, helping to implement sdaps-scripts.
+To register a function as a sdaps-script(callable from the command line), use
+ at register
+'''
+
+import sys
+import os
+import functools
+import argparse
+
+import log
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+# Create parser
+
+if "sphinx" in sys.argv[0]:
+    prog = "sdaps"
+else:
+    prog = None
+
+description = _("SDAPS -- Paper based survey tool.")
+epilog = None
+parser = argparse.ArgumentParser(description=description, epilog=epilog, prog=prog)
+
+parser.add_argument('project', type=str, help=_("project directory|The SDAPS project."))
+subparsers = parser.add_subparsers(help=_("command list|Commands:"))
+                    
+                    
+def doc(docstring):
+    u'''decorator to add a docstring to a function.
+
+    When using normal Python docstring syntax it cannot be generated
+    dynamically. Using this one can for example add translations.
+
+    >>> @doc(_(u'docstring'))
+    >>> def function(*args, **kwargs):
+    >>>    pass
+    '''
+
+    def decorator(function):
+        function.func_doc = docstring
+        return function
+    return decorator
+
+def connect(parser, name=None):
+    u'''decorator to connect an already prepared parser to call into a function.
+
+    This function initilizes the _func and _name properties for the parser to
+    the given function, and its name. It also sets the functions docstring to
+    be the parsers help. This way the help string appears in the sphinx
+    documentation for the function.
+
+    >>> @script.connect(parser)
+    >>> def add(cmdline):
+    >>>     pass
+    '''
+
+    def decorator(function):
+        # Use the function name as a fallback, it should be the same usually.
+        if name is None:
+            local_name = function.__name__
+        else:
+            local_name = name
+
+        parser.set_defaults(_func=function, _name=local_name)
+
+        function.func_doc = parser.format_help()
+
+        return function
+
+    return decorator
+
+def logfile(function):
+    u'''open the logfile when running the function and close it afterwards.
+
+    >>> @logfile
+    >>> def function(survey_dir, *args, **kwargs):
+    >>>     pass
+
+    @logfile will open survey_dir/log as a logfile when function is called and
+    close it, when function finishes.
+    '''
+    def decorated_function(cmdline):
+        log.logfile.open(os.path.join(cmdline['project'], 'log'))
+        result = function(cmdline)
+        log.logfile.close()
+        return result
+
+    functools.update_wrapper(decorated_function, function)
+
+    return decorated_function
+
+
diff --git a/sdaps/setup/__init__.py b/sdaps/setup/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/sdaps/setup/additionalparser.py b/sdaps/setup/additionalparser.py
new file mode 100644
index 0000000..8e42980
--- /dev/null
+++ b/sdaps/setup/additionalparser.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+from sdaps import model
+
+
+def parse(survey, additionalqobjects):
+
+    document = file(additionalqobjects, 'r')
+
+    for line in document:
+        line = line.decode('utf-8')
+        args = line.strip().split('\t')
+        qobject = getattr(model.questionnaire, 'Additional_%s' % args.pop(0))
+        assert issubclass(qobject, model.questionnaire.QObject)
+        qobject = qobject()
+        survey.questionnaire.add_qobject(qobject)
+        qobject.setup.setup(args)
+
diff --git a/sdaps/setup/buddies.py b/sdaps/setup/buddies.py
new file mode 100644
index 0000000..dbba9db
--- /dev/null
+++ b/sdaps/setup/buddies.py
@@ -0,0 +1,229 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+from sdaps import model
+from sdaps import log
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+
+class QObject(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    obj_class = model.questionnaire.QObject
+    name = 'setup'
+
+    def init(self):
+        pass
+
+    def question(self, chars):
+        pass
+
+    def answer(self, chars):
+        pass
+
+    def box(self, box):
+        pass
+
+    def validate(self):
+        pass
+
+    def setup(self, args):
+        pass
+
+
+class Head(QObject):
+
+    __metaclass__ = model.buddy.Register
+    obj_class = model.questionnaire.Head
+
+    def question(self, chars):
+        self.obj.title += chars.strip()
+
+    def validate(self):
+        if not self.obj.title:
+            log.warn(_(u'Head %(l0)i got no title.') % {'l0': self.obj.id[0]})
+
+
+class Question(QObject):
+
+    __metaclass__ = model.buddy.Register
+    obj_class = model.questionnaire.Question
+
+    def question(self, chars):
+        self.obj.question += chars.strip()
+
+    def validate(self):
+        if not self.obj.question:
+            log.warn(_(u'%(class)s %(l0)i.%(l1)i got no question.') % {
+                'class': self.obj.__class__.__name__,
+                'l0': self.obj.id[0], 'l1': self.obj.id[1]
+            })
+
+
+class Choice(Question):
+
+    __metaclass__ = model.buddy.Register
+    obj_class = model.questionnaire.Choice
+
+    def init(self):
+        self.cache = list()
+
+    def _box(self, box):
+        self.obj.add_box(box)
+        if self.obj.page_number == 0:
+            self.obj.page_number = box.page_number
+        else:
+            assert self.obj.page_number == box.page_number
+
+    def answer(self, chars):
+        if self.cache:
+            if isinstance(self.cache[0], unicode):
+                self.cache.append(chars)
+            else:
+                box = self.cache.pop(0)
+                box.setup.answer(chars)
+                self._box(box)
+        else:
+            self.cache.append(chars)
+
+    def box(self, box):
+        if self.cache:
+            if isinstance(self.cache[0], unicode):
+                answer = self.cache.pop(0)
+                box.setup.answer(answer)
+                self._box(box)
+            else:
+                self.cache.append(box)
+        else:
+            self.cache.append(box)
+
+    def validate(self):
+        Question.validate(self)
+        if self.cache:
+            raise AssertionError(_("Error in question \"%s\"") % self.obj.question)
+        del self.cache
+        if not self.obj.boxes:
+            log.warn(_(u'%(class)s %(l0)i.%(l1)i got no boxes.') % {
+                'class': self.obj.__class__.__name__,
+                'l0': self.obj.id[0], 'l1': self.obj.id[1]
+            })
+
+
+class Mark(Question):
+
+    __metaclass__ = model.buddy.Register
+    obj_class = model.questionnaire.Mark
+
+    def answer(self, chars):
+        self.obj.answers.append(chars)
+
+    def box(self, box):
+        assert isinstance(box, model.questionnaire.Checkbox)
+        self.obj.add_box(box)
+        if self.obj.page_number == 0:
+            self.obj.page_number = box.page_number
+        else:
+            assert self.obj.page_number == box.page_number
+
+    def validate(self):
+        Question.validate(self)
+        if not len(self.obj.answers) == 2:
+            log.warn(_(u'%(class)s %(l0)i.%(l1)i got not exactly two answers.') % {
+                'class': self.obj.__class__.__name__,
+                'l0': self.obj.id[0], 'l1': self.obj.id[1]
+            })
+
+
+class Text(Question):
+
+    __metaclass__ = model.buddy.Register
+    obj_class = model.questionnaire.Text
+
+    def box(self, box):
+        assert isinstance(box, model.questionnaire.Textbox)
+        self.obj.add_box(box)
+        if self.obj.page_number == 0:
+            self.obj.page_number = box.page_number
+        else:
+            assert self.obj.page_number == box.page_number
+
+    def validate(self):
+        Question.validate(self)
+        if not len(self.obj.boxes) == 1:
+            log.warn(_(u'%(class)s %(l0)i.%(l1)i got not exactly one box.') % {
+                'class': self.obj.__class__.__name__,
+                'l0': self.obj.id[0], 'l1': self.obj.id[1]
+            })
+
+
+class Additional_Head(Head):
+
+    __metaclass__ = model.buddy.Register
+    obj_class = model.questionnaire.Additional_Head
+
+    def setup(self, args):
+        assert len(args) == 1
+        self.question(args[0])
+        self.validate()
+
+
+class Additional_Mark(Question):
+
+    __metaclass__ = model.buddy.Register
+    obj_class = model.questionnaire.Additional_Mark
+
+    def setup(self, args):
+        assert len(args) == 3
+        self.question(args[0])
+        self.obj.answers.append(args[1])
+        self.obj.answers.append(args[2])
+        self.validate()
+
+
+class Additional_FilterHistogram(Question):
+
+    __metaclass__ = model.buddy.Register
+    obj_class = model.questionnaire.Additional_FilterHistogram
+
+    def setup(self, args):
+        assert len(args) % 2 == 1
+        self.question(args.pop(0))
+        while len(args):
+            self.obj.answers.append(args.pop(0))
+            self.obj.filters.append(args.pop(0))
+        self.validate()
+
+
+class Box(model.buddy.Buddy):
+
+    __metaclass__ = model.buddy.Register
+    obj_class = model.questionnaire.Box
+    name = 'setup'
+
+    def setup(self, page_number, x, y, width, height):
+        self.obj.page_number = page_number
+        self.obj.x = x
+        self.obj.y = y
+        self.obj.width = width
+        self.obj.height = height
+
+    def answer(self, text):
+        self.obj.text = text
+
diff --git a/sdaps/setupodt/__init__.py b/sdaps/setupodt/__init__.py
new file mode 100644
index 0000000..7156a25
--- /dev/null
+++ b/sdaps/setupodt/__init__.py
@@ -0,0 +1,144 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+u"""
+Contains the functionality to create a new SDAPS project using an OpenOffice.org
+document and its PDF Export.
+"""
+
+import os
+import shutil
+
+from sdaps.utils.mimetype import mimetype
+from sdaps import model
+from sdaps import log
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+from ..setup import buddies
+from ..setup import additionalparser
+from . import boxesparser
+from . import qobjectsparser
+from . import metaparser
+from pdftools import pdffile
+
+
+def setup(survey, questionnaire_odt, questionnaire_pdf, additionalqobjects, options):
+
+    if os.access(survey.path(), os.F_OK):
+        log.error(_('The survey directory already exists.'))
+        return 1
+
+    mime = mimetype(questionnaire_odt)
+    if mime != 'application/vnd.oasis.opendocument.text' and mime not in ['', 'binary', 'application/octet-stream']:
+        log.error(_('Unknown file type (%s). questionnaire_odt should be application/vnd.oasis.opendocument.text.') % mime)
+        return 1
+
+    mime = mimetype(questionnaire_pdf)
+    if mime != 'application/pdf' and mime != '':
+        log.error(_('Unknown file type (%s). questionnaire_pdf should be application/pdf.') % mime)
+        return 1
+
+    if additionalqobjects is not None:
+        mime = mimetype(additionalqobjects)
+        if mime != 'text/plain' and mime != '':
+            log.error(_('Unknown file type (%s). additionalqobjects should be text/plain.') % mime)
+            return 1
+
+    # Add the new questionnaire
+    survey.add_questionnaire(model.questionnaire.Questionnaire())
+
+    # Parse the box objects into a cache
+    boxes, page_count = boxesparser.parse(questionnaire_pdf)
+    survey.questionnaire.page_count = page_count
+
+    # Get the papersize
+    doc = pdffile.PDFDocument(questionnaire_pdf)
+    page = doc.read_page(1)
+    survey.defs.paper_width = abs(page.MediaBox[0] - page.MediaBox[2]) / 72.0 * 25.4
+    survey.defs.paper_height = abs(page.MediaBox[1] - page.MediaBox[3]) / 72.0 * 25.4
+    survey.defs.print_questionnaire_id = options['print_questionnaire_id']
+    survey.defs.print_survey_id = options['print_survey_id']
+
+    survey.defs.style = options['style']
+    survey.defs.checkmode = options['checkmode']
+    # Force simplex if page count is one.
+    survey.defs.duplex = False if page_count == 1 else options['duplex']
+
+    survey.global_id = options['global_id']
+
+    # Parse qobjects
+    try:
+        qobjectsparser.parse(survey, questionnaire_odt, boxes)
+    except:
+        log.error(_("Caught an Exception while parsing the ODT file. The current state is:"))
+        print unicode(survey.questionnaire)
+        print "------------------------------------"
+        print _("If the dependencies for the \"annotate\" command are installed, then an annotated version will be created next to the original PDF file.")
+        print "------------------------------------"
+
+        # Try to make an annotation
+        try:
+            if questionnaire_pdf.lower().endswith('.pdf'):
+                annotated_pdf = questionnaire_pdf[:-4] + '_annotated.pdf'
+            else:
+                # No .pdf ending? Just append the _annotated.pdf.
+                annotated_pdf = questionnaire_pdf + '_annotated.pdf'
+
+            import sdaps.annotate as annotate
+            annotate.annotate(survey, questionnaire_pdf, annotated_pdf)
+        except:
+            # Well, whatever
+            pass
+
+        raise
+
+    # Parse additionalqobjects
+    if additionalqobjects:
+        additionalparser.parse(survey, additionalqobjects)
+
+    # Parse Metadata
+    metaparser.parse(survey, questionnaire_odt)
+
+    # Last but not least calculate the survey id
+    survey.calculate_survey_id()
+
+    if not survey.check_settings():
+        log.error(_("Some combination of options and project properties do not work. Aborted Setup."))
+        return 1
+
+    # Print the result
+    print survey.title
+
+    for item in survey.info.items():
+        print u'%s: %s' % item
+
+    print unicode(survey.questionnaire)
+
+    # Create the survey
+    os.makedirs(survey.path())
+
+    log.logfile.open(survey.path('log'))
+
+    shutil.copy(questionnaire_odt, survey.path('questionnaire.odt'))
+    shutil.copy(questionnaire_pdf, survey.path('questionnaire.pdf'))
+
+    survey.save()
+    log.logfile.close()
+
diff --git a/sdaps/setupodt/boxesparser.py b/sdaps/setupodt/boxesparser.py
new file mode 100644
index 0000000..5ba4318
--- /dev/null
+++ b/sdaps/setupodt/boxesparser.py
@@ -0,0 +1,264 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+from pdftools import *
+
+from sdaps import model
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+
+inch = 72.0 # pt(pdf unit)
+cm = inch / 2.54
+mm = cm * 0.1
+
+# TODO Documentation for these values
+# (min, max)
+TEXTBOX_WIDTH = (6.0 * cm, 20.0 * cm)
+TEXTBOX_HEIGHT = (0.6 * cm, 20.0 * cm)
+CHECKBOX_SIZE = (3.4 * mm, 3.6 * mm)
+LINE_SIZE = (0.0 * mm, 0.5 * mm)
+
+
+class DummyBox(object):
+    def __init__(self, page, x, y, width, height):
+        self.page = page
+        self.x = x
+        self.y = y
+        self.width = width
+        self.height = height
+
+
+def parse(questionnaire_pdf):
+
+    doc = pdffile.PDFDocument(questionnaire_pdf)
+
+    boxes = list()
+
+    page_count = doc.count_pages()
+    assert page_count == 1 or page_count % 2 == 0
+
+    for page_number in range(1, page_count + 1):
+        page = doc.read_page(page_number)
+        contents = page.read_contents()
+        box = None
+        box_linecount = 0
+        # width and height are in pt!
+        # TODO Test if its A4...
+        width = page['MediaBox'][2]
+        height = page['MediaBox'][3]
+
+        for obj in contents.contents:
+            newbox = None
+            if isinstance(obj, pdfpath.Path) and obj.painting:
+                # only analyse painting paths. Ignore clipping paths
+                # in fact, the subpath contains all information
+                if len(obj.subpaths) == 1 and isinstance(obj.subpaths[0], pdfpath.Rectangle):
+                    # OpenOffice seams to construct only paths containing exactly one rectangle
+                    rect = obj.subpaths[0]
+
+                    if LINE_SIZE[0] < rect.width < LINE_SIZE[1] or \
+                            LINE_SIZE[0] < rect.height < LINE_SIZE[1]:
+
+                        if box is None:
+                            continue
+
+                        if rect.width < LINE_SIZE[1]:
+                            if rect.height / mm == box.height and \
+                                    box.y == (height - rect.point.y - rect.height) / mm and \
+                                    (abs(box.x - rect.point.x / mm) < 0.01 or
+                                     abs(box.x + box.width - ((rect.point.x + rect.width) / mm)) < 0.01):
+                                box_linecount += 1
+                        else:
+                            if rect.width / mm == box.width and \
+                                    box.x == rect.point.x / mm and \
+                                    (abs(box.y - (height - rect.point.y - rect.height) / mm) < 0.01 or
+                                     abs(box.y + box.height - ((height - rect.point.y - rect.height) +
+                                                               rect.height) / mm) < 0.01):
+                                box_linecount += 1
+                        if box_linecount == 4:
+                            if isinstance(box, DummyBox):
+                                print _("Warning: Ignoring a box (page: %i, x: %.1f, y: %.1f, width: %.1f, height: %.1f).") % \
+                                    (box.page, box.x, box.y, box.width, box.height)
+                            else:
+                                boxes.append(box)
+                            box = None
+                            box_linecount = 0
+                    else:
+                        # transform the coordinate origin from the lower left corner to the upper left corner
+                        # and name the upper left corner of the box, not the lower left one
+                        newbox = (rect.point.x, (height - rect.point.y - rect.height), rect.width, rect.height)
+
+                elif len(obj.subpaths) == 1 and isinstance(obj.subpaths[0], pdfpath.Subpath):
+
+                    # OOo 3.x draws the box using a move + 4 lines + close.
+                    if len(obj.subpaths[0].contents) == 6:
+                        # 5 subpaths, m,l,l,l,l,c
+
+                        point = obj.subpaths[0].contents[0]
+                        if not isinstance(point, pdfpath.Move):
+                            continue
+                        point_a = point.point
+
+                        point = obj.subpaths[0].contents[1]
+                        if not isinstance(point, pdfpath.Line):
+                            continue
+                        point_b = point.point2
+
+                        point = obj.subpaths[0].contents[2]
+                        if not isinstance(point, pdfpath.Line):
+                            continue
+                        point_c = point.point2
+
+                        point = obj.subpaths[0].contents[3]
+                        if not isinstance(point, pdfpath.Line):
+                            continue
+                        point_d = point.point2
+
+                        point = obj.subpaths[0].contents[4]
+                        if not isinstance(point, pdfpath.Line):
+                            continue
+                        point_e = point.point2
+
+                        if not isinstance(obj.subpaths[0].contents[5], pdfpath.Close):
+                            continue
+
+                        if (point_e.x == point_a.x and point_e.y == point_a.y):
+                            # assume we have a box, just use first and third point
+                            # to calculate size.
+                            bwidth = point_c.x - point_a.x
+                            bheight = point_a.y - point_c.y
+                            newbox = ( point_a.x, (height - point_a.y), bwidth, bheight )
+
+                        if box is not None:
+                            bad = False
+                            if not (point_a.x / mm - box.x < 0.0001 and
+                                    (height - point_a.y) / mm - box.y < 0.0001):
+                                bad = True
+                            if not (point_b.x / mm - box.x < 0.0001 and
+                                    (height - point_b.y) / mm - box.y - box.height < 0.0001):
+                                bad = True
+                            if not (point_c.x / mm - box.x - box.width < 0.0001 and
+                                    (height - point_c.y) / mm - box.y - box.height < 0.0001):
+                                bad = True
+                            if not (point_d.x / mm - box.x - box.width < 0.0001 and
+                                    (height - point_d.y) / mm - box.y < 0.0001):
+                                bad = True
+                            if not (point_e.x / mm - box.x < 0.0001 and
+                                    (height - point_e.y) / mm - box.y - box.height < 0.0001):
+                                bad = True
+
+                            if not bad:
+                                if isinstance(box, DummyBox):
+                                    print _("Warning: Ignoring a box (page: %i, x: %.1f, y: %.1f, width: %.1f, height: %.1f).") % \
+                                        (box.page, box.x, box.y, box.width, box.height)
+                                else:
+                                    boxes.append(box)
+                                box = None
+
+                    # LibreOffice 3.5 draws the border as four trapezoids, with
+                    # a 45degree angle in between, then filling it
+                    elif len(obj.subpaths[0].contents) == 8:
+                        if box is None:
+                            continue
+
+                        # 8 subpaths, m,l,l,l,l,l,l,h
+                        # The angled corners have 3 points, instead of two, whoever thought of that ...
+                        # We just check that all points are acurately inside the rectangle here
+                        x_min = 99999
+                        y_min = 99999
+                        x_max = -99999
+                        y_max = -99999
+                        for point in obj.subpaths[0].contents:
+                            if isinstance(point, pdfpath.Close):
+                                continue
+
+                            if isinstance(point, pdfpath.Line):
+                                point = point.point2
+                            else:
+                                point = point.point
+
+                            x_min = min(x_min, point.x)
+                            y_min = min(y_min, point.y)
+
+                            x_max = max(x_max, point.x)
+                            y_max = max(y_max, point.y)
+
+                        _width = x_max - x_min
+                        _height = y_max - y_min
+
+                        # The tolerance needs to be this high. At least
+                        # LO 3.5.1.2 on MacOSX is 0.1 pt off for one of the
+                        # border thicknesses.
+                        if abs(_width - 1) < 0.1001:
+                            if abs(_height - box.height * mm) < 0.0001 and \
+                               abs(height - y_max - box.y * mm) < 0.0001:
+                                box_linecount += 1
+                        elif abs(_height - 1) < 0.1001:
+                            if abs(_width - box.width * mm) < 0.0001 and \
+                               abs(x_min - box.x * mm) < 0.0001:
+                                box_linecount += 1
+                        else:
+                            box = None
+                            box_linecount = 0
+
+                        if box_linecount == 4:
+                            boxes.append(box)
+                            box = None
+                            box_linecount = 0
+
+            if newbox is not None:
+                # if it's a big rectangle, it's a textbox
+                if TEXTBOX_WIDTH[0] < newbox[2] < TEXTBOX_WIDTH[1] and \
+                        TEXTBOX_HEIGHT[0] < newbox[3] < TEXTBOX_HEIGHT[1]:
+                    box = model.questionnaire.Textbox()
+                    box_linecount = 0
+                    box.setup.setup(
+                        page_number,
+                        newbox[0] / mm,
+                        newbox[1] / mm,
+                        newbox[2] / mm,
+                        newbox[3] / mm
+                    )
+                # if it's a small square, it's a checkbox
+                elif CHECKBOX_SIZE[0] < newbox[2] < CHECKBOX_SIZE[1] and \
+                        CHECKBOX_SIZE[0] < newbox[3] < CHECKBOX_SIZE[1]:
+                    box = model.questionnaire.Checkbox()
+                    box_linecount = 0
+                    box.setup.setup(
+                        page_number,
+                        newbox[0] / mm,
+                        newbox[1] / mm,
+                        newbox[2] / mm,
+                        newbox[3] / mm
+                    )
+                else:
+                    box_linecount = 0
+                    box = DummyBox(
+                        page_number,
+                        newbox[0] / mm,
+                        newbox[1] / mm,
+                        newbox[2] / mm,
+                        newbox[3] / mm
+                    )
+
+
+    return boxes, page_count
+
+
+
diff --git a/sdaps/setupodt/metaparser.py b/sdaps/setupodt/metaparser.py
new file mode 100644
index 0000000..f33f2ba
--- /dev/null
+++ b/sdaps/setupodt/metaparser.py
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import xml.sax
+import zipfile
+
+from sdaps import model
+
+
+class StylesHandler(xml.sax.ContentHandler):
+
+    def __init__(self, survey):
+        self.active = 0
+        self.survey = survey
+        self.title = list()
+
+    def startElement(self, name, attrs):
+        if name == u'style:header':
+            self.active = 1
+        elif self.active and name == u'text:p':
+            self.title.append(unicode())
+
+    def endElement(self, name):
+        if name == u'style:header':
+            self.active = 0
+
+    def characters(self, chars):
+        if self.active:
+            self.title[-1] += chars.strip()
+
+    def endDocument(self):
+        self.survey.title = '\n'.join([x for x in self.title if x])
+
+
+class MetaHandler(xml.sax.ContentHandler):
+
+    def __init__(self, survey):
+        self.attribute = None
+        self.survey = survey
+        self.chars = unicode()
+
+    def startElement(self, name, attrs):
+        if name == u'meta:user-defined':
+            self.attribute = attrs[u'meta:name']
+            self.chars = unicode()
+
+    def endElement(self, name):
+        if self.attribute and name == u'meta:user-defined':
+            if len(self.chars.strip()) > 0:
+                self.survey.info[self.attribute] = self.chars
+            self.attribute = None
+
+    def characters(self, chars):
+        if self.attribute:
+            self.chars += chars.strip()
+
+
+def parse(survey, questionnaire_odt):
+
+    document = zipfile.ZipFile(questionnaire_odt, 'r')
+
+    content = document.read('styles.xml')
+    handler = StylesHandler(survey)
+    xml.sax.parseString(content, handler)
+
+    content = document.read('meta.xml')
+    handler = MetaHandler(survey)
+    xml.sax.parseString(content, handler)
+
+    document.close()
+
diff --git a/sdaps/setupodt/qobjectsparser.py b/sdaps/setupodt/qobjectsparser.py
new file mode 100644
index 0000000..bf67e20
--- /dev/null
+++ b/sdaps/setupodt/qobjectsparser.py
@@ -0,0 +1,102 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import xml.sax
+import zipfile
+
+from sdaps import model
+
+
+QOBJECT_PREFIX = u'QObject'
+ANSWER_PREFIX = u'Answer'
+BOXES = [u'Checkbox', u'Textbox']
+
+
+class ContentHandler(xml.sax.ContentHandler):
+
+    def __init__(self, survey, boxes):
+        self.survey = survey
+        self.boxes = boxes
+        self.active = 1
+        self.qobject = None
+        self.answer = None
+        self.last_qobject = None
+        self.chars = unicode()
+        self.parent_styles = dict()
+
+    def endDocument(self):
+        for qobject in self.survey.questionnaire.qobjects:
+            qobject.setup.validate()
+
+    def startElement(self, name, attrs):
+        if name != u'text:span':
+            self.setup_characters()
+
+        if self.active and name == u'text:p':
+            qobject = attrs[u'text:style-name']
+            if qobject in self.parent_styles:
+                qobject = self.parent_styles[qobject]
+            if qobject.startswith(QOBJECT_PREFIX):
+                qobject = qobject[len(QOBJECT_PREFIX) + 1:]
+                qobject = getattr(model.questionnaire, qobject)
+                assert issubclass(qobject, model.questionnaire.QObject)
+                self.qobject = qobject()
+                self.survey.questionnaire.add_qobject(self.qobject)
+                self.qobject.setup.init()
+            elif qobject.startswith(ANSWER_PREFIX):
+                self.answer = self.last_qobject
+        elif name == u'draw:frame':
+            self.active = 0
+            if attrs[u'draw:style-name'] in BOXES or self.parent_styles[attrs[u'draw:style-name']] in BOXES:
+                self.answer.setup.box(self.boxes.pop(0))
+        elif name == u'style:style' and u'style:parent-style-name' in attrs:
+            self.parent_styles[attrs[u'style:name']] = attrs[u'style:parent-style-name']
+
+    def endElement(self, name):
+        if name != u'text:span':
+            self.setup_characters()
+
+        if self.active and self.qobject and name == u'text:p':
+            self.last_qobject = self.qobject
+            self.qobject = None
+        elif self.active and self.answer and name == u'text:p':
+            self.answer = None
+        elif name == u'draw:frame':
+            self.active = 1
+
+    def setup_characters(self):
+        if self.active and self.chars:
+            if self.qobject:
+                self.qobject.setup.question(self.chars)
+            elif self.answer:
+                self.answer.setup.answer(self.chars)
+        self.chars = unicode()
+
+    def characters(self, chars):
+        if self.active and(self.qobject or self.answer):
+            self.chars += chars.strip()
+
+
+def parse(survey, questionnaire_odt, boxes):
+
+    document = zipfile.ZipFile(questionnaire_odt, 'r')
+    content = document.read('content.xml')
+    document.close()
+
+    handler = ContentHandler(survey, boxes)
+    xml.sax.parseString(content, handler)
diff --git a/sdaps/setuptex/__init__.py b/sdaps/setuptex/__init__.py
new file mode 100644
index 0000000..f9ceab4
--- /dev/null
+++ b/sdaps/setuptex/__init__.py
@@ -0,0 +1,175 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2010, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+u"""
+Contains the functionality to create a new SDAPS project using a LaTeX input
+file.
+"""
+
+import sys
+import os
+import shutil
+import glob
+
+from sdaps.utils.mimetype import mimetype
+from sdaps import model
+from sdaps import log
+from sdaps import paths
+from sdaps import defs
+
+from sdaps.utils import latex
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+from ..setup import buddies
+from . import sdapsfileparser
+from ..setup import additionalparser
+
+
+def write_latex_override_file(survey, draft=False):
+    # Create the sdaps.opt file.
+    latex_override = open(survey.path('sdaps.opt'), 'w')
+    latex_override.write('% This file exists to force the latex document into "final" mode.\n')
+    latex_override.write('% It is parsed after the setup phase of the SDAPS class.\n\n')
+    latex_override.write('\\@STAMPtrue\n')
+    latex_override.write('\\@PAGEMARKtrue\n\n')
+    latex_override.write('\setcounter{surveyidlshw}{%i}\n' % (survey.survey_id % (2 ** 16)))
+    latex_override.write('\setcounter{surveyidmshw}{%i}\n' % (survey.survey_id / (2 ** 16)))
+    latex_override.write('\def\surveyid{%i}\n' % (survey.survey_id))
+    if not draft:
+        latex_override.write('% We turn off draft mode if questionnaire IDs are not printed.\n')
+        latex_override.write('\\if at PrintQuestionnaireId\n')
+        latex_override.write('\\else\n')
+        latex_override.write('\\@sdaps at draftfalse\n')
+        latex_override.write('\\fi\n')
+    else:
+        latex_override.write('% Turn on draft mode on first compile (this should only be temporary).\n')
+        latex_override.write('\\@sdaps at drafttrue\n')
+    latex_override.close()
+
+
+def setup(survey, questionnaire_tex, additionalqobjects=None, extra_files=[]):
+    if os.access(survey.path(), os.F_OK):
+        log.error(_('The survey directory already exists.'))
+        return 1
+
+    mime = mimetype(questionnaire_tex)
+    if mime != 'text/x-tex' and mime != '':
+        log.warn(_('Unknown file type (%s). questionnaire_tex should be of type text/x-tex.') % mime)
+        log.warn(_('Will keep going, but expect failure!'))
+
+    if additionalqobjects is not None:
+        mime = mimetype(additionalqobjects)
+        if mime != 'text/plain' and mime != '':
+            log.error(_('Unknown file type (%s). additionalqobjects should be text/plain.') % mime)
+            return 1
+
+    # Add the new questionnaire
+    survey.add_questionnaire(model.questionnaire.Questionnaire())
+
+    # Create the survey directory, and copy the tex file.
+    os.makedirs(survey.path())
+    try:
+        shutil.copy(questionnaire_tex, survey.path('questionnaire.tex'))
+
+        write_latex_override_file(survey, draft=True)
+
+        # Copy class and dictionary files
+        if paths.local_run:
+            cls_file = os.path.join(paths.source_dir, 'tex', 'sdaps.cls')
+            code128_file = os.path.join(paths.source_dir, 'tex', 'code128.tex')
+            qrcode_style = os.path.join(paths.source_dir, 'tex', 'qrcode.sty')
+            dict_files = os.path.join(paths.build_dir, 'tex', '*.dict')
+            dict_files = glob.glob(dict_files)
+        else:
+            cls_file = os.path.join(paths.prefix, 'share', 'sdaps', 'tex', 'sdaps.cls')
+            code128_file = os.path.join(paths.prefix, 'share', 'sdaps', 'tex', 'code128.tex')
+            qrcode_style = os.path.join(paths.prefix, 'share', 'sdaps', 'tex', 'qrcode.sty')
+            dict_files = os.path.join(paths.prefix, 'share', 'sdaps', 'tex', '*.dict')
+            dict_files = glob.glob(dict_files)
+
+        shutil.copyfile(cls_file, survey.path('sdaps.cls'))
+        shutil.copyfile(code128_file, survey.path('code128.tex'))
+        shutil.copyfile(qrcode_style, survey.path('qrcode.sty'))
+        for dict_file in dict_files:
+            shutil.copyfile(dict_file, survey.path(os.path.basename(dict_file)))
+
+        for add_file in extra_files:
+            shutil.copyfile(add_file, survey.path(os.path.basename(add_file)))
+
+        print _("Running %s now twice to generate the questionnaire.") % defs.latex_engine
+        latex.compile('questionnaire.tex', cwd=survey.path())
+
+        if not os.path.exists(survey.path('questionnaire.pdf')):
+            print _("Error running \"%s\" to compile the LaTeX file.") % defs.latex_engine
+            raise AssertionError('PDF file not generated')
+
+        survey.defs.print_questionnaire_id = False
+        survey.defs.print_survey_id = True
+
+        # Parse qobjects
+        try:
+            sdapsfileparser.parse(survey)
+
+            # for qobject in survey.questionnaire.qobjects:
+            #     qobject.setup.validate()
+
+        except Exception, e:
+            log.error(_("Caught an Exception while parsing the SDAPS file. The current state is:"))
+            print >>sys.stderr, unicode(survey.questionnaire)
+            print >>sys.stderr, "------------------------------------"
+
+            raise e
+
+        # Parse additionalqobjects
+        if additionalqobjects:
+            additionalparser.parse(survey, additionalqobjects)
+
+        # Last but not least calculate the survey id
+        survey.calculate_survey_id()
+
+        if not survey.check_settings():
+            log.error(_("Some combination of options and project properties do not work. Aborted Setup."))
+            shutil.rmtree(survey.path())
+            return 1
+
+        # We need to now rebuild everything so that the correct ID is at the bottom
+        write_latex_override_file(survey)
+        print _("Running %s now twice to generate the questionnaire.") % defs.latex_engine
+        os.remove(survey.path('questionnaire.pdf'))
+        latex.compile('questionnaire.tex', survey.path())
+
+        if not os.path.exists(survey.path('questionnaire.pdf')):
+            print _("Error running \"%s\" to compile the LaTeX file.") % defs.latex_engine
+            raise AssertionError('PDF file not generated')
+
+        # Print the result
+        print survey.title
+
+        for item in survey.info.items():
+            print u'%s: %s' % item
+
+        print unicode(survey.questionnaire)
+
+        log.logfile.open(survey.path('log'))
+
+        survey.save()
+        log.logfile.close()
+    except:
+        log.error(_("An error occured in the setup routine. The survey directory still exists. You can for example check the questionnaire.log file for LaTeX compile errors."))
+        raise
diff --git a/sdaps/setuptex/sdapsfileparser.py b/sdaps/setuptex/sdapsfileparser.py
new file mode 100644
index 0000000..f0a29f3
--- /dev/null
+++ b/sdaps/setuptex/sdapsfileparser.py
@@ -0,0 +1,148 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+from sdaps import log
+import xml.sax
+import zipfile
+import re
+
+from sdaps import model
+from sdaps.utils.latex import latex_to_unicode
+
+QOBJECT_PREFIX = u'QObject'
+ANSWER_PREFIX = u'Answer'
+BOX = u'Box'
+TEXTBOX = u'Textbox'
+
+index_re = re.compile(r'''^(?P<index>(?:[0-9]+\.)+)(?P<string>.*)$''')
+
+
+def get_index_and_string(string):
+    match = index_re.match(string)
+    if match is None:
+        if string.startswith('XAUTO. '):
+            return None, string[7:]
+        return None, string
+
+    string = match.group('string')
+    index = match.group('index')
+    index = index.split('.')[:-1]
+    index = tuple([int(x) for x in index])
+
+    return index, string
+
+def parse(survey):
+
+    sdaps_file = open(survey.path('questionnaire.sdaps'))
+    # the file is encoded in ascii format
+    sdaps_data = sdaps_file.read().decode('utf-8')
+    qobject = None
+    auto_numbering_id = (0,)
+
+    for line in sdaps_data.split('\n'):
+        line = line.strip()
+        if line == "":
+            continue
+        arg, value = line.split('=', 1)
+        arg = arg.strip()
+        value = value.strip()
+        value = latex_to_unicode(value)
+
+        if arg == 'Title':
+            survey.title = value
+        elif arg == 'PrintQuestionnaireId':
+            survey.defs.print_questionnaire_id = bool(int(value))
+        elif arg == 'PrintSurveyId':
+            survey.defs.print_survey_id = bool(int(value))
+        elif arg == 'Pages':
+            survey.questionnaire.page_count = int(value)
+        elif arg == 'CheckMode':
+            survey.defs.checkmode = value
+            assert survey.defs.checkmode in model.survey.valid_checkmodes
+        elif arg == 'GlobalID':
+            survey.global_id = value
+        elif arg == 'GlobalIDLabel':
+            # Ignore for now
+            pass
+        elif arg == 'Duplex':
+            survey.defs.duplex = (value == "True")
+        elif arg == 'Style':
+            survey.defs.style = value
+            assert survey.defs.style in model.survey.valid_styles
+        elif arg == "PageSize":
+            args = value.split(',')
+            args = [arg.strip() for arg in args]
+
+            width, height = [round(float(arg[:-2]) / 72.27 * 25.4, 3) for arg in args]
+
+            survey.defs.paper_width = width
+            survey.defs.paper_height = height
+
+        elif arg.startswith(QOBJECT_PREFIX):
+            index, string = get_index_and_string(value)
+            if index:
+                auto_numbering_id = index + (0,)
+            else:
+                auto_numbering_id = auto_numbering_id[:-1] + (auto_numbering_id[-1] + 1,)
+                index = auto_numbering_id
+
+            qobject_type = arg[len(QOBJECT_PREFIX) + 1:]
+
+            qobject = getattr(model.questionnaire, qobject_type)
+            assert issubclass(qobject, model.questionnaire.QObject)
+            qobject = qobject()
+            survey.questionnaire.add_qobject(qobject, new_id=index)
+            qobject.setup.init()
+
+            qobject.setup.question(string)
+        elif arg.startswith(ANSWER_PREFIX):
+            assert qobject is not None
+
+            answer_type = arg[len(ANSWER_PREFIX) + 1:]
+
+            qobject.setup.answer(value)
+        elif arg == BOX:
+            args = value.split(',')
+            args = [arg.strip() for arg in args]
+
+            boxtype = args[0]
+            # Convert to mm
+            page = int(args[1])
+            x, y, width, height = [float(arg[:-2]) / 72.27 * 25.4 for arg in args[2:6]]
+            y = survey.defs.paper_height - y
+
+            if boxtype == 'Textbox':
+                box = model.questionnaire.Textbox()
+                assert(len(args) == 6)
+            else:
+                box = model.questionnaire.Checkbox()
+                if len(args) == 7:
+                    box.form = args[6]
+                else:
+                    assert(len(args) == 6)
+
+            box.setup.setup(page, x, y, width, height)
+            qobject.setup.box(box)
+        else:
+            # Falltrough, it is some metadata:
+            survey.info[arg] = value
+
+    # Force duplex of for one page questionnaires
+    if survey.questionnaire.page_count == 1:
+        survey.defs.duplex = False
+
diff --git a/sdaps/stamp/__init__.py b/sdaps/stamp/__init__.py
new file mode 100644
index 0000000..3771886
--- /dev/null
+++ b/sdaps/stamp/__init__.py
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import random
+
+import os
+import sys
+import math
+import codecs
+
+from sdaps import model
+from sdaps import log
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+
+def stamp(survey, output_filename, cmdline):
+    # copy questionnaire_ids
+    # get number of sheets to create
+    if cmdline['file'] or cmdline['random'] or cmdline['existing']:
+        if not survey.defs.print_questionnaire_id:
+            log.error(_("You may not specify the number of sheets for this survey. All questionnaires will be identical as the survey has been configured to not use questionnaire IDs for each sheet."))
+            return 1
+
+        if cmdline['existing']:
+            questionnaire_ids = survey.questionnaire_ids
+        elif cmdline['file']:
+            if cmdline['file'] == '-':
+                fd = sys.stdin
+            else:
+                fd = codecs.open(cmdline['file'], 'r', encoding="utf-8")
+
+            questionnaire_ids = list()
+            for line in fd.readlines():
+                # Only strip newline/linefeed not spaces
+                line = line.strip('\n\r')
+
+                # Skip empty lines
+                if line == "":
+                    continue
+
+                questionnaire_ids.append(survey.validate_questionnaire_id(line))
+        else:
+            # Create random IDs
+            max = pow(2, 16)
+            min = max - 50000
+            questionnaire_ids = range(min, max)
+
+            # Remove any id that has already been used.
+            for id in survey.questionnaire_ids:
+                if type(id) != int:
+                    continue
+                questionnaire_ids[id - min] = 0
+
+            questionnaire_ids = [id for id in questionnaire_ids if id > min]
+            random.shuffle(questionnaire_ids)
+            questionnaire_ids = questionnaire_ids[:cmdline['random']]
+    else:
+        if survey.defs.print_questionnaire_id:
+            log.error(_("This survey has been configured to use questionnaire IDs. Each questionnaire will be unique. You need to use on of the options to add new IDs or use the existing ones."))
+            return 1
+
+        questionnaire_ids = None
+
+    if questionnaire_ids is not None and not cmdline['existing']:
+        survey.questionnaire_ids.extend(questionnaire_ids)
+
+    if os.path.exists(survey.path('questionnaire.tex')):
+        # use the LaTeX stamper
+        from sdaps.stamp.latex import create_stamp_pdf
+    else:
+        from sdaps.stamp.generic import create_stamp_pdf
+
+    create_stamp_pdf(survey, output_filename, questionnaire_ids)
+
+    survey.save()
+
diff --git a/sdaps/stamp/generic.py b/sdaps/stamp/generic.py
new file mode 100644
index 0000000..8a92427
--- /dev/null
+++ b/sdaps/stamp/generic.py
@@ -0,0 +1,548 @@
+
+import sys
+
+try:
+    import cStringIO as StringIO
+except:
+    import StringIO
+
+import os
+import subprocess
+import tempfile
+import shutil
+
+import reportlab.pdfgen.canvas
+from reportlab.graphics import renderPDF
+from reportlab.graphics.barcode import createBarcodeDrawing, qr
+from reportlab.graphics.shapes import Drawing
+from reportlab.lib import units
+
+from sdaps import log
+from sdaps import defs
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+mm = units.mm
+
+
+def draw_survey_id(canvas, survey):
+    if 0:
+        assert isinstance(canvas, reportlab.pdfgen.canvas.Canvas)
+
+    pos = survey.defs.get_survey_id_pos()
+
+    canvas.saveState()
+    canvas.setFont(defs.codebox_text_font, defs.codebox_text_font_size)
+    canvas.drawCentredString(pos[3] * mm, pos[4] * mm, _(u'Survey-ID: %i') % survey.survey_id)
+    draw_codebox(canvas, pos[0] * mm, pos[2] * mm, survey.survey_id >> 16)
+    draw_codebox(canvas, pos[1] * mm, pos[2] * mm, survey.survey_id & ((1 << 16) - 1))
+    canvas.restoreState()
+
+
+def draw_questionnaire_id(canvas, survey, questionnaire_id):
+    if 0:
+        assert isinstance(canvas, reportlab.pdfgen.canvas.Canvas)
+
+    pos = survey.defs.get_questionnaire_id_pos()
+
+    canvas.saveState()
+    canvas.setFont(defs.codebox_text_font, defs.codebox_text_font_size)
+    canvas.drawCentredString(pos[3] * mm, pos[4] * mm, _(u'Questionnaire-ID: %i') % questionnaire_id)
+    draw_codebox(canvas, pos[0] * mm, pos[2] * mm, questionnaire_id)
+    draw_codebox(canvas, pos[1] * mm, pos[2] * mm, questionnaire_id)
+    canvas.restoreState()
+
+
+def draw_codebox(canvas, x, y, code):
+    if 0:
+        assert isinstance(canvas, reportlab.pdfgen.canvas.Canvas)
+    size = defs.codebox_step * mm
+    length = defs.codebox_length # 2 Bytes
+    canvas.saveState()
+    canvas.translate(x, y)
+    canvas.rect(0, 0, defs.codebox_width * mm, defs.codebox_height * mm)
+    for i in range(length):
+        if code & (1 << i):
+            canvas.rect((length - i - 1) * size, 0,
+                         size, defs.codebox_height * mm,
+                         stroke=1, fill=1)
+    canvas.restoreState()
+
+
+def draw_corner_marks(survey, canvas):
+    if 0:
+        assert isinstance(canvas, reportlab.pdfgen.canvas.Canvas)
+
+    length = defs.corner_mark_length
+    x, y = (defs.corner_mark_left, defs.corner_mark_top)
+    canvas.line(x * mm, y * mm, (x + length) * mm, y * mm)
+    canvas.line(x * mm, y * mm, x * mm, (y + length) * mm)
+
+    x, y = (survey.defs.paper_width - defs.corner_mark_right, defs.corner_mark_top)
+    canvas.line(x * mm, y * mm, (x - length) * mm, y * mm)
+    canvas.line(x * mm, y * mm, x * mm, (y + length) * mm)
+
+    x, y = (defs.corner_mark_left, survey.defs.paper_height - defs.corner_mark_bottom)
+    canvas.line(x * mm, y * mm, (x + length) * mm, y * mm)
+    canvas.line(x * mm, y * mm, x * mm, (y - length) * mm)
+
+    x, y = (survey.defs.paper_width - defs.corner_mark_right, survey.defs.paper_height - defs.corner_mark_bottom)
+    canvas.line(x * mm, y * mm, (x - length) * mm, y * mm)
+    canvas.line(x * mm, y * mm, x * mm, (y - length) * mm)
+
+
+# top left, top right, bottom left, bottom right
+corners = [
+    [0, 1, 1, 1],
+    [1, 1, 0, 0],
+    [1, 0, 1, 1],
+    [1, 0, 1, 0],
+    [1, 0, 0, 0],
+    [0, 0, 0, 1],
+]
+
+
+def draw_corner_boxes(survey, canvas, page):
+    if 0:
+        assert isinstance(canvas, reportlab.pdfgen.canvas.Canvas)
+
+    width = defs.corner_box_width
+    height = defs.corner_box_height
+    padding = defs.corner_box_padding
+
+    corner_boxes_positions = [
+        (defs.corner_mark_left + padding,
+         defs.corner_mark_top + padding),
+        (survey.defs.paper_width - defs.corner_mark_right - padding - width,
+         defs.corner_mark_top + padding),
+        (defs.corner_mark_left + padding,
+         survey.defs.paper_height - defs.corner_mark_bottom - padding - height),
+        (survey.defs.paper_width - defs.corner_mark_right - padding - width,
+         survey.defs.paper_height - defs.corner_mark_bottom - padding - height)
+    ]
+
+    for i in xrange(4):
+        x, y = corner_boxes_positions[i]
+        canvas.rect(x * mm, y * mm, width * mm, height * mm, fill=corners[page][i])
+
+
+# CODE 128 support
+
+def draw_code128_questionnaire_id(canvas, survey, id):
+    # Only supports ascii for now (see also defs.py)
+    barcode_value = unicode(id).encode('ascii')
+    barcode = createBarcodeDrawing("Code128",
+                                   value=barcode_value,
+                                   barWidth=defs.code128_barwidth / 25.4 * 72.0,
+                                   height=defs.code128_height / 25.4 * 72.0,
+                                   quiet=False)
+
+    y = survey.defs.paper_height - defs.corner_mark_bottom
+    x = defs.corner_mark_left
+
+    barcode_y = y - defs.code128_vpad - defs.code128_height
+    barcode_x = x + defs.code128_hpad
+
+    # The barcode should be flush left.
+    barcode_x = barcode_x
+
+    renderPDF.draw(barcode, canvas, barcode_x * mm, barcode_y * mm)
+
+    # Label
+    text_x = barcode_x + barcode.width / mm / 2.0
+    text_y = barcode_y + defs.code128_height + 1 + \
+             defs.code128_text_font_size / 72.0 * 25.4 / 2.0
+
+    canvas.saveState()
+    canvas.setFont(defs.code128_text_font, defs.code128_text_font_size)
+    canvas.drawCentredString(text_x * mm, text_y * mm, barcode_value)
+    canvas.restoreState()
+
+
+def draw_code128_global_id(canvas, survey):
+    if survey.global_id is None:
+        raise AssertionError
+
+    # Only allow ascii
+    barcode_value = survey.global_id.encode('ascii')
+
+    barcode = createBarcodeDrawing("Code128",
+                                   value=barcode_value,
+                                   barWidth=defs.code128_barwidth / 25.4 * 72.0,
+                                   height=defs.code128_height / 25.4 * 72.0,
+                                   quiet=False)
+
+    y = survey.defs.paper_height - defs.corner_mark_bottom
+    x = (survey.defs.paper_width - defs.corner_mark_right + defs.corner_mark_left) / 2
+
+    barcode_y = y - defs.code128_vpad - defs.code128_height
+    barcode_x = x
+
+    # Center
+    barcode_x = barcode_x - barcode.width / mm / 2.0
+
+    renderPDF.draw(barcode, canvas, barcode_x * mm, barcode_y * mm)
+
+    # Label
+    text_x = barcode_x + barcode.width / mm / 2.0
+    text_y = barcode_y + defs.code128_height + 1 + defs.code128_text_font_size / 72.0 * 25.4 / 2.0
+
+    canvas.saveState()
+    canvas.setFont(defs.code128_text_font, defs.code128_text_font_size)
+    canvas.drawCentredString(text_x * mm, text_y * mm, barcode_value)
+    canvas.restoreState()
+
+
+def draw_code128_sdaps_info(canvas, survey, page):
+    # The page number is one based here already
+    # The survey_id is a 32bit number, which means we need
+    # 10 decimal digits to encode it, then we need to encode the
+    # the page with at least 3 digits(just in case someone is insane enough
+    # to have a questionnaire with more than 99 pages.
+    # So use 10+4 digits
+
+    barcode_value = "%010d%04d" % (survey.survey_id, page)
+    barcode = createBarcodeDrawing("Code128",
+                                   value=barcode_value,
+                                   barWidth=defs.code128_barwidth / 25.4 * 72.0,
+                                   height=defs.code128_height / 25.4 * 72.0,
+                                   quiet=False)
+
+    y = survey.defs.paper_height - defs.corner_mark_bottom
+    x = survey.defs.paper_width - defs.corner_mark_right
+
+    barcode_y = y - defs.code128_vpad - defs.code128_height
+    barcode_x = x - defs.code128_hpad
+
+    # The barcode should be flush left.
+    barcode_x = barcode_x - barcode.width / mm
+
+    renderPDF.draw(barcode, canvas, barcode_x * mm, barcode_y * mm)
+
+    # Label
+    text_x = barcode_x + barcode.width / mm / 2.0
+    text_y = barcode_y + defs.code128_height + 1 + defs.code128_text_font_size / 72.0 * 25.4 / 2.0
+
+    canvas.saveState()
+    canvas.setFont(defs.code128_text_font, defs.code128_text_font_size)
+    canvas.drawCentredString(text_x * mm, text_y * mm, barcode_value)
+    canvas.restoreState()
+
+
+# QR-Code support
+
+def draw_qr_questionnaire_id(canvas, survey, id):
+    # Only supports ascii for now (see also defs.py)
+    value = unicode(id).encode('ascii')
+
+    y = survey.defs.paper_height - defs.corner_mark_bottom
+    x = defs.corner_mark_left
+
+    qr_code = qr.QrCodeWidget(value, barLevel='H')
+    bounds = qr_code.getBounds()
+
+    width = bounds[2] - bounds[0]
+    height = bounds[3] - bounds[1]
+
+    # Squeeze into the space between corner mark and content
+    size = defs.bottom_page_margin - defs.corner_mark_bottom
+
+    code_y = y
+    code_x = x
+
+    d = Drawing(size*mm, size*mm, transform=[float(size*mm)/width,0,0,-float(size*mm)/height,0,0])
+    d.add(qr_code)
+
+    renderPDF.draw(d, canvas, code_x*mm, code_y*mm)
+
+def draw_qr_global_id(canvas, survey):
+    if survey.global_id is None:
+        raise AssertionError
+
+    # Only allow ascii
+    value = survey.global_id.encode('ascii')
+
+    y = survey.defs.paper_height - defs.corner_mark_bottom
+    x = (survey.defs.paper_width - defs.corner_mark_right + defs.corner_mark_left) / 2
+
+    qr_code = qr.QrCodeWidget(value, barLevel='H')
+    bounds = qr_code.getBounds()
+
+    width = bounds[2] - bounds[0]
+    height = bounds[3] - bounds[1]
+
+    # Squeeze into the space between corner mark and content
+    size = defs.bottom_page_margin - defs.corner_mark_bottom
+
+    code_y = y
+    code_x = x - size / 2.0
+
+    d = Drawing(size*mm, size*mm, transform=[float(size*mm)/width,0,0,-float(size*mm)/height,0,0])
+    d.add(qr_code)
+
+    renderPDF.draw(d, canvas, code_x*mm, code_y*mm)
+
+def draw_qr_sdaps_info(canvas, survey, page):
+    # The page number is one based here already
+    # The survey_id is a 32bit number, which means we need
+    # 10 decimal digits to encode it, then we need to encode the
+    # the page with at least 3 digits(just in case someone is insane enough
+    # to have a questionnaire with more than 99 pages.
+    # So use 10+4 digits
+
+    value = "%010d%04d" % (survey.survey_id, page)
+
+    y = survey.defs.paper_height - defs.corner_mark_bottom
+    x = survey.defs.paper_width - defs.corner_mark_right
+
+    qr_code = qr.QrCodeWidget(value, barLevel='H')
+    bounds = qr_code.getBounds()
+
+    width = bounds[2] - bounds[0]
+    height = bounds[3] - bounds[1]
+
+    # Squeeze into the space between corner mark and content
+    size = defs.bottom_page_margin - defs.corner_mark_bottom
+
+    code_y = y
+    code_x = x - size
+
+    d = Drawing(size*mm, size*mm, transform=[float(size*mm)/width,0,0,-float(size*mm)/height,0,0])
+    d.add(qr_code)
+
+    renderPDF.draw(d, canvas, code_x*mm, code_y*mm)
+
+
+def create_stamp_pdf(survey, output_filename, questionnaire_ids):
+    sheets = 1 if questionnaire_ids is None else len(questionnaire_ids)
+
+    questionnaire_length = survey.questionnaire.page_count
+
+    have_pdftk = False
+    # Test if pdftk is present, if it is we can use it to be faster
+    try:
+        result = subprocess.Popen(['pdftk', '--version'], stdout=subprocess.PIPE)
+        # Just assume pdftk is there, if it was executed sucessfully
+        if result is not None:
+            have_pdftk = True
+    except OSError:
+        pass
+
+    if not have_pdftk:
+        try:
+            import pyPdf
+        except:
+            log.error(_(u'You need to have either pdftk or pyPdf installed. pdftk is the faster method.'))
+            sys.exit(1)
+
+    # Write the "stamp" out to tmp.pdf if are using pdftk.
+    if have_pdftk:
+        stampsfile = file(survey.path('tmp.pdf'), 'wb')
+    else:
+        stampsfile = StringIO.StringIO()
+
+    canvas = \
+        reportlab.pdfgen.canvas.Canvas(stampsfile,
+                                       bottomup=False,
+                                       pagesize=(survey.defs.paper_width * mm,
+                                                 survey.defs.paper_height * mm))
+    # bottomup = False =>(0, 0) is the upper left corner
+
+    print ungettext(u'Creating stamp PDF for %i sheet', u'Creating stamp PDF for %i sheets', sheets) % sheets
+    log.progressbar.start(sheets)
+    for i in range(sheets):
+        if questionnaire_ids is not None:
+            id = questionnaire_ids.pop(0)
+
+        for j in range(questionnaire_length):
+            if survey.defs.style == "classic":
+                draw_corner_marks(survey, canvas)
+                draw_corner_boxes(survey, canvas, j)
+                if not survey.defs.duplex or j % 2:
+                    if questionnaire_ids is not None:
+                        draw_questionnaire_id(canvas, survey, id)
+
+                    if survey.defs.print_survey_id:
+                        draw_survey_id(canvas, survey)
+
+            elif survey.defs.style == "code128":
+                draw_corner_marks(survey, canvas)
+
+                if not survey.defs.duplex or j % 2:
+                    if questionnaire_ids is not None:
+                        draw_code128_questionnaire_id(canvas, survey, id)
+
+                    # Survey ID has to be printed in CODE128 mode, because it
+                    # contains the page number and rotation.
+                    draw_code128_sdaps_info(canvas, survey, j + 1)
+
+                    if survey.global_id is not None:
+                        draw_code128_global_id(canvas, survey)
+
+            elif survey.defs.style == "qr":
+                draw_corner_marks(survey, canvas)
+
+                if not survey.defs.duplex or j % 2:
+                    if questionnaire_ids is not None:
+                        draw_qr_questionnaire_id(canvas, survey, id)
+
+                    # Survey ID has to be printed in QR mode, because it
+                    # contains the page number and rotation.
+                    draw_qr_sdaps_info(canvas, survey, j + 1)
+
+                    if survey.global_id is not None:
+                        draw_qr_global_id(canvas, survey)
+
+            elif survey.defs.style == "custom":
+                # Only draw corner marker
+                draw_corner_marks(survey, canvas)
+
+                pass
+            else:
+                raise AssertionError()
+
+            canvas.showPage()
+        log.progressbar.update(i + 1)
+
+    canvas.save()
+
+    print ungettext(u'%i sheet; %f seconds per sheet', u'%i sheet; %f seconds per sheet', log.progressbar.max_value) % (
+        log.progressbar.max_value,
+        float(log.progressbar.elapsed_time) /
+        float(log.progressbar.max_value)
+    )
+
+    if have_pdftk:
+        stampsfile.close()
+        # Merge using pdftk
+        print _("Stamping using pdftk")
+        tmp_dir = tempfile.mkdtemp()
+
+        if sheets == 1:
+            # Shortcut if we only have one sheet.
+            # In this case form data in the PDF will *not* break, in
+            # the other code path it *will* break.
+            print _(u"pdftk: Overlaying the original PDF with the markings.")
+            subprocess.call(['pdftk',
+                             survey.path('questionnaire.pdf'),
+                             'multistamp',
+                             survey.path('tmp.pdf'),
+                             'output',
+                             output_filename])
+        else:
+            for page in xrange(1, questionnaire_length + 1):
+                print ungettext(u"pdftk: Splitting out page %d of each sheet.", u"pdftk: Splitting out page %d of each sheet.", page) % page
+                args = []
+                args.append('pdftk')
+                args.append(survey.path('tmp.pdf'))
+                args.append('cat')
+                cur = page
+                for i in range(sheets):
+                    args.append('%d' % cur)
+                    cur += questionnaire_length
+                args.append('output')
+                args.append(os.path.join(tmp_dir, 'stamp-%d.pdf' % page))
+
+                subprocess.call(args)
+
+            print _(u"pdftk: Splitting the questionnaire for watermarking.")
+            subprocess.call(['pdftk', survey.path('questionnaire.pdf'),
+                             'dump_data', 'output',
+                             os.path.join(tmp_dir, 'doc_data.txt')])
+            for page in xrange(1, questionnaire_length + 1):
+                subprocess.call(['pdftk', survey.path('questionnaire.pdf'), 'cat',
+                                 '%d' % page, 'output',
+                                 os.path.join(tmp_dir, 'watermark-%d.pdf' % page)])
+
+            if sheets == 1:
+                for page in xrange(1, questionnaire_length + 1):
+                    print ungettext(u"pdftk: Watermarking page %d of all sheets.", u"pdftk: Watermarking page %d of all sheets.", page) % page
+                    subprocess.call(['pdftk',
+                                     os.path.join(tmp_dir, 'stamp-%d.pdf' % page),
+                                     'background',
+                                     os.path.join(tmp_dir, 'watermark-%d.pdf' % page),
+                                     'output',
+                                     os.path.join(tmp_dir, 'watermarked-%d.pdf' % page)])
+            else:
+                for page in xrange(1, questionnaire_length + 1):
+                    print ungettext(u"pdftk: Watermarking page %d of all sheets.", u"pdftk: Watermarking page %d of all sheets.", page) % page
+                    subprocess.call(['pdftk',
+                                     os.path.join(tmp_dir, 'stamp-%d.pdf' % page),
+                                     'background',
+                                     os.path.join(tmp_dir, 'watermark-%d.pdf' % page),
+                                     'output',
+                                     os.path.join(tmp_dir, 'watermarked-%d.pdf' % page)])
+
+            args = []
+            args.append('pdftk')
+            for page in xrange(1, questionnaire_length + 1):
+                char = chr(ord('A') + page - 1)
+                args.append('%s=' % char + os.path.join(tmp_dir, 'watermarked-%d.pdf' % page))
+
+            args.append('cat')
+
+            for i in range(sheets):
+                for page in xrange(1, questionnaire_length + 1):
+                    char = chr(ord('A') + page - 1)
+                    args.append('%s%d' % (char, i + 1))
+
+            args.append('output')
+            args.append(os.path.join(tmp_dir, 'final.pdf'))
+            print _(u"pdftk: Assembling everything into the final PDF.")
+            subprocess.call(args)
+
+            subprocess.call(['pdftk', os.path.join(tmp_dir, 'final.pdf'),
+                             'update_info', os.path.join(tmp_dir, 'doc_data.txt'),
+                             'output', output_filename])
+
+        # Remove tmp.pdf
+        os.unlink(survey.path('tmp.pdf'))
+        # Remove all the temporary files
+        shutil.rmtree(tmp_dir)
+
+    else:
+        # Merge using pyPdf
+        stamped = pyPdf.PdfFileWriter()
+        stamped._info.getObject().update({
+            pyPdf.generic.NameObject('/Producer'): pyPdf.generic.createStringObject(u'sdaps'),
+            pyPdf.generic.NameObject('/Title'): pyPdf.generic.createStringObject(survey.title),
+        })
+
+        subject = []
+        for key, value in survey.info.iteritems():
+            subject.append(u'%(key)s: %(value)s' % {'key': key, 'value': value})
+        subject = u'\n'.join(subject)
+
+        stamped._info.getObject().update({
+            pyPdf.generic.NameObject('/Subject'): pyPdf.generic.createStringObject(subject),
+        })
+
+        stamps = pyPdf.PdfFileReader(stampsfile)
+
+        del stampsfile
+
+        questionnaire = pyPdf.PdfFileReader(
+            file(survey.path('questionnaire.pdf'), 'rb')
+        )
+
+        print _(u'Stamping using pyPdf. For faster stamping, install pdftk.')
+        log.progressbar.start(sheets)
+
+        for i in range(sheets):
+            for j in range(questionnaire_length):
+                s = stamps.getPage(i * questionnaire_length + j)
+                if not have_pdftk:
+                    q = questionnaire.getPage(j)
+                    s.mergePage(q)
+                stamped.addPage(s)
+            log.progressbar.update(i + 1)
+
+        stamped.write(open(output_filename, 'wb'))
+
+        print ungettext(u'%i sheet; %f seconds per sheet', u'%i sheet; %f seconds per sheet',
+                        log.progressbar.max_value) % (
+                            log.progressbar.max_value,
+                            float(log.progressbar.elapsed_time) /
+                            float(log.progressbar.max_value))
+
diff --git a/sdaps/stamp/latex.py b/sdaps/stamp/latex.py
new file mode 100644
index 0000000..1b8a128
--- /dev/null
+++ b/sdaps/stamp/latex.py
@@ -0,0 +1,57 @@
+
+import sys
+import os
+import tempfile
+import shutil
+
+from sdaps import log
+from sdaps import paths
+from sdaps import defs
+
+from sdaps.utils import latex
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+def tex_quote_braces(string):
+    return string.replace('{', '\\{').replace('}', '\\}')
+
+def create_stamp_pdf(survey, output_filename, questionnaire_ids):
+
+    if questionnaire_ids is None:
+        log.warn(_("There should be no need to stamp a SDAPS Project that uses LaTeX and does not have different questionnaire IDs printed on each sheet.\nI am going to do so anyways."))
+
+    # Temporary directory for TeX files.
+    tmpdir = tempfile.mkdtemp()
+
+    try:
+        # Similar to setuptex/setup.py, but we also set questionnaire IDs
+        latex_override = open(os.path.join(tmpdir, 'sdaps.opt'), 'w')
+        latex_override.write('% This file exists to force the latex document into "final" mode.\n')
+        latex_override.write('% It is parsed after the setup phase of the SDAPS class.\n\n')
+        latex_override.write('\setcounter{surveyidlshw}{%i}\n' % (survey.survey_id % (2 ** 16)))
+        latex_override.write('\setcounter{surveyidmshw}{%i}\n' % (survey.survey_id / (2 ** 16)))
+        latex_override.write('\def\surveyid{%i}\n' % (survey.survey_id))
+        latex_override.write('\def\globalid{%s}\n' % (tex_quote_braces(survey.global_id)) if survey.global_id is not None else '')
+        latex_override.write('\\@STAMPtrue\n')
+        latex_override.write('\\@PAGEMARKtrue\n')
+        latex_override.write('\\@sdaps at draftfalse\n')
+        if questionnaire_ids is not None:
+            quoted_ids = [tex_quote_braces(str(id)) for id in questionnaire_ids]
+            latex_override.write('\def\questionnaireids{{%s}}\n' % '},{'.join(quoted_ids))
+        latex_override.close()
+
+        print _("Running %s now twice to generate the stamped questionnaire.") % defs.latex_engine
+        latex.compile('questionnaire.tex', tmpdir, inputs=[os.path.abspath(survey.path())])
+
+        if not os.path.exists(os.path.join(tmpdir, 'questionnaire.pdf')):
+            log.error(_("Error running \"%s\" to compile the LaTeX file.") % defs.latex_engine)
+            raise AssertionError('PDF file not generated')
+
+        shutil.move(os.path.join(tmpdir, 'questionnaire.pdf'), output_filename)
+
+    except:
+        log.error(_("An error occured during creation of the report. Temporary files left in '%s'." % tmpdir))
+
+        raise
+
+    shutil.rmtree(tmpdir)
diff --git a/sdaps/surface.py b/sdaps/surface.py
new file mode 100644
index 0000000..59ca234
--- /dev/null
+++ b/sdaps/surface.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+u"""
+The surface module adds support for loading the scanned images. It adds a buddy
+to the model.sheet.Image and provides the surface via
+model.sheet.Image.surface.surface at runtime.
+"""
+
+import model
+import image
+
+
+class Image(model.buddy.Buddy):
+    """
+    Buddy to load and cache image data. Do not forget to call :py:meth:`clean`
+    when you are done (ie. before moving to the next sheet) or else the
+    surface will be cached indefinately.
+
+    :ivar surface: The cairo A1 surface, after it has been loaded using :py:meth:`load`.
+    :ivar surface_rgb: The cairo RGB24 surface, after it has been loaded using :py:meth:`load_rgb`.
+    """
+
+    __metaclass__ = model.buddy.Register
+    name = 'surface'
+    obj_class = model.sheet.Image
+
+    def load(self):
+        """Load the A1 cairo surface, which is accessible using the surface
+        attribute.
+        :py:meth`clean` needs to be called when the surface is
+        no longer needed."""
+        self.surface = image.get_a1_from_tiff(
+            self.obj.sheet.survey.path(self.obj.filename),
+            self.obj.tiff_page,
+            True if self.obj.rotated else False
+        )
+
+    def load_rgb(self):
+        """Load the RGB24 cairo surface, which is accessible using the
+        surface_rgb attribute.
+        :py:meth:`clean` needs to be called when the surface is
+        no longer needed."""
+        self.surface_rgb = image.get_rgb24_from_tiff(
+            self.obj.sheet.survey.path(self.obj.filename),
+            self.obj.tiff_page,
+            True if self.obj.rotated else False
+        )
+
+    def load_uncached(self):
+        """Load the A1 surface and directly return it. It will not be cached,
+        so using this function may result repeated reloads from file."""
+        if hasattr(self, 'surface'):
+            return self.surface
+        else:
+            return image.get_a1_from_tiff(
+                self.obj.sheet.survey.path(self.obj.filename),
+                self.obj.tiff_page,
+                True if self.obj.rotated else False)
+
+    def get_size(self):
+        """Read the size of the surface. If the surface is already loaded, it
+        will read the size from that. If it is not loaded, an uncached load will
+        be done, which may be rather slow."""
+        # Load uncached does not check the rgb surface
+        if hasattr(self, 'surface_rgb'):
+            s = self.surface_rgb
+        else:
+            s = self.load_uncached()
+        return s.get_width(), s.get_height()
+
+    def clean(self):
+        """Call when you are done handling a specific sheet to free any cached
+        cairo surface."""
+        if hasattr(self, 'surface'):
+            del self.surface
+        if hasattr(self, 'surface_rgb'):
+            del self.surface_rgb
+
diff --git a/sdaps/template.py b/sdaps/template.py
new file mode 100644
index 0000000..e22e8e0
--- /dev/null
+++ b/sdaps/template.py
@@ -0,0 +1,179 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+u"""
+Template
+========
+
+This modules contains templates for reportlab. These are used to create the
+reports.
+"""
+
+from reportlab import platypus
+from reportlab.lib import styles
+from reportlab.lib import units
+from reportlab.lib import pagesizes
+
+mm = units.mm
+PADDING = 15 * mm
+
+class DocTemplate(platypus.BaseDocTemplate):
+
+    def __init__(self, filename, title, metainfo={}, papersize=pagesizes.A4):
+        platypus.BaseDocTemplate.__init__(
+            self, filename,
+            pagesize=papersize,
+            leftMargin=0,
+            rightMargin=0,
+            topMargin=0,
+            bottomMargin=0,
+            **metainfo
+        )
+            #pageTemplates=[],
+            #showBoundary=0,
+            #allowSplitting=1,
+            #title=None,
+            #author=None,
+            #_pageBreakQuick=1
+        self.addPageTemplates(TitlePageTemplate(papersize, title))
+        self.addPageTemplates(PageTemplate(papersize))
+
+
+class TitlePageTemplate(platypus.PageTemplate):
+
+    def __init__(self, papersize, title):
+        self.title = title
+        frames = [
+            platypus.Frame(
+                PADDING, papersize[1] / 2,
+                papersize[0] - 2*PADDING, papersize[1] / 6,
+                showBoundary=0
+            ),
+            platypus.Frame(
+                PADDING, PADDING,
+                papersize[0] - 2*PADDING, papersize[1] / 2 - PADDING,
+                showBoundary=0
+            ),
+        ]
+        platypus.PageTemplate.__init__(
+            self,
+            id='Title',
+            frames=frames,
+        )
+
+    def beforeDrawPage(self, canvas, document):
+        canvas.saveState()
+        canvas.setFont('Times-Bold', 24)
+        canvas.drawCentredString(
+            document.width / 2.0,
+            document.height - 50 * mm,
+            self.title
+        )
+        canvas.restoreState()
+
+    def afterDrawPage(self, canvas, document):
+        pass
+
+
+class PageTemplate(platypus.PageTemplate):
+
+    def __init__(self, papersize):
+        frames = [
+            platypus.Frame(
+                PADDING, PADDING,
+                papersize[0] - 2*PADDING, papersize[1] - 2*PADDING,
+                showBoundary=0
+            )
+        ]
+        platypus.PageTemplate.__init__(
+            self,
+            id='Normal',
+            frames=frames,
+        )
+        self.frames
+
+    def beforeDrawPage(self, canvas, document):
+        pass
+
+    def afterDrawPage(self, canvas, document):
+        pass
+
+
+stylesheet = dict()
+
+stylesheet['Normal'] = styles.ParagraphStyle(
+    'Normal',
+    fontName='Times-Roman',
+    fontSize=10,
+    leading=14,
+)
+
+stylesheet['Title'] = styles.ParagraphStyle(
+    'Title',
+    parent=stylesheet['Normal'],
+    fontSize=18,
+    leading=22,
+    alignment=styles.TA_CENTER,
+)
+
+
+def story_title(survey, info=dict()):
+    story = [
+        platypus.Paragraph(unicode(line), stylesheet['Title'])
+        for line in survey.title.split('\n')
+    ]
+    story += [
+        platypus.FrameBreak(),
+    ]
+
+    keys = survey.info.keys()
+    if keys:
+        keys.sort()
+        table = [
+            [
+                platypus.Paragraph(unicode(key), stylesheet['Normal']),
+                platypus.Paragraph(unicode(survey.info[key]), stylesheet['Normal'])
+            ]
+            for key in keys
+        ]
+        story += [
+            platypus.Table(table, colWidths=(50 * mm, None)),
+        ]
+    if info:
+        story += [
+            platypus.Spacer(0, 10 * mm)
+        ]
+        keys = info.keys()
+        keys.sort()
+        table = [
+            [
+                platypus.Paragraph(unicode(key), stylesheet['Normal']),
+                platypus.Paragraph(unicode(info[key]), stylesheet['Normal'])
+            ]
+            for key in keys
+        ]
+        story += [
+            platypus.Table(table, colWidths=(50 * mm, None)),
+        ]
+
+    story += [
+        platypus.NextPageTemplate('Normal'),
+        platypus.PageBreak()
+    ]
+    return story
+
diff --git a/sdaps/utils/__init__.py b/sdaps/utils/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/sdaps/utils/barcode.py b/sdaps/utils/barcode.py
new file mode 100644
index 0000000..0f2f3e0
--- /dev/null
+++ b/sdaps/utils/barcode.py
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2012, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+u"""
+This module contains helpers to read barcodes from cairo A1 surfaces.
+"""
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+import cairo
+import zbar
+from sdaps import image
+from sdaps import defs
+
+
+def read_barcode(surface, matrix, x, y, width, height, btype="CODE128"):
+    u"""Tries to read the barcode at the given position"""
+    result = scan(surface, matrix, x, y, width, height, btype)
+
+    if result == None:
+      # Try kfill approach
+      result = scan(surface, matrix, x, y, width, height, btype, True)
+
+    return result
+
+def scan(surface, matrix, x, y, width, height, btype="CODE128", kfill=False):
+    x0, y0 = matrix.transform_point(x, y)
+    x1, y1 = matrix.transform_point(x + width, y + height)
+
+    # Bounding box ...
+    x = min(x0, x1)
+    y = min(y0, y1)
+    width = max(x0, x1) - x
+    height = max(y0, y1) - y
+
+    x, y, width, height = int(x), int(y), int(width), int(height)
+    # Round the width to multiple of 4 pixel, so that the stride will
+    # be good ... hopefully
+    width = width - width % 4 + 4
+
+    # a1 surface for kfill algorithm
+    a1_surface = cairo.ImageSurface(cairo.FORMAT_A1, width, height)
+
+    cr = cairo.Context(a1_surface)
+    cr.set_operator(cairo.OPERATOR_SOURCE)
+    cr.set_source_surface(surface, -x, -y)
+    cr.paint()
+
+    if kfill:
+        pxpermm = (matrix[0] + matrix[3]) / 2
+        barwidth = pxpermm * defs.code128_barwidth
+        barwidth = int(round(barwidth))
+
+        if barwidth <= 3:
+            return
+
+        if barwidth > 6:
+            barwidth = 6
+
+        image.kfill_modified(a1_surface, barwidth)
+
+    # zbar does not understand A1, but it can handle 8bit greyscale ...
+    # We create an inverted A8 mask for zbar, which is the same as a greyscale
+    # image.
+    a8_surface = cairo.ImageSurface(cairo.FORMAT_A8, width, height)
+
+    cr = cairo.Context(a8_surface)
+    cr.set_source_rgba(1, 1, 1, 1)
+    cr.set_operator(cairo.OPERATOR_SOURCE)
+    cr.paint()
+    cr.set_source_rgba(0, 0, 0, 0)
+    cr.mask_surface(a1_surface, 0, 0)
+
+    del cr
+    a8_surface.flush()
+
+    # Now we have pixel data that can be passed to zbar
+    img = zbar.Image()
+    img.format = "Y800"
+    img.data = str(a8_surface.get_data())
+    img.height = height
+    # Use the stride, it should be the same as width, but if it is not, then
+    # it is saner to add a couple of junk pixels instead of getting weird
+    # offsets and missing data.
+    img.width = a8_surface.get_stride()
+
+    scanner = zbar.ImageScanner()
+    scanner.scan(img)
+
+    result = None
+    result_quality = -1
+    for symbol in scanner.results:
+        # Ignore barcodes of the wrong type
+        if str(symbol.type) != btype:
+            continue
+
+        # return the symbol with the highest quality
+        if symbol.quality > result_quality:
+            result = symbol.data
+            result_quality = symbol.quality
+
+
+    # The following can be used to look at the images
+    #rgb_surface = cairo.ImageSurface(cairo.FORMAT_RGB24, width, height)
+    #cr = cairo.Context(rgb_surface)
+    #cr.set_source_rgba(1, 1, 1, 1)
+    #cr.set_operator(cairo.OPERATOR_SOURCE)
+    #cr.paint()
+    #cr.set_source_rgba(0, 0, 0, 0)
+    #cr.mask_surface(a1_surface, 0, 0)
+    #rgb_surface.write_to_png("/tmp/barcode-%03i.png" % barcode)
+    #barcode += 1
+
+    return result
+
+
+
diff --git a/sdaps/utils/create-latexmap.py b/sdaps/utils/create-latexmap.py
new file mode 100755
index 0000000..acd3659
--- /dev/null
+++ b/sdaps/utils/create-latexmap.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python2
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2011, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+u"""
+This file generates the latexmap.py file which is needed
+to convert latex symbols into their unicode counterpart.
+
+Run with create-latexmap.py path/to/utf-8enc.dfu
+"""
+
+import re
+import sys
+
+if len(sys.argv) != 2:
+    print __doc__
+    sys.exit(1)
+
+output = open('latexmap.py', 'w')
+input = open(sys.argv[1])
+
+data = input.read()
+
+regexp = re.compile(r'^\\DeclareUnicodeCharacter\{(?P<unicode>[0-9a-fA-F]{4})\}\{(?P<str>[^\}]+)\}', re.MULTILINE)
+
+mapping = []
+for match in regexp.finditer(data):
+    repr = match.group('str')
+    repr = repr.replace('\\@tabacckludge', '\\')
+    repr = repr.replace('\\', '\\\\').replace('\'', '\\\'')
+    mapping.append('''    u\'%s\': u\'\\u%s\'''' %
+                   (repr,
+                    match.group('unicode')))
+
+output.write('''#This file is auto generated from the latex unicode mapping.
+
+#: Mapping from LaTeX commands to unicode characters.
+mapping = {
+''')
+
+output.write(',\n'.join(mapping))
+output.write('''
+}
+''')
diff --git a/sdaps/utils/exceptions.py b/sdaps/utils/exceptions.py
new file mode 100644
index 0000000..c1d9dff
--- /dev/null
+++ b/sdaps/utils/exceptions.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+class RecognitionError(Exception):
+
+    pass
+
+
diff --git a/sdaps/utils/image.py b/sdaps/utils/image.py
new file mode 100644
index 0000000..bd7318e
--- /dev/null
+++ b/sdaps/utils/image.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, 2011, 2014, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+# Ensure the matrix buddy is loaded
+from sdaps import matrix
+
+from sdaps import image
+
+import cairo
+import math
+
+import os
+import os.path
+
+def get_box_surface(img, filename, x, y, width, height, format=cairo.FORMAT_RGB24):
+    mm_to_px = img.matrix.mm_to_px()
+    x0, y0 = mm_to_px.transform_point(x, y)
+    x1, y1 = mm_to_px.transform_point(x + width, y)
+    x2, y2 = mm_to_px.transform_point(x, y + height)
+    x3, y3 = mm_to_px.transform_point(x + width, y + height)
+
+    x = int(min(x0, x1, x2, x3))
+    y = int(min(y0, y1, y2, y3))
+    width = int(math.ceil(max(x0, x1, x2, x3) - x))
+    height = int(math.ceil(max(y0, y1, y2, y3) - y))
+
+    img = image.get_a1_from_tiff(filename, img.tiff_page, img.rotated if img.rotated else False)
+
+    # Not sure if this is correct for A1 ... probably not
+    assert(format == cairo.FORMAT_RGB24)
+    surf = cairo.ImageSurface(format, width, height)
+    cr = cairo.Context(surf)
+    cr.set_source_rgb(1, 1, 1)
+    cr.paint()
+
+    cr.set_source_surface(img, -x, -y)
+    cr.paint()
+
+    return surf
+
+class ImageWriter:
+    def __init__(self, path, prefix):
+        # I am not qutie sure whether I like the params here.
+        self.count = 0
+        self.path = path
+        self.prefix = prefix
+
+        # Create directory if it does not exists. Assumes only one level
+        # is missing.
+        # In case the prefix contains further path components
+        real_path = os.path.dirname(os.path.join(self.path, self.prefix))
+        if not os.path.exists(real_path):
+            os.mkdir(real_path)
+
+    def output_area(self, img, filename, x, y, width, height):
+        surf = get_box_surface(img, filename, x, y, width, height)
+
+        filename = "%s%04d.png" % (self.prefix, self.count)
+        full_path = os.path.join(self.path, filename)
+
+        surf.write_to_png(full_path)
+
+        self.count += 1
+
+        return filename
+
+    def output_box(self, box):
+        img = box.sheet.get_page_image(box.question.page_number)
+
+        filename = box.question.questionnaire.survey.path(img.filename)
+
+        return self.output_area(img, filename, box.data.x, box.data.y, box.data.width, box.data.height)
+
+    def output_boxes(self, boxes, real=False, padding=5):
+        x1 = 99999
+        y1 = 99999
+        x2 = 0
+        y2 = 0
+        img = boxes[0].sheet.get_page_image(boxes[0].question.page_number)
+        filename = boxes[0].question.questionnaire.survey.path(img.filename)
+
+        for box in boxes:
+            if real:
+                pos = box.data
+            else:
+                pos = box
+            x1 = min(x1, pos.x)
+            y1 = min(y1, pos.y)
+
+            x2 = max(x2, pos.x + pos.width)
+            y2 = max(y2, pos.y + pos.height)
+
+        x1 -= padding
+        y1 -= padding
+        x2 += padding
+        y2 += padding
+
+        return self.output_area(img, filename, x1, y1, x2 - x1, y2 - y1)
+
+
diff --git a/sdaps/utils/latex.py b/sdaps/utils/latex.py
new file mode 100644
index 0000000..9adedf0
--- /dev/null
+++ b/sdaps/utils/latex.py
@@ -0,0 +1,125 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, 2013, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+from sdaps import log
+import re
+
+from sdaps import defs
+import subprocess
+import os
+
+try:
+    from sdaps.utils.latexmap import mapping
+except ImportError:
+    mapping = {}
+    log.warning(_(u'The latex character map is missing! Please build it using the supplied tool (create-latexmap.py).'))
+
+# Add some more mappings
+# NBSP
+mapping[u'~'] = u' '
+
+
+re_latex_to_unicode_mapping = {}
+for token, replacement in mapping.iteritems():
+    regexp = re.compile(u'%s(?=^w|})' % re.escape(token))
+    re_latex_to_unicode_mapping[regexp] = replacement
+
+# Regular expressions don't work really, but we replace a single string anyways
+unicode_to_latex_mapping = {}
+for token, replacement in mapping.iteritems():
+    unicode_to_latex_mapping[replacement] = u"{%s}" % token
+
+
+def latex_to_unicode(string):
+    string = unicode(string)
+    for regexp, replacement in re_latex_to_unicode_mapping.iteritems():
+        string, count = regexp.subn(replacement, string)
+
+    def ret_char(match):
+        return match.group('char')
+    string, count = re.subn(r'\\IeC {(?P<char>.*?)}', ret_char, string)
+    return string
+
+def unicode_to_latex(string):
+    string = unicode(string)
+    for char, replacement in unicode_to_latex_mapping.iteritems():
+        string = string.replace(char, replacement)
+
+    # Ensure only ASCII characters are left
+    return string.encode('ascii')
+
+# This is a list, because the order is relevant!
+ascii_to_latex = [
+    (u'{', u'\\{'),
+    (u'}', u'\\}'),
+    (u'\\', u'{\\textbackslash}'),
+    (u'%', u'\\%'),
+    (u'$', u'\\$'),
+    (u'_', u'\\_'),
+    (u'|', u'{\\textbar}'),
+    (u'>', u'{\\textgreater}'),
+    (u'<', u'{\\textless}'),
+    (u'&', u'\\&'),
+    (u'#', u'\#'),
+    (u'^', u'\\^{}'),
+    (u'~', u'\\~{}'),
+    (u'"', u'\\"{}'),
+]
+
+def raw_unicode_to_latex(string):
+    u"""In addition to converting all unicode characters to LaTeX expressions
+    this function also replaces some characters like newlines with their LaTeX
+    equvivalent."""
+
+    # We need to quote any special character (or replace it)
+    for char, replacement in ascii_to_latex:
+        string = string.replace(char, replacement)
+
+    string = unicode_to_latex(string)
+
+    string = unicode(string)
+
+    # Replace many newlines with a paragraph marker
+    string, count = re.subn('\n\n+', u'\u2029', string, flags=re.MULTILINE)
+
+    # Replace single newline with \\+newline
+    string = string.replace('\n', '\\\\\n')
+
+    # And remove the paragraph marker again (insert two newlines)
+    string = string.replace(u'\u2029', '\n\n')
+
+    return string.encode('ascii')
+
+def run_engine(texfile, cwd, inputs=None):
+    def _preexec_fn():
+        if defs.latex_preexec_hook is not None:
+            defs.latex_preexec_hook()
+
+        if inputs:
+            os.environ['TEXINPUTS'] = ':'.join(['.'] + inputs + [''])
+
+    subprocess.call([defs.latex_engine, '-halt-on-error',
+                     '-interaction', 'batchmode', texfile],
+                    cwd=cwd,
+                    preexec_fn=_preexec_fn)
+
+
+def compile(texfile, cwd, inputs=None):
+    run_engine(texfile, cwd, inputs)
+    run_engine(texfile, cwd, inputs)
+
+
diff --git a/sdaps/utils/latexmap.py b/sdaps/utils/latexmap.py
new file mode 100644
index 0000000..9a57286
--- /dev/null
+++ b/sdaps/utils/latexmap.py
@@ -0,0 +1,436 @@
+#This file is auto generated from the latex unicode mapping.
+
+#: Mapping from LaTeX commands to unicode characters.
+mapping = {
+    u'\\textexclamdown': u'\u00A1',
+    u'\\textcent': u'\u00A2',
+    u'\\textsterling': u'\u00A3',
+    u'\\textcurrency': u'\u00A4',
+    u'\\textyen': u'\u00A5',
+    u'\\textbrokenbar': u'\u00A6',
+    u'\\textsection': u'\u00A7',
+    u'\\textasciidieresis': u'\u00A8',
+    u'\\textcopyright': u'\u00A9',
+    u'\\textordfeminine': u'\u00AA',
+    u'\\guillemotleft': u'\u00AB',
+    u'\\textlnot': u'\u00AC',
+    u'\\textregistered': u'\u00AE',
+    u'\\textasciimacron': u'\u00AF',
+    u'\\textdegree': u'\u00B0',
+    u'\\textpm': u'\u00B1',
+    u'\\texttwosuperior': u'\u00B2',
+    u'\\textthreesuperior': u'\u00B3',
+    u'\\textasciiacute': u'\u00B4',
+    u'\\textmu': u'\u00B5',
+    u'\\textparagraph': u'\u00B6',
+    u'\\textperiodcentered': u'\u00B7',
+    u'\\c\\ ': u'\u00B8',
+    u'\\textonesuperior': u'\u00B9',
+    u'\\textordmasculine': u'\u00BA',
+    u'\\guillemotright': u'\u00BB',
+    u'\\textonequarter': u'\u00BC',
+    u'\\textonehalf': u'\u00BD',
+    u'\\textthreequarters': u'\u00BE',
+    u'\\textquestiondown': u'\u00BF',
+    u'\\`A': u'\u00C0',
+    u'\\\'A': u'\u00C1',
+    u'\\^A': u'\u00C2',
+    u'\\~A': u'\u00C3',
+    u'\\"A': u'\u00C4',
+    u'\\r A': u'\u00C5',
+    u'\\AE': u'\u00C6',
+    u'\\c C': u'\u00C7',
+    u'\\`E': u'\u00C8',
+    u'\\\'E': u'\u00C9',
+    u'\\^E': u'\u00CA',
+    u'\\"E': u'\u00CB',
+    u'\\`I': u'\u00CC',
+    u'\\\'I': u'\u00CD',
+    u'\\^I': u'\u00CE',
+    u'\\"I': u'\u00CF',
+    u'\\DH': u'\u00D0',
+    u'\\~N': u'\u00D1',
+    u'\\`O': u'\u00D2',
+    u'\\\'O': u'\u00D3',
+    u'\\^O': u'\u00D4',
+    u'\\~O': u'\u00D5',
+    u'\\"O': u'\u00D6',
+    u'\\texttimes': u'\u00D7',
+    u'\\O': u'\u00D8',
+    u'\\`U': u'\u00D9',
+    u'\\\'U': u'\u00DA',
+    u'\\^U': u'\u00DB',
+    u'\\"U': u'\u00DC',
+    u'\\\'Y': u'\u00DD',
+    u'\\TH': u'\u00DE',
+    u'\\ss': u'\u00DF',
+    u'\\`a': u'\u00E0',
+    u'\\\'a': u'\u00E1',
+    u'\\^a': u'\u00E2',
+    u'\\~a': u'\u00E3',
+    u'\\"a': u'\u00E4',
+    u'\\r a': u'\u00E5',
+    u'\\ae': u'\u00E6',
+    u'\\c c': u'\u00E7',
+    u'\\`e': u'\u00E8',
+    u'\\\'e': u'\u00E9',
+    u'\\^e': u'\u00EA',
+    u'\\"e': u'\u00EB',
+    u'\\`\\i': u'\u00EC',
+    u'\\\'\\i': u'\u00ED',
+    u'\\^\\i': u'\u00EE',
+    u'\\"\\i': u'\u00EF',
+    u'\\dh': u'\u00F0',
+    u'\\~n': u'\u00F1',
+    u'\\`o': u'\u00F2',
+    u'\\\'o': u'\u00F3',
+    u'\\^o': u'\u00F4',
+    u'\\~o': u'\u00F5',
+    u'\\"o': u'\u00F6',
+    u'\\textdiv': u'\u00F7',
+    u'\\o': u'\u00F8',
+    u'\\`u': u'\u00F9',
+    u'\\\'u': u'\u00FA',
+    u'\\^u': u'\u00FB',
+    u'\\"u': u'\u00FC',
+    u'\\\'y': u'\u00FD',
+    u'\\th': u'\u00FE',
+    u'\\"y': u'\u00FF',
+    u'\\u A': u'\u0102',
+    u'\\u a': u'\u0103',
+    u'\\k A': u'\u0104',
+    u'\\k a': u'\u0105',
+    u'\\\'C': u'\u0106',
+    u'\\\'c': u'\u0107',
+    u'\\v C': u'\u010C',
+    u'\\v c': u'\u010D',
+    u'\\v D': u'\u010E',
+    u'\\v d': u'\u010F',
+    u'\\DJ': u'\u0110',
+    u'\\dj': u'\u0111',
+    u'\\k E': u'\u0118',
+    u'\\k e': u'\u0119',
+    u'\\v E': u'\u011A',
+    u'\\v e': u'\u011B',
+    u'\\u G': u'\u011E',
+    u'\\u g': u'\u011F',
+    u'\\.I': u'\u0130',
+    u'\\i': u'\u0131',
+    u'\\IJ': u'\u0132',
+    u'\\ij': u'\u0133',
+    u'\\\'L': u'\u0139',
+    u'\\\'l': u'\u013A',
+    u'\\v L': u'\u013D',
+    u'\\v l': u'\u013E',
+    u'\\L': u'\u0141',
+    u'\\l': u'\u0142',
+    u'\\\'N': u'\u0143',
+    u'\\\'n': u'\u0144',
+    u'\\v N': u'\u0147',
+    u'\\v n': u'\u0148',
+    u'\\NG': u'\u014A',
+    u'\\ng': u'\u014B',
+    u'\\H O': u'\u0150',
+    u'\\H o': u'\u0151',
+    u'\\OE': u'\u0152',
+    u'\\oe': u'\u0153',
+    u'\\\'R': u'\u0154',
+    u'\\\'r': u'\u0155',
+    u'\\v R': u'\u0158',
+    u'\\v r': u'\u0159',
+    u'\\\'S': u'\u015A',
+    u'\\\'s': u'\u015B',
+    u'\\c S': u'\u015E',
+    u'\\c s': u'\u015F',
+    u'\\v S': u'\u0160',
+    u'\\v s': u'\u0161',
+    u'\\c T': u'\u0162',
+    u'\\c t': u'\u0163',
+    u'\\v T': u'\u0164',
+    u'\\v t': u'\u0165',
+    u'\\r U': u'\u016E',
+    u'\\r u': u'\u016F',
+    u'\\H U': u'\u0170',
+    u'\\H u': u'\u0171',
+    u'\\"Y': u'\u0178',
+    u'\\\'Z': u'\u0179',
+    u'\\\'z': u'\u017A',
+    u'\\.Z': u'\u017B',
+    u'\\.z': u'\u017C',
+    u'\\v Z': u'\u017D',
+    u'\\v z': u'\u017E',
+    u'\\textflorin': u'\u0192',
+    u'\\textasciicircum': u'\u02C6',
+    u'\\textasciicaron': u'\u02C7',
+    u'\\textasciitilde': u'\u02DC',
+    u'\\textasciibreve': u'\u02D8',
+    u'\\textacutedbl': u'\u02DD',
+    u'\\`\\CYRE': u'\u0400',
+    u'\\CYRYO': u'\u0401',
+    u'\\CYRDJE': u'\u0402',
+    u'\\`\\CYRG': u'\u0403',
+    u'\\CYRIE': u'\u0404',
+    u'\\CYRDZE': u'\u0405',
+    u'\\CYRII': u'\u0406',
+    u'\\CYRYI': u'\u0407',
+    u'\\CYRJE': u'\u0408',
+    u'\\CYRLJE': u'\u0409',
+    u'\\CYRNJE': u'\u040A',
+    u'\\CYRTSHE': u'\u040B',
+    u'\\`\\CYRK': u'\u040C',
+    u'\\`\\CYRI': u'\u040D',
+    u'\\CYRUSHRT': u'\u040E',
+    u'\\CYRDZHE': u'\u040F',
+    u'\\CYRA': u'\u0410',
+    u'\\CYRB': u'\u0411',
+    u'\\CYRV': u'\u0412',
+    u'\\CYRG': u'\u0413',
+    u'\\CYRD': u'\u0414',
+    u'\\CYRE': u'\u0415',
+    u'\\CYRZH': u'\u0416',
+    u'\\CYRZ': u'\u0417',
+    u'\\CYRI': u'\u0418',
+    u'\\CYRISHRT': u'\u0419',
+    u'\\CYRK': u'\u041A',
+    u'\\CYRL': u'\u041B',
+    u'\\CYRM': u'\u041C',
+    u'\\CYRN': u'\u041D',
+    u'\\CYRO': u'\u041E',
+    u'\\CYRP': u'\u041F',
+    u'\\CYRR': u'\u0420',
+    u'\\CYRS': u'\u0421',
+    u'\\CYRT': u'\u0422',
+    u'\\CYRU': u'\u0423',
+    u'\\CYRF': u'\u0424',
+    u'\\CYRH': u'\u0425',
+    u'\\CYRC': u'\u0426',
+    u'\\CYRCH': u'\u0427',
+    u'\\CYRSH': u'\u0428',
+    u'\\CYRSHCH': u'\u0429',
+    u'\\CYRHRDSN': u'\u042A',
+    u'\\CYRERY': u'\u042B',
+    u'\\CYRSFTSN': u'\u042C',
+    u'\\CYREREV': u'\u042D',
+    u'\\CYRYU': u'\u042E',
+    u'\\CYRYA': u'\u042F',
+    u'\\cyra': u'\u0430',
+    u'\\cyrb': u'\u0431',
+    u'\\cyrv': u'\u0432',
+    u'\\cyrg': u'\u0433',
+    u'\\cyrd': u'\u0434',
+    u'\\cyre': u'\u0435',
+    u'\\cyrzh': u'\u0436',
+    u'\\cyrz': u'\u0437',
+    u'\\cyri': u'\u0438',
+    u'\\cyrishrt': u'\u0439',
+    u'\\cyrk': u'\u043A',
+    u'\\cyrl': u'\u043B',
+    u'\\cyrm': u'\u043C',
+    u'\\cyrn': u'\u043D',
+    u'\\cyro': u'\u043E',
+    u'\\cyrp': u'\u043F',
+    u'\\cyrr': u'\u0440',
+    u'\\cyrs': u'\u0441',
+    u'\\cyrt': u'\u0442',
+    u'\\cyru': u'\u0443',
+    u'\\cyrf': u'\u0444',
+    u'\\cyrh': u'\u0445',
+    u'\\cyrc': u'\u0446',
+    u'\\cyrch': u'\u0447',
+    u'\\cyrsh': u'\u0448',
+    u'\\cyrshch': u'\u0449',
+    u'\\cyrhrdsn': u'\u044A',
+    u'\\cyrery': u'\u044B',
+    u'\\cyrsftsn': u'\u044C',
+    u'\\cyrerev': u'\u044D',
+    u'\\cyryu': u'\u044E',
+    u'\\cyrya': u'\u044F',
+    u'\\`\\cyre': u'\u0450',
+    u'\\cyryo': u'\u0451',
+    u'\\cyrdje': u'\u0452',
+    u'\\`\\cyrg': u'\u0453',
+    u'\\cyrie': u'\u0454',
+    u'\\cyrdze': u'\u0455',
+    u'\\cyrii': u'\u0456',
+    u'\\cyryi': u'\u0457',
+    u'\\cyrje': u'\u0458',
+    u'\\cyrlje': u'\u0459',
+    u'\\cyrnje': u'\u045A',
+    u'\\cyrtshe': u'\u045B',
+    u'\\`\\cyrk': u'\u045C',
+    u'\\`\\cyri': u'\u045D',
+    u'\\cyrushrt': u'\u045E',
+    u'\\cyrdzhe': u'\u045F',
+    u'\\CYRYAT': u'\u0462',
+    u'\\cyryat': u'\u0463',
+    u'\\CYRBYUS': u'\u046A',
+    u'\\cyrbyus': u'\u046B',
+    u'\\CYRFITA': u'\u0472',
+    u'\\cyrfita': u'\u0473',
+    u'\\CYRIZH': u'\u0474',
+    u'\\cyrizh': u'\u0475',
+    u'\\C\\CYRIZH': u'\u0476',
+    u'\\C\\cyrizh': u'\u0477',
+    u'\\CYRSEMISFTSN': u'\u048C',
+    u'\\cyrsemisftsn': u'\u048D',
+    u'\\CYRRTICK': u'\u048E',
+    u'\\cyrrtick': u'\u048F',
+    u'\\CYRGUP': u'\u0490',
+    u'\\cyrgup': u'\u0491',
+    u'\\CYRGHCRS': u'\u0492',
+    u'\\cyrghcrs': u'\u0493',
+    u'\\CYRGHK': u'\u0494',
+    u'\\cyrghk': u'\u0495',
+    u'\\CYRZHDSC': u'\u0496',
+    u'\\cyrzhdsc': u'\u0497',
+    u'\\CYRZDSC': u'\u0498',
+    u'\\cyrzdsc': u'\u0499',
+    u'\\CYRKDSC': u'\u049A',
+    u'\\cyrkdsc': u'\u049B',
+    u'\\CYRKVCRS': u'\u049C',
+    u'\\cyrkvcrs': u'\u049D',
+    u'\\CYRKHCRS': u'\u049E',
+    u'\\cyrkhcrs': u'\u049F',
+    u'\\CYRKBEAK': u'\u04A0',
+    u'\\cyrkbeak': u'\u04A1',
+    u'\\CYRNDSC': u'\u04A2',
+    u'\\cyrndsc': u'\u04A3',
+    u'\\CYRNG': u'\u04A4',
+    u'\\cyrng': u'\u04A5',
+    u'\\CYRPHK': u'\u04A6',
+    u'\\cyrphk': u'\u04A7',
+    u'\\CYRABHHA': u'\u04A8',
+    u'\\cyrabhha': u'\u04A9',
+    u'\\CYRSDSC': u'\u04AA',
+    u'\\cyrsdsc': u'\u04AB',
+    u'\\CYRTDSC': u'\u04AC',
+    u'\\cyrtdsc': u'\u04AD',
+    u'\\CYRY': u'\u04AE',
+    u'\\cyry': u'\u04AF',
+    u'\\CYRYHCRS': u'\u04B0',
+    u'\\cyryhcrs': u'\u04B1',
+    u'\\CYRHDSC': u'\u04B2',
+    u'\\cyrhdsc': u'\u04B3',
+    u'\\CYRTETSE': u'\u04B4',
+    u'\\cyrtetse': u'\u04B5',
+    u'\\CYRCHRDSC': u'\u04B6',
+    u'\\cyrchrdsc': u'\u04B7',
+    u'\\CYRCHVCRS': u'\u04B8',
+    u'\\cyrchvcrs': u'\u04B9',
+    u'\\CYRSHHA': u'\u04BA',
+    u'\\cyrshha': u'\u04BB',
+    u'\\CYRABHCH': u'\u04BC',
+    u'\\cyrabhch': u'\u04BD',
+    u'\\CYRABHCHDSC': u'\u04BE',
+    u'\\cyrabhchdsc': u'\u04BF',
+    u'\\CYRpalochka': u'\u04C0',
+    u'\\U\\CYRZH': u'\u04C1',
+    u'\\U\\cyrzh': u'\u04C2',
+    u'\\CYRKHK': u'\u04C3',
+    u'\\cyrkhk': u'\u04C4',
+    u'\\CYRLDSC': u'\u04C5',
+    u'\\cyrldsc': u'\u04C6',
+    u'\\CYRNHK': u'\u04C7',
+    u'\\cyrnhk': u'\u04C8',
+    u'\\CYRCHLDSC': u'\u04CB',
+    u'\\cyrchldsc': u'\u04CC',
+    u'\\CYRMDSC': u'\u04CD',
+    u'\\cyrmdsc': u'\u04CE',
+    u'\\U\\CYRA': u'\u04D0',
+    u'\\U\\cyra': u'\u04D1',
+    u'\\"\\CYRA': u'\u04D2',
+    u'\\"\\cyra': u'\u04D3',
+    u'\\CYRAE': u'\u04D4',
+    u'\\cyrae': u'\u04D5',
+    u'\\U\\CYRE': u'\u04D6',
+    u'\\U\\cyre': u'\u04D7',
+    u'\\CYRSCHWA': u'\u04D8',
+    u'\\cyrschwa': u'\u04D9',
+    u'\\"\\CYRSCHWA': u'\u04DA',
+    u'\\"\\cyrschwa': u'\u04DB',
+    u'\\"\\CYRZH': u'\u04DC',
+    u'\\"\\cyrzh': u'\u04DD',
+    u'\\"\\CYRZ': u'\u04DE',
+    u'\\"\\cyrz': u'\u04DF',
+    u'\\CYRABHDZE': u'\u04E0',
+    u'\\cyrabhdze': u'\u04E1',
+    u'\\=\\CYRI': u'\u04E2',
+    u'\\=\\cyri': u'\u04E3',
+    u'\\"\\CYRI': u'\u04E4',
+    u'\\"\\cyri': u'\u04E5',
+    u'\\"\\CYRO': u'\u04E6',
+    u'\\"\\cyro': u'\u04E7',
+    u'\\CYROTLD': u'\u04E8',
+    u'\\cyrotld': u'\u04E9',
+    u'\\"\\CYREREV': u'\u04EC',
+    u'\\"\\cyrerev': u'\u04ED',
+    u'\\=\\CYRU': u'\u04EE',
+    u'\\=\\cyru': u'\u04EF',
+    u'\\"\\CYRU': u'\u04F0',
+    u'\\"\\cyru': u'\u04F1',
+    u'\\H\\CYRU': u'\u04F2',
+    u'\\H\\cyru': u'\u04F3',
+    u'\\"\\CYRCH': u'\u04F4',
+    u'\\"\\cyrch': u'\u04F5',
+    u'\\CYRGDSC': u'\u04F6',
+    u'\\cyrgdsc': u'\u04F7',
+    u'\\"\\CYRERY': u'\u04F8',
+    u'\\"\\cyrery': u'\u04F9',
+    u'\\CYRGDSCHCRS': u'\u04FA',
+    u'\\cyrgdschcrs': u'\u04FB',
+    u'\\CYRHHK': u'\u04FC',
+    u'\\cyrhhk': u'\u04FD',
+    u'\\CYRHHCRS': u'\u04FE',
+    u'\\cyrhhcrs': u'\u04FF',
+    u'\\textbaht': u'\u0E3F',
+    u'\\textcompwordmark': u'\u200C',
+    u'\\textendash': u'\u2013',
+    u'\\textemdash': u'\u2014',
+    u'\\textbardbl': u'\u2016',
+    u'\\textquoteleft': u'\u2018',
+    u'\\textquoteright': u'\u2019',
+    u'\\quotesinglbase': u'\u201A',
+    u'\\textquotedblleft': u'\u201C',
+    u'\\textquotedblright': u'\u201D',
+    u'\\quotedblbase': u'\u201E',
+    u'\\textdagger': u'\u2020',
+    u'\\textdaggerdbl': u'\u2021',
+    u'\\textbullet': u'\u2022',
+    u'\\textellipsis': u'\u2026',
+    u'\\textperthousand': u'\u2030',
+    u'\\textpertenthousand': u'\u2031',
+    u'\\guilsinglleft': u'\u2039',
+    u'\\guilsinglright': u'\u203A',
+    u'\\textreferencemark': u'\u203B',
+    u'\\textinterrobang': u'\u203D',
+    u'\\textfractionsolidus': u'\u2044',
+    u'\\textasteriskcentered': u'\u204E',
+    u'\\textdiscount': u'\u2052',
+    u'\\textcolonmonetary': u'\u20A1',
+    u'\\textlira': u'\u20A4',
+    u'\\textnaira': u'\u20A6',
+    u'\\textwon': u'\u20A9',
+    u'\\textdong': u'\u20AB',
+    u'\\texteuro': u'\u20AC',
+    u'\\textpeso': u'\u20B1',
+    u'\\textcelsius': u'\u2103',
+    u'\\textnumero': u'\u2116',
+    u'\\textcircledP': u'\u2117',
+    u'\\textrecipe': u'\u211E',
+    u'\\textservicemark': u'\u2120',
+    u'\\texttrademark': u'\u2122',
+    u'\\textohm': u'\u2126',
+    u'\\textmho': u'\u2127',
+    u'\\textestimated': u'\u212E',
+    u'\\textleftarrow': u'\u2190',
+    u'\\textuparrow': u'\u2191',
+    u'\\textrightarrow': u'\u2192',
+    u'\\textdownarrow': u'\u2193',
+    u'\\textlangle': u'\u2329',
+    u'\\textrangle': u'\u232A',
+    u'\\textblank': u'\u2422',
+    u'\\textvisiblespace': u'\u2423',
+    u'\\textopenbullet': u'\u25E6',
+    u'\\textbigcircle': u'\u25EF',
+    u'\\textmusicalnote': u'\u266A'
+}
diff --git a/sdaps/utils/mimetype.py b/sdaps/utils/mimetype.py
new file mode 100644
index 0000000..c7acc2d
--- /dev/null
+++ b/sdaps/utils/mimetype.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import subprocess
+
+
+def mimetype(filename):
+    u'''return the mimetype of the file as string or an error string,
+    if the file does not exist or is not accesible.
+    '''
+    file = subprocess.Popen(
+        ['file', '--brief', '--mime-type', filename],
+        stdout=subprocess.PIPE, stderr=subprocess.PIPE
+    )
+    stdout, stderr = file.communicate()
+    if stdout:
+        stdout = stdout.strip()
+    return stdout
+
diff --git a/sdaps/utils/opencv.py b/sdaps/utils/opencv.py
new file mode 100644
index 0000000..7147430
--- /dev/null
+++ b/sdaps/utils/opencv.py
@@ -0,0 +1,252 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2013, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import cv2
+import numpy as np
+from sdaps import image
+import cairo
+from sdaps import defs
+from sdaps import log
+
+from sdaps.utils.ugettext import ugettext, ungettext
+_ = ugettext
+
+try:
+    from gi.repository import Poppler, Gio
+except:
+    log.warn(_("Cannot convert PDF files as poppler is not installed or usable!"))
+
+def iter_images_and_pages(images):
+    """This function iterates over a images and also the contained pages. As
+    OpenCV is not able to handle multipage TIFF files, we use the SDAPS internal
+    loading method for those."""
+
+    for filename in images:
+        pages = 1
+        is_tiff = False
+        is_pdf = False
+
+        try:
+            # Check whether this is a TIFF file (ie. try to retrieve the page count)
+            pages = image.get_tiff_page_count(filename)
+            is_tiff = True
+        except AssertionError:
+            pass
+
+        if not is_tiff:
+            try:
+                gfile = Gio.File.new_for_path(filename)
+                pdf_doc = Poppler.Document.new_from_gfile(gfile, None, None)
+                pages = pdf_doc.get_n_pages()
+                is_pdf = True
+            except:
+                # Either not PDF/damaged or poppler not installed properly
+                pass
+
+
+        for page in xrange(pages):
+            if is_tiff:
+                # TIFF pages are zero based
+                surf = image.get_rgb24_from_tiff(filename, page, False)
+
+                img = to_opencv(surf)
+
+            elif is_pdf:
+                # Try to retrieve a single fullpage image, if that fails, render
+                # document at 300dpi.
+
+                THRESH = 10 #pt
+
+                pdfpage = pdf_doc.get_page(page)
+                page_width, page_height = pdfpage.get_size()
+
+                images = pdfpage.get_image_mapping()
+                if len(images) == 1 and (
+                        abs(images[0].area.x1) < THRESH and
+                        abs(images[0].area.y1) < THRESH and
+                        abs(images[0].area.x2 - page_width) < THRESH and
+                        abs(images[0].area.y2 - page_height) < THRESH):
+                    # Assume one full page image, and simply use that.
+                    surf = pdfpage.get_image(images[0].image_id)
+
+                else:
+                    # Render page at 300dpi
+                    surf = cairo.ImageSurface(cairo.FORMAT_RGB24, int(300 / 72 * page_width), int(300 / 72 * page_height))
+                    cr = cairo.Context(surf)
+                    cr.scale(300 / 72, 300 / 72)
+                    cr.set_source_rgb(1, 1, 1)
+                    cr.paint()
+
+                    pdfpage.render_for_printing(cr)
+
+                    del cr
+
+                img = to_opencv(surf)
+
+            else:
+                img = cv2.imread(filename)
+
+            yield img, filename, page
+
+def sharpen(img):
+    blured = cv2.GaussianBlur(img, (0,0), 5)
+    img = cv2.addWeighted(img, 1.5, blured, -0.5, 0)
+
+    return img
+
+def to_opencv(surf):
+    width = surf.get_width()
+    height = surf.get_height()
+    stride = surf.get_stride()
+
+    # We need to ensure a sane stride!
+    np_width = stride / 4
+
+    # This converts by doing a copy; first create target numpy array
+    # We need a dummy alpha channel ...
+    target = np.empty((height, np_width), dtype=np.uint32)
+
+    tmp_surf = cairo.ImageSurface.create_for_data(target.data, cairo.FORMAT_RGB24, width, height, stride)
+    cr = cairo.Context(tmp_surf)
+    # Handle A1 surfaces?
+    cr.set_source_surface(surf)
+    cr.paint()
+    del cr
+    tmp_surf.flush()
+    del tmp_surf
+
+    # Now, we need a bit of reshaping
+    img = np.empty((height, width, 3), dtype=np.uint8)
+
+    # order should be BGR
+    img[:,:,2] = 0xff & (target[:,:] >> 16)
+    img[:,:,1] = 0xff & (target[:,:] >> 8)
+    img[:,:,0] = 0xff & target[:,:]
+
+    return img
+
+def to_a1_surf(img):
+    # Assume that x is the second position
+    assert(len(img.strides) == 2)
+    assert(img.strides[1] == 1)
+
+    if img.strides[0] % 4:
+        # Need to reshape, lets just cut off up to 3 pixel
+        target = np.empty((img.shape[0], img.shape[1] - img.shape[1] % 4), dtype=np.uint8)
+        target[:,:] = img[:,:target.shape[1]]
+
+        img = target
+
+    height, width = img.shape
+    surf_a8 = cairo.ImageSurface.create_for_data(img.data, cairo.FORMAT_A8, width, height, img.strides[0])
+
+    surf_a1 = cairo.ImageSurface(cairo.FORMAT_A1, width, height)
+    cr = cairo.Context(surf_a1)
+    cr.set_operator(cairo.OPERATOR_SOURCE)
+    cr.set_source_surface(surf_a8)
+    cr.paint()
+
+    return surf_a1
+
+def ensure_orientation(img, portrait):
+    img_portrait = img.shape[1] <= img.shape[0]
+    if img_portrait != portrait:
+        # Rotate into new array (CV does not like negative strides and such)
+        new = np.empty((img.shape[1],img.shape[0])+img.shape[2:], dtype=img.dtype)
+        new[:] = np.rot90(img)
+        img = new
+
+    return img
+
+def ensure_greyscale(img):
+    if len(img.shape) == 2:
+        # Well, seems to be greyscale/monochrome already
+        return img
+
+    # Average the color samples, and convert back to uint8
+    img = np.average(img, 2)
+    img = np.array(img, dtype=np.uint8)
+
+    return img
+
+def convert_to_monochrome(img, size=201, thresh_adjust=5):
+    img = ensure_greyscale(img)
+
+    img = cv2.adaptiveThreshold(img, 255.0, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, size, thresh_adjust)
+
+    return img
+   
+def save(img, filename):
+    cv2.imwrite(img, filename)
+
+
+def _fallback_matrix(width, height, paper_width, paper_height):
+    """Calcualte a fallback matrix. Basically the same as in matrix.py, should
+    be merged somehow!"""
+    xres = width / float(paper_width)
+    yres = height / float(paper_height)
+
+    # Assume the smaller size fits better ...
+    res = min(xres, yres)
+
+    scan_width = width / res
+    scan_height = height / res
+
+    dx = (paper_width - scan_width) / 2.0
+    dy = (paper_height - scan_height) / 2.0
+
+    matrix = cairo.Matrix()
+
+    # Center the image
+    matrix.translate(dx, dy)
+    matrix.scale(1.0 / res, 1.0 / res)
+
+    matrix.invert()
+
+    return matrix, res
+
+
+def transform_using_corners(img, paper_width, paper_height):
+    surf = to_a1_surf(convert_to_monochrome(img))
+
+    matrix, res = _fallback_matrix(surf.get_width(), surf.get_height(), paper_width, paper_height)
+
+    top_left = image.find_corner_marker(surf, matrix, 1)
+    top_right = image.find_corner_marker(surf, matrix, 2)
+    bottom_right = image.find_corner_marker(surf, matrix, 3)
+    bottom_left = image.find_corner_marker(surf, matrix, 4)
+
+    scale = 1 * res
+
+    x0 = scale * defs.corner_mark_left
+    y0 = scale * defs.corner_mark_top
+    x1 = scale * (paper_width - defs.corner_mark_right)
+    y1 = scale * (paper_height - defs.corner_mark_bottom)
+
+    width, height = int(scale * paper_width), int(scale * paper_height)
+    # Increase width to be a multiple of 4
+    if width % 4:
+        width = width + 4 - width % 4
+
+    transform_matrix = cv2.getPerspectiveTransform(
+        np.array((top_left, top_right, bottom_right, bottom_left), dtype=np.float32),
+        np.array(((x0, y0), (x1, y0), (x1, y1), (x0, y1)), dtype=np.float32))
+
+    transformed = cv2.warpPerspective(img, transform_matrix, dsize=(width, height))
+
+    return transformed
+
diff --git a/sdaps/utils/paper.py b/sdaps/utils/paper.py
new file mode 100644
index 0000000..3ed4d5f
--- /dev/null
+++ b/sdaps/utils/paper.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2013, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+_fallback = "A4", (210., 297.)
+
+def _get_gtk_ppd_papersize(paper=None):
+    try:
+        from gi.repository import Gtk
+    except:
+        return paper, None
+    else:
+        def _find_papersize_by_ppd_name(paper=None):
+            if not paper:
+                return None
+
+            paper = paper.lower()
+            for papersize in Gtk.PaperSize.get_paper_sizes(False):
+                if paper == papersize.get_ppd_name().lower():
+                    return papersize
+
+        papersize = _find_papersize_by_ppd_name(paper)
+        if papersize is None:
+            # Retrieve default paper size
+            paper_name = Gtk.PaperSize.get_default()
+            papersize = Gtk.PaperSize.new(paper_name)
+
+        width = papersize.get_width(Gtk.Unit.MM)
+        height = papersize.get_height(Gtk.Unit.MM)
+
+        return papersize.get_ppd_name(), (width, height)
+
+def get_tex_papersize(paper=None):
+    # Assume that the name is the PPD name in lowercase + 'paper'
+    paper, size = _get_gtk_ppd_papersize(paper)
+
+    if paper is None:
+        paper = _fallback[0]
+
+    return paper.lower() + 'paper'
+
+def get_reportlab_papersize(paper=None):
+    paper, size = _get_gtk_ppd_papersize(paper)
+
+    if size:
+        size = (size[0] / 25.4 * 72.0, size[1] / 25.4 * 72.0)
+
+    if size is None:
+        if paper:
+            from reportlab.lib import pagesizes
+            if hasattr(pagesizes, paper.upper()):
+                size = getattr(pagesizes, paper.upper())
+
+    if size is None:
+        size = (_fallback[1][0] / 25.4 * 72.0, _fallback[1][1] / 25.4 * 72.0)
+
+    return size
diff --git a/sdaps/utils/ugettext.py b/sdaps/utils/ugettext.py
new file mode 100644
index 0000000..c6a317a
--- /dev/null
+++ b/sdaps/utils/ugettext.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright(C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright(C) 2008, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+import gettext
+
+
+def ugettext(string):
+    u'''gettext for unicode objects
+    '''
+    translation = gettext.gettext(string.encode('UTF-8')).decode('UTF-8')
+
+    return translation.split('|', 1)[-1]
+
+
+def ungettext(singular, plural, n):
+    u'''ngettext for unicode objects
+    '''
+    return gettext.ngettext(
+        singular.encode('UTF-8'),
+        plural.encode('UTF-8'),
+        n).decode('UTF-8')
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..07274d5
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,10 @@
+[build]
+icons=False
+help=False
+i18n=True
+
+[build_i18n]
+domain=sdaps
+#desktop_files=[("share/applications", ("data/update-manager.desktop.in",))]
+#schemas_files=[("share/gconf/schemas", ("data/update-manager.schemas.in",))]
+#desktop_files=[("share/sdaps/tex", ('tex/tex_translations.in',))]
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..9ed6042
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,205 @@
+#!/usr/bin/env python2
+# SDAPS - Scripts for data acquisition with paper based surveys
+# Copyright (C) 2008, Christoph Simon <post at christoph-simon.eu>
+# Copyright (C) 2008-2013, Benjamin Berg <benjamin at sipsolutions.net>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+from distutils.core import setup
+from distutils.extension import Extension
+import glob
+import os
+import os.path
+import commands
+import sys
+from DistUtilsExtra.command import *
+import ConfigParser
+
+def pkgconfig(*packages, **kw):
+    flag_map = {'-I': 'include_dirs', '-L': 'library_dirs', '-l': 'libraries', '-D' : 'define_macros'}
+    (status, tokens) = commands.getstatusoutput("pkg-config --libs --cflags %s" % ' '.join(packages))
+    if status != 0:
+        print tokens
+        sys.exit(1)
+
+    for token in tokens.split():
+        type = flag_map.get(token[:2])
+        value = token[2:]
+        if type == 'define_macros':
+            value = tuple(value.split('=', 1))
+        if type is None:
+           value = token
+           type = 'extra_compile_args'
+        kw.setdefault(type, []).append(value)
+    return kw
+
+class sdaps_build_i18n(build_i18n.build_i18n):
+
+    # Hardcoded ...
+    dict_sourcefile = 'tex/tex_translations.in'
+    dict_dir = 'share/sdaps/tex'
+    dict_filename = 'tex_translations'
+
+    def run(self):
+        # run the original code
+        build_i18n.build_i18n.run(self)
+
+        dest_dir = os.path.join('build', self.dict_dir)
+        tex_translations = os.path.join(dest_dir, self.dict_filename)
+
+        # Build the tex_translations file
+        if not os.path.isdir(dest_dir):
+            os.makedirs(dest_dir)
+        cmd = ['intltool-merge', '-d', 'tex/po', self.dict_sourcefile, tex_translations]
+        self.spawn(cmd)
+
+        ###################
+        # Now build the LaTeX dictionaries
+        def extract_key_lang(key):
+            if not key.endswith(']'):
+                return key, None
+            index = key.rfind('[')
+            return key[:index], key[index+1:-1]
+
+        parser = ConfigParser.ConfigParser()
+        parser.read(tex_translations)
+
+        langs = {}
+        keys = set()
+        for k, v in parser.items("translations"):
+            key, lang = extract_key_lang(k)
+            if not key == 'tex-language':
+                keys.add(key)
+                continue
+
+            assert lang not in langs
+            assert v not in langs.items()
+
+            langs[lang] = v
+
+        # Load mapping from unicode to LaTeX command name
+        from sdaps.utils.latex import unicode_to_latex
+
+        dictfiles = []
+        for lang, name in langs.iteritems():
+            print 'building LaTeX dictionary file for language %s (%s)' % (name, lang if lang else 'C')
+            dictfiles.append(os.path.join(dest_dir, 'translator-sdaps-dictionary-%s.dict' % name))
+            f = open(dictfiles[-1], 'w')
+
+            f.write('% This file is auto-generated from gettext translations (.po files).\n')
+            f.write('% The header of the original file follows for reference:\n')
+            f.write('%\n')
+            for line in open(self.dict_sourcefile).readlines():
+                if not line.startswith('#'):
+                    break
+                f.write('%' + line[1:])
+            f.write('%\n\n')
+            f.write('\\ProvidesDictionary{translator-sdaps-dictionary}{%s}\n\n' % name)
+
+            for key in keys:
+                if lang is not None:
+                    k = "%s[%s]" % (key, lang)
+                else:
+                    k = key
+
+                try:
+                    value = parser.get("translations", k)
+                except ConfigParser.NoOptionError:
+                    value = parser.get("translations", key)
+
+                value = value.decode('UTF-8')
+                value = unicode_to_latex(value)
+
+                f.write('\\providetranslation{%s}{%s}\n' % (key, value))
+
+        # And install the dictionary files
+        self.distribution.data_files.append((self.dict_dir, dictfiles))
+
+class sdaps_clean_i18n(clean_i18n.clean_i18n):
+    dict_dir = 'share/sdaps/tex'
+    dict_filename = "tex_translations"
+
+    def run(self):
+        # Remove dictionaries
+
+        directory = os.path.join('build', self.dict_dir)
+        if os.path.isdir(directory):
+            print "removing all LaTeX dictionaries in '%s'" % directory
+            for filename in os.listdir(directory):
+                if filename.startswith('translator-sdaps-dictionary-'):
+                    os.unlink(os.path.join(directory, filename))
+
+        fn = os.path.join('build', self.dict_dir, self.dict_filename)
+        if os.path.exists(fn):
+            os.unlink(fn)
+
+        clean_i18n.clean_i18n.run(self)
+
+setup(name='sdaps',
+      version='1.2.1',
+      description='Scripts for data acquisition with paper-based surveys',
+      url='http://sdaps.sipsolutions.net',
+      author='Benjamin Berg, Christoph Simon',
+      author_email='benjamin at sipsolutions.net, post at christoph-simon.eu',
+      license='GPL-3',
+      long_description="""
+SDAPS is a tool to carry out paper based surveys. You can create machine
+readable questionnaires using LibreOffice and LaTeX. It also provides
+the tools to later analyse the scanned data, and create a report.
+""",
+      packages=['sdaps',
+                'sdaps.add',
+                'sdaps.annotate',
+                'sdaps.boxgallery',
+                'sdaps.cmdline',
+                'sdaps.cover',
+                'sdaps.convert',
+                'sdaps.csvdata',
+                'sdaps.gui',
+                'sdaps.image',
+                'sdaps.model',
+                'sdaps.recognize',
+                'sdaps.reorder',
+                'sdaps.stamp',
+                'sdaps.report',
+                'sdaps.reporttex',
+                'sdaps.setup',
+                'sdaps.setupodt',
+                'sdaps.setuptex',
+                'sdaps.utils'
+      ],
+      package_dir={'sdaps.gui': 'sdaps/gui'},
+      scripts=[
+               'bin/sdaps',
+               ],
+      ext_modules=[Extension('sdaps.image.image',
+                   ['sdaps/image/wrap_image.c', 'sdaps/image/image.c', 'sdaps/image/transform.c', 'sdaps/image/surface.c'],
+                   **pkgconfig('pycairo', 'cairo' ,'glib-2.0', libraries=['tiff']))],
+      data_files=[
+                  ('share/sdaps/ui',
+                   glob.glob("sdaps/gui/*.ui")
+                  ),
+                  ('share/sdaps/tex', glob.glob('tex/*.cls')
+                  ),
+                  ('share/sdaps/tex', glob.glob('tex/*.tex')
+                  ),
+                  ('share/sdaps/tex', glob.glob('tex/*.sty')
+                  ),
+                  ],
+      cmdclass = { "build" : build_extra.build_extra,
+                   "build_i18n" :  sdaps_build_i18n,
+                   "build_help" :  build_help.build_help,
+                   "build_icons" :  build_icons.build_icons,
+                   "clean" : sdaps_clean_i18n }
+     )
diff --git a/test/data/info_files/test-odt b/test/data/info_files/test-odt
new file mode 100644
index 0000000..ec18d82
--- /dev/null
+++ b/test/data/info_files/test-odt
@@ -0,0 +1,24 @@
+[sdaps]
+title = Fachschaft Elektro- und Informationstechnik
+	AG Lernverhalten
+global_id = 
+
+[info]
+Datum = 28.07.2008
+Umfrage = Prüfung ES Sommersemester 2008
+
+[defs]
+# These values are not read back, they exist for information only!
+paper_width = 209.902777778
+paper_height = 297.038888889
+print_questionnaire_id = True
+print_survey_id = False
+style = classic
+duplex = True
+checkmode = checkcorrect
+
+[questionnaire]
+# These values are not read back, they exist for information only!
+page_count = 2
+survey_id = 1554259237
+
diff --git a/test/data/info_files/test-odt-lo35 b/test/data/info_files/test-odt-lo35
new file mode 100644
index 0000000..f3e5c72
--- /dev/null
+++ b/test/data/info_files/test-odt-lo35
@@ -0,0 +1,24 @@
+[sdaps]
+title = Fachschaft Elektro- und Informationstechnik
+	AG Lernverhalten
+global_id = SDAPS!
+
+[info]
+Datum = 28.07.2008
+Umfrage = Prüfung ES Sommersemester 2008
+
+[defs]
+# These values are not read back, they exist for information only!
+paper_width = 209.902777778
+paper_height = 297.038888889
+print_questionnaire_id = True
+print_survey_id = True
+style = code128
+duplex = True
+checkmode = checkcorrect
+
+[questionnaire]
+# These values are not read back, they exist for information only!
+page_count = 2
+survey_id = 2862966460
+
diff --git a/test/data/info_files/test-tex-ids b/test/data/info_files/test-tex-ids
new file mode 100644
index 0000000..c9d0f34
--- /dev/null
+++ b/test/data/info_files/test-tex-ids
@@ -0,0 +1,23 @@
+[sdaps]
+title = Testfragebogen \LaTeX
+global_id = SDAPS
+
+[info]
+Author = Someone
+Umfrage = Testfragebogen
+
+[defs]
+# These values are not read back, they exist for information only!
+paper_width = 210.0
+paper_height = 297.0
+print_questionnaire_id = True
+print_survey_id = True
+style = code128
+duplex = True
+checkmode = checkcorrect
+
+[questionnaire]
+# These values are not read back, they exist for information only!
+page_count = 2
+survey_id = 143062700
+
diff --git a/test/data/info_files/test-tex-ids.2 b/test/data/info_files/test-tex-ids.2
new file mode 100644
index 0000000..ec1c8e8
--- /dev/null
+++ b/test/data/info_files/test-tex-ids.2
@@ -0,0 +1,23 @@
+[sdaps]
+title = Testfragebogen \LaTeX
+global_id = SDAPS
+
+[info]
+Author = Someone
+Umfrage = Testfragebogen
+
+[defs]
+# These values are not read back, they exist for information only!
+paper_width = 210.0
+paper_height = 297.0
+print_questionnaire_id = True
+print_survey_id = True
+style = code128
+duplex = True
+checkmode = checkcorrect
+
+[questionnaire]
+# These values are not read back, they exist for information only!
+page_count = 2
+survey_id = 3751077550
+
diff --git a/test/data/info_files/test-tex-no-ids b/test/data/info_files/test-tex-no-ids
new file mode 100644
index 0000000..720c9c1
--- /dev/null
+++ b/test/data/info_files/test-tex-no-ids
@@ -0,0 +1,24 @@
+[sdaps]
+title = The Title
+global_id = 
+
+[info]
+Author = The Author
+Date = 10.03.2013
+asdf = title
+
+[defs]
+# These values are not read back, they exist for information only!
+paper_width = 210.0
+paper_height = 297.0
+print_questionnaire_id = False
+print_survey_id = True
+style = code128
+duplex = True
+checkmode = checkcorrect
+
+[questionnaire]
+# These values are not read back, they exist for information only!
+page_count = 2
+survey_id = 2829233940
+
diff --git a/test/data/info_files/test-tex-no-ids.2 b/test/data/info_files/test-tex-no-ids.2
new file mode 100644
index 0000000..d9fe0cd
--- /dev/null
+++ b/test/data/info_files/test-tex-no-ids.2
@@ -0,0 +1,24 @@
+[sdaps]
+title = The Title
+global_id = 
+
+[info]
+Author = The Author
+Date = 10.03.2013
+asdf = title
+
+[defs]
+# These values are not read back, they exist for information only!
+paper_width = 210.0
+paper_height = 297.0
+print_questionnaire_id = False
+print_survey_id = True
+style = code128
+duplex = True
+checkmode = checkcorrect
+
+[questionnaire]
+# These values are not read back, they exist for information only!
+page_count = 2
+survey_id = 4199997583
+
diff --git a/test/data/info_files/test-tex-no-ids.3 b/test/data/info_files/test-tex-no-ids.3
new file mode 100644
index 0000000..39eea1d
--- /dev/null
+++ b/test/data/info_files/test-tex-no-ids.3
@@ -0,0 +1,24 @@
+[sdaps]
+title = The Title
+global_id = 
+
+[info]
+Author = The Author
+Date = 10.03.2013
+asdf = title
+
+[defs]
+# These values are not read back, they exist for information only!
+paper_width = 210.0
+paper_height = 297.0
+print_questionnaire_id = False
+print_survey_id = True
+style = code128
+duplex = True
+checkmode = checkcorrect
+
+[questionnaire]
+# These values are not read back, they exist for information only!
+page_count = 2
+survey_id = 2684750826
+
diff --git a/test/data/odt-3/debug.internetquestions b/test/data/odt-3/debug.internetquestions
new file mode 100644
index 0000000..2400106
--- /dev/null
+++ b/test/data/odt-3/debug.internetquestions
@@ -0,0 +1,2 @@
+Head	Resultat
+Mark	Welche Note hast Du bekommen?	1	5
diff --git a/test/data/odt-3/debug.odt b/test/data/odt-3/debug.odt
new file mode 100644
index 0000000..d6631cb
Binary files /dev/null and b/test/data/odt-3/debug.odt differ
diff --git a/test/data/odt-3/debug.pdf b/test/data/odt-3/debug.pdf
new file mode 100644
index 0000000..c0f32aa
Binary files /dev/null and b/test/data/odt-3/debug.pdf differ
diff --git a/test/data/odt-3/questionnaire_ids b/test/data/odt-3/questionnaire_ids
new file mode 100644
index 0000000..5bdc339
--- /dev/null
+++ b/test/data/odt-3/questionnaire_ids
@@ -0,0 +1,5 @@
+Hello
+These are
+some IDs for
+testing purposes
+() {} 092312
diff --git a/test/data/odt-5/debug.internetquestions b/test/data/odt-5/debug.internetquestions
new file mode 100644
index 0000000..2400106
--- /dev/null
+++ b/test/data/odt-5/debug.internetquestions
@@ -0,0 +1,2 @@
+Head	Resultat
+Mark	Welche Note hast Du bekommen?	1	5
diff --git a/test/data/odt-5/debug.odt b/test/data/odt-5/debug.odt
new file mode 100644
index 0000000..e4c3dfd
Binary files /dev/null and b/test/data/odt-5/debug.odt differ
diff --git a/test/data/odt-5/debug.pdf b/test/data/odt-5/debug.pdf
new file mode 100644
index 0000000..1ccb976
Binary files /dev/null and b/test/data/odt-5/debug.pdf differ
diff --git a/test/data/odt-5/questionnaire_ids b/test/data/odt-5/questionnaire_ids
new file mode 100644
index 0000000..5bdc339
--- /dev/null
+++ b/test/data/odt-5/questionnaire_ids
@@ -0,0 +1,5 @@
+Hello
+These are
+some IDs for
+testing purposes
+() {} 092312
diff --git a/test/data/odt/debug.internetquestions b/test/data/odt/debug.internetquestions
new file mode 100644
index 0000000..2400106
--- /dev/null
+++ b/test/data/odt/debug.internetquestions
@@ -0,0 +1,2 @@
+Head	Resultat
+Mark	Welche Note hast Du bekommen?	1	5
diff --git a/test/data/odt/debug.odt b/test/data/odt/debug.odt
new file mode 100644
index 0000000..d6631cb
Binary files /dev/null and b/test/data/odt/debug.odt differ
diff --git a/test/data/odt/debug.pdf b/test/data/odt/debug.pdf
new file mode 100644
index 0000000..6bcbcb3
Binary files /dev/null and b/test/data/odt/debug.pdf differ
diff --git a/test/data/odt/debug.tif b/test/data/odt/debug.tif
new file mode 100644
index 0000000..70fbf55
Binary files /dev/null and b/test/data/odt/debug.tif differ
diff --git a/test/data/tex/code128_test_ids b/test/data/tex/code128_test_ids
new file mode 100644
index 0000000..8403f1f
--- /dev/null
+++ b/test/data/tex/code128_test_ids
@@ -0,0 +1,4 @@
+Hello
+12345
+12 hello
+[]()+-{}
diff --git a/test/data/tex/questionnaire_classic.tex b/test/data/tex/questionnaire_classic.tex
new file mode 100644
index 0000000..299aa23
--- /dev/null
+++ b/test/data/tex/questionnaire_classic.tex
@@ -0,0 +1,67 @@
+\documentclass[pdf, style=classic, print_questionnaire_id, english, stamp, pagemark]{sdaps}
+\usepackage{ifxetex}
+\ifxetex
+\else
+  \usepackage[utf8]{inputenc}
+\fi
+\usepackage{ulem}
+\usepackage{babel}
+
+\author{Someone}
+\title{Testfragebogen \LaTeX}
+
+\begin{document}
+  % Will be printed on the report
+  \begin{questionnaire}
+    \addinfo{Umfrage}{Testfragebogen}
+
+    \begin{info}
+      Hier steht ein Informationstext, denn man ganz normal, \textbf{Fett}, \textit{kursiv}, oder \underline{unterstreichen} setzen kann.\par
+      \centering Auch das Zentrieren des Textes ist möglich.
+    \end{info}
+
+    \section{Bewertungsfragen}
+    \singlemark{Einzeln stehende Frage}{sehr gut}{sehr schlecht}
+
+    \begin{markgroup}{Mehrere gebündelte Bewertungsfragen.}
+      \markline{stolz}{sollte man haben}{finde ich unangenehm}
+      \markline{hier ist ein langer text. Lorem ipsum dolor sit amet,
+      consectetur adipiscing elit.}{finde ich gut}{finde ich voll doof}
+      \markline{nett}{wichtig}{unwichtig}
+      \markline{sympatisch}{wichtig}{unwichtig}
+    \end{markgroup}
+
+    \section{Fragen mit Auswahlfeldern}
+    \begin{choicequestion}[4]{Bitte wähle etwas aus oder schreibe in das Textfeld}
+      \choiceitem{erste wahl}
+      \choiceitem{zweite wahl}
+      \choiceitem{dritte wahl}
+      \choiceitem{vierte wahl}
+      \choiceitem{fünfte wahl}
+      \choiceitem{sechste wahl}
+      \choicemulticolitem{2}{längere Auswahl mit langem Text damit mans merkt}
+      \choiceitem{siebte wahl}
+      \choiceitemtext{1cm}{2}{Sonstiges:}
+    \end{choicequestion}
+
+    Alternativ eine Liste von Auswahlfragen mit den gleichen Antworten.
+    \begin{choicegroup}{Welche Software ist für die folgenden Anwendungen am besten geeignet?}
+      \groupaddchoice{\LaTeX}
+      \groupaddchoice{LibreOffice}
+      \groupaddchoice{Microsoft Word}
+
+      \choiceline{Texte schreiben}
+      \choiceline{Mathematische Formeln}
+      \choiceline{Fragebögen erstellen}
+    \end{choicegroup}
+
+    \section{Freitextfelder}
+    Freitextfelder werden automatisch in der höhe Skaliert so dass die Seite voll wird.
+    Es muss aber eine Mindesthöhe angegeben werden.
+    \textbox{2cm}{Hier sollst du was eintragen}
+    \textbox{4cm}{Und mal etwas mit macros \LaTeX}
+
+    Und noch eine andere Frage.
+    \singlemark{Wie gefällt dir dieser Bogen?}{sehr gut}{sehr schlecht}
+  \end{questionnaire}
+\end{document}
diff --git a/test/data/tex/questionnaire_with_ids.tex b/test/data/tex/questionnaire_with_ids.tex
new file mode 100644
index 0000000..3868197
--- /dev/null
+++ b/test/data/tex/questionnaire_with_ids.tex
@@ -0,0 +1,67 @@
+\documentclass[pdf, print_questionnaire_id, globalid=SDAPS, english, stamp, pagemark]{sdaps}
+\usepackage{ifxetex}
+\ifxetex
+\else
+  \usepackage[utf8]{inputenc}
+\fi
+\usepackage{ulem}
+\usepackage{babel}
+
+\author{Someone}
+\title{Testfragebogen \LaTeX}
+
+\begin{document}
+  % Will be printed on the report
+  \begin{questionnaire}
+    \addinfo{Umfrage}{Testfragebogen}
+
+    \begin{info}
+      Hier steht ein Informationstext, denn man ganz normal, \textbf{Fett}, \textit{kursiv}, oder \underline{unterstreichen} setzen kann.\par
+      \centering Auch das Zentrieren des Textes ist möglich.
+    \end{info}
+
+    \section{Bewertungsfragen}
+    \singlemark{Einzeln stehende Frage}{sehr gut}{sehr schlecht}
+
+    \begin{markgroup}{Mehrere gebündelte Bewertungsfragen.}
+      \markline{stolz}{sollte man haben}{finde ich unangenehm}
+      \markline{hier ist ein langer text. Lorem ipsum dolor sit amet,
+      consectetur adipiscing elit.}{finde ich gut}{finde ich voll doof}
+      \markline{nett}{wichtig}{unwichtig}
+      \markline{sympatisch}{wichtig}{unwichtig}
+    \end{markgroup}
+
+    \section{Fragen mit Auswahlfeldern}
+    \begin{choicequestion}[4]{Bitte wähle etwas aus oder schreibe in das Textfeld}
+      \choiceitem{erste wahl}
+      \choiceitem{zweite wahl}
+      \choiceitem{dritte wahl}
+      \choiceitem{vierte wahl}
+      \choiceitem{fünfte wahl}
+      \choiceitem{sechste wahl}
+      \choicemulticolitem{2}{längere Auswahl mit langem Text damit mans merkt}
+      \choiceitem{siebte wahl}
+      \choiceitemtext{1cm}{2}{Sonstiges:}
+    \end{choicequestion}
+
+    Alternativ eine Liste von Auswahlfragen mit den gleichen Antworten.
+    \begin{choicegroup}{Welche Software ist für die folgenden Anwendungen am besten geeignet?}
+      \groupaddchoice{\LaTeX}
+      \groupaddchoice{LibreOffice}
+      \groupaddchoice{Microsoft Word}
+
+      \choiceline{Texte schreiben}
+      \choiceline{Mathematische Formeln}
+      \choiceline{Fragebögen erstellen}
+    \end{choicegroup}
+
+    \section{Freitextfelder}
+    Freitextfelder werden automatisch in der höhe Skaliert so dass die Seite voll wird.
+    Es muss aber eine Mindesthöhe angegeben werden.
+    \textbox{2cm}{Hier sollst du was eintragen}
+    \textbox{4cm}{Und mal etwas mit macros \LaTeX}
+
+    Und noch eine andere Frage.
+    \singlemark{Wie gefällt dir dieser Bogen?}{sehr gut}{sehr schlecht}
+  \end{questionnaire}
+\end{document}
diff --git a/test/data/tex/questionnaire_without_ids.tex b/test/data/tex/questionnaire_without_ids.tex
new file mode 100644
index 0000000..8ee7a70
--- /dev/null
+++ b/test/data/tex/questionnaire_without_ids.tex
@@ -0,0 +1,147 @@
+\documentclass[
+  % Babel language, also used to load translations
+  english,
+  %
+  % If you need it, you can add a custom barcode at the center
+  %globalid=SDAPS,
+  %
+  % And the following adds a per sheet barcode at the bottom left
+  %print_questionnaire_id,
+  %
+  % You can choose between twoside and oneside. twoside is the default, and
+  % requires the document to be printed and scanned in duplex mode.
+  %oneside,
+  %
+  % The following options make sense so that we can get a better feel for the
+  % final look.
+  pagemark,
+  stamp]{sdaps}
+\usepackage[utf8]{inputenc}
+% For demonstration purposes
+\usepackage{multicol}
+
+\author{The Author}
+\title{The Title}
+
+\begin{document}
+  % Everything you do should be done inside the questionnaire environment.
+
+  % If you don't like the default text at the beginning of each questionnaire
+  % you can remove it with the optional [noinfo] parameter for the environment 
+  \begin{questionnaire}
+    % There is a predefined "info" style to hilight some text.
+    \begin{info}
+      Some information here. Nothing special, just adds a line above/below.
+    \end{info}
+
+    % Use \addinfo to add metadata (which is printed on the report later on)
+    \addinfo{Date}{10.03.2013}
+
+    % You can structure the document using sections. You should not use
+    % subsections yourself, as these are used to typeset question text.
+    \section{Range Questions}
+
+    % Lets ask some questions.
+    % \singlemark creates a single range (1-5) question.
+    \singlemark{How often do you use SDAPS?}{never}{daily}
+
+    % Now we would like to ask multiple range questions that are similar. We
+    % can use a markgroup environment to typeset many range questions under
+    % one heading.
+    \begin{markgroup}{What do you think about the following aspects of \LaTeX?}
+      \markline{equation syntax}{bad}{good}
+      \markline{rendered equations}{ugly}{beautiful}
+      \markline{ease of use}{hard}{easy}
+    \end{markgroup}
+
+    \section{Choice Questions}
+    We can also give users a question with predefined choices. Such a list
+    of choices is typesetted using a tabularx environment with equally
+    sized columns. Items can span multiple columns.
+
+    \begin{choicequestion}[3]{Which of the following Open Source
+                              Optical Mark Recognition software
+                              packages have you heard about?}
+      \choiceitem{SDAPS}
+      \choicemulticolitem{2}{Auto Multiple Choice}
+      \choiceitem{QueXF}
+
+      % Insert a text field. The freeform box automatically scales horizontally
+      % The first parameter is the height of the box. The second parameter
+      % is the amount of columns it should span.
+      \choiceitemtext{1.2cm}{2}{Other:}
+    \end{choicequestion}
+
+    % And a more compact way of doing it; similar to markgroup
+    \begin{choicegroup}{Which software do you prefere for the following tasks?}
+      % We have to add the possible choices at the start.
+      \groupaddchoice{\LaTeX}
+      \groupaddchoice{LibreOffice}
+      \groupaddchoice{Microsoft Word}
+      \groupaddchoice{other}
+
+      % After that it is possible to add each question.
+      \choiceline{writing letters}
+      \choiceline{creating tables}
+      \choiceline{typesetting equations}
+    \end{choicegroup}
+
+    \section{Freeform text fields}
+
+    SDAPS will extract freeform textfields such as below as images and put
+    these into reports. SDAPS knows whether there is writing in the box and
+    how large it is.
+
+    % This is a textbox which is at least 2cm high. It will automatically scale
+    % to fill the page.
+    \textbox{2cm}{Do you have any comments?}
+
+    % Force a new page here
+    \newpage
+    \section{Tricks and Features}
+    SDAPS can also use circular checkboxes if you prefere. Or you can use the
+    {\ttfamily multicol} package to create multi-column layouts as is done below.
+
+    % Set checkbox style to be circular
+    \def\checkboxstyle{ellipse}
+
+    \begin{multicols}{2}
+      \singlemark{This is a range question}{lower bound}{upper bound}%
+      % Note that we need the % at the end of the last line to prevent
+      % LaTeX from inserting too much whitespace.
+      \label{somelabel}
+
+      As you can see, this is a multi-column layout. The {\ttfamily markgroup} and
+      {\ttfamily choicegroup} environments may be a bit tight in this mode.
+
+      Lets put some more questions here, just because we can.
+
+      \begin{choicequestion}[1]{A choice question!}
+        \choiceitem{first choice}
+        \choiceitem{second choice}
+        \choiceitem{third choice}
+        \choiceitemtext{1.2cm}{1}{other:}
+      \end{choicequestion}
+
+      \singlemark{Another range question}{lower bound}{upper bound}
+      This text is closer to the question compared to question~\ref{somelabel}
+      because it is not starting a new paragraph.
+
+
+      \textbox{3cm}{And a freeform text field}
+    \end{multicols}
+
+    That's it for the multi-column part; it was fun while it lasted!
+
+    There are some more special commands. You can draw \checkedbox{} crossed
+    checkboxes, \filledbox{} filled or \correctedbox{} filled and crossed ones. Finally there is
+    also the plain \checkbox*{} checkbox using {\ttfamily \textbackslash{}checkbox*}.
+
+    \textbox*{2cm}{And textboxes with a fixed height. This one is exactly 2\,cm high.}
+
+    % Reset checkbox style again.
+    \def\checkboxstyle{box}
+
+  \end{questionnaire}
+\end{document}
+
diff --git a/test/data/tex/test_with_ids.tif b/test/data/tex/test_with_ids.tif
new file mode 100644
index 0000000..fcd7640
Binary files /dev/null and b/test/data/tex/test_with_ids.tif differ
diff --git a/test/data/tex/test_without_ids.tif b/test/data/tex/test_without_ids.tif
new file mode 100644
index 0000000..8660d2f
Binary files /dev/null and b/test/data/tex/test_without_ids.tif differ
diff --git a/test/data/tex/test_without_ids_2.tif b/test/data/tex/test_without_ids_2.tif
new file mode 100644
index 0000000..b0121c1
Binary files /dev/null and b/test/data/tex/test_without_ids_2.tif differ
diff --git a/test/run-test-locally.sh b/test/run-test-locally.sh
new file mode 100755
index 0000000..215dd59
--- /dev/null
+++ b/test/run-test-locally.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+sh "run-test.sh" "../sdaps.py"
diff --git a/test/run-test.sh b/test/run-test.sh
new file mode 100755
index 0000000..cd4c2a3
--- /dev/null
+++ b/test/run-test.sh
@@ -0,0 +1,204 @@
+#!/bin/sh
+
+# Stop if anything goes wrong
+set -e
+
+# Executable
+if [ "x$1" = "x" ]; then
+	SDAPS="sdaps"
+else
+	SDAPS="$1"
+fi
+
+PROJECT="projects/test-odt"
+
+# Create projects dir if it does not exist
+if [ ! -e `dirname $PROJECT` ]; then
+	mkdir -p `dirname $PROJECT`
+fi
+
+# Remove project dir that may exist
+rm -rf "$PROJECT"
+
+# Setup the test project, using the data in "data"
+# By disabling the surveyid and enable the questionnaire id we test more
+# unsual code paths, and we don't have a problem because the survey id
+# changed ...
+"$SDAPS" "$PROJECT" setup --style=classic --print-questionnaire-id --no-print-survey-id "data/odt/debug.odt" "data/odt/debug.pdf" "data/odt/debug.internetquestions"
+
+# Create a cover page in projects/test/cover.pdf
+"$SDAPS" "$PROJECT" cover
+
+# Create 10 unique sheets that can be printed and handed out
+"$SDAPS" "$PROJECT" stamp --random 10
+
+# Dumps a list of all the questionaire IDs (ie. the ids of each of the 10 sheets)
+#"$SDAPS" "$PROJECT" ids
+
+# Import the scanned data. The data has to be a multipage 1bpp tif file.
+"$SDAPS" "$PROJECT" add "data/odt/debug.tif"
+
+# Analyse the image data
+"$SDAPS" "$PROJECT" recognize
+
+# Export to CSV
+"$SDAPS" "$PROJECT" csv export
+
+# And finally, create a report with the result
+"$SDAPS" "$PROJECT" report
+
+###########################################################
+# LibreOffice 3.5 PDF export
+###########################################################
+
+PROJECT="projects/test-odt-lo35"
+
+# Remove project dir that may exist
+rm -rf "$PROJECT"
+
+# Setup the test project, using the data in "data"
+# By disabling the surveyid and enable the questionnaire id we test more
+# unsual code paths, and we don't have a problem because the survey id
+# changed ...
+# Also test code128 style for ODT support
+"$SDAPS" "$PROJECT" setup --style="code128" --global-id="SDAPS!" --print-questionnaire-id "data/odt-3/debug.odt" "data/odt-3/debug.pdf" "data/odt-3/debug.internetquestions"
+"$SDAPS" "$PROJECT" stamp --file "data/odt-3/questionnaire_ids"
+"$SDAPS" "$PROJECT" ids -o "$PROJECT/ids"
+diff "data/odt-3/questionnaire_ids" "$PROJECT/ids"
+
+
+###########################################################
+# LibreOffice 5.2 PDF export
+###########################################################
+
+PROJECT="projects/test-odt-lo52"
+
+# Remove project dir that may exist
+rm -rf "$PROJECT"
+
+# Setup the test project, using the data in "data"
+# By disabling the surveyid and enable the questionnaire id we test more
+# unsual code paths, and we don't have a problem because the survey id
+# changed ...
+# Also test code128 style for ODT support
+"$SDAPS" "$PROJECT" setup --style="code128" --global-id="SDAPS!" --print-questionnaire-id "data/odt-5/debug.odt" "data/odt-5/debug.pdf" "data/odt-5/debug.internetquestions"
+"$SDAPS" "$PROJECT" stamp --file "data/odt-5/questionnaire_ids"
+"$SDAPS" "$PROJECT" ids -o "$PROJECT/ids"
+diff "data/odt-5/questionnaire_ids" "$PROJECT/ids"
+
+
+###########################################################
+# Test Tex with IDs
+###########################################################
+
+PROJECT="projects/test-tex-ids"
+
+# Create projects dir if it does not exist
+if [ ! -e `dirname $PROJECT` ]; then
+	mkdir -p `dirname $PROJECT`
+fi
+
+# Remove project dir that may exist
+rm -rf "$PROJECT"
+
+"$SDAPS" "$PROJECT" setup_tex "data/tex/questionnaire_with_ids.tex"
+
+# Create a cover page in projects/test/cover.pdf
+"$SDAPS" "$PROJECT" cover
+
+# Create sheets with some given IDs
+"$SDAPS" "$PROJECT" stamp -f "data/tex/code128_test_ids"
+"$SDAPS" "$PROJECT" ids -o "$PROJECT/ids"
+diff "data/tex/code128_test_ids" "$PROJECT/ids"
+
+
+
+# Add dummy tiff
+"$SDAPS" "$PROJECT" add "data/tex/test_with_ids.tif"
+
+# Recognize the empty image (ie. the barcodes)
+"$SDAPS" "$PROJECT" recognize
+
+# And finally, create a report with the result
+"$SDAPS" "$PROJECT" report_tex
+
+###########################################################
+# Test Tex with IDs (classic mode)
+###########################################################
+
+PROJECT="projects/test-tex-classic"
+
+# Create projects dir if it does not exist
+if [ ! -e `dirname $PROJECT` ]; then
+	mkdir -p `dirname $PROJECT`
+fi
+
+# Remove project dir that may exist
+rm -rf "$PROJECT"
+
+"$SDAPS" "$PROJECT" setup_tex "data/tex/questionnaire_classic.tex"
+
+# Create 10 unique sheets that can be printed and handed out
+"$SDAPS" "$PROJECT" stamp --random 10
+
+###########################################################
+# Test Tex without IDs
+###########################################################
+
+PROJECT="projects/test-tex-no-ids"
+
+# Create projects dir if it does not exist
+if [ ! -e `dirname $PROJECT` ]; then
+	mkdir -p `dirname $PROJECT`
+fi
+
+# Remove project dir that may exist
+rm -rf "$PROJECT"
+
+"$SDAPS" "$PROJECT" setup_tex "data/tex/questionnaire_without_ids.tex"
+
+# Create a cover page in projects/test/cover.pdf
+"$SDAPS" "$PROJECT" cover
+
+# Run stamp, not neccessary
+"$SDAPS" "$PROJECT" stamp
+
+# Dump some infos
+"$SDAPS" "$PROJECT" info
+"$SDAPS" "$PROJECT" info title
+"$SDAPS" "$PROJECT" info --set title "asdf"
+
+# Add and recognize test data
+"$SDAPS" "$PROJECT" add "data/tex/test_without_ids.tif"
+"$SDAPS" "$PROJECT" add "data/tex/test_without_ids_2.tif"
+"$SDAPS" "$PROJECT" recognize
+
+
+# And finally, create a report with the result
+"$SDAPS" "$PROJECT" report_tex
+
+
+###########################################################
+# Compare info files
+###########################################################
+
+for i in projects/*; do
+  success=0
+  error=0
+  name=`basename "$i"`
+  for j in "data/info_files/$name" data/info_files/$name.*; do
+    if [ ! -f "$j" ]; then
+      continue;
+    fi;
+    # This ignores the title; for whatever reason the \LaTeX
+    # is written out differently with newer latex versions.
+    diff -I '^title' "$j" "$i/info" && success=1 || error=1
+  done
+
+  if [ $success -eq 0 -a $error -ne 0 ]; then
+    # Throw error
+    echo "None of the info files match for $name!"
+    exit 1;
+  fi
+done
+
diff --git a/tex/code128.tex b/tex/code128.tex
new file mode 100644
index 0000000..90835f5
--- /dev/null
+++ b/tex/code128.tex
@@ -0,0 +1,374 @@
+%% Copyright 1996 Petr Olsak.
+%
+% This work may be distributed and/or modified under the
+% conditions of the LaTeX Project Public License, either version 1.3c
+% of this license or (at your option) any later version.
+% The latest version of this license is in
+%   http://www.latex-project.org/lppl.txt
+%
+% The work has been licensed under these terms with permission
+% by Petr Olsak for distribution with SDAPS.
+%
+% The original file follows
+% See http://math.feld.cvut.cz/olsak/
+
+% Macro for conversion of string to barcodes by Code 128 standard
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% March 1996                                 (C) Petr Ol\v{s}\'ak
+
+% For user information see the file test128.tex
+
+% Comments at programmer level are included in this file. First you can
+% read the end of this file: The description of Code 128 standard.
+
+\wlog{**** Macro for barcodes in "Code 128" by (C) Petr Olsak used ****}
+
+% Declarations:
+\newdimen\X            % the module size X,
+\newdimen\bcorr        % the bar correction (see bellow).
+\newdimen\workdimen \newdimen\barheight   % internal variables
+\newtoks\inputtext \newtoks\icode
+\newcount\tempnum \newcount\chnum \newcount\chtotal
+\newif\ifnext \newif\ifchar
+\def\empty{} \def\End{@@end}
+
+% Implicit values:
+\X=.33mm         % The X module width.
+\bcorr=.020mm    % Bar reduction.
+\barheight=1.5cm % The code height.
+
+% First we declare some tables.
+% "\definetable<lab><num> string \relax": Each token from "string" gets a value
+% (spaces are ignored). First token gets value <num>, second <num+1>
+% and so on. It is possible to reconstruct the value by
+% "\csname<lab>\string<token>\endcsname".
+\def\definetable#1#2 {\tempnum=#2 \def\temp{#1}\let\next=\repeatdefine \next}
+\def\repeatdefine#1{\ifx#1\relax \let\next=\relax \else
+  \expandafter\edef\csname\temp\string#1\endcsname{\the\tempnum}%
+  \advance\tempnum by1 \fi \next}
+
+%%%%%%%%%%%%%%%%%%%%% Basic tables for Code 128: %%%%%%%%%%%%%%%%%%%%%%%%%%%
+\definetable:0  % All input characters from Code B:
+  \  ! " \# \$ \% \& ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @
+  A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \\ ] \^ \_ `
+  a b c d e f g h i j k l m n o p q r s t u v w x y z \{ | \} \~ \DEL \relax
+\definetable:64 % Other input characters from Code A:
+  \NUL \SOH \STX \ETX \EOT \ENQ \ACK \BEL \BS \HT \LF \VT \FF \CR \SO \SI
+  \DLE \DCone \DCtwo \DCthree \DCfour \NAK \SYN \ETB \CAN \EM \SUB \ESC
+  \FS \GS \RS \US \relax
+\definetable:0 \
+  \relax    % the \^^M must be the same as \<space>
+\definetable:0 \SP \relax  % the \SP is alternative to \<space>
+\definetable{D:}0 0123456789 \relax  % Digits
+\definetable{B:}0 `abcdefghijklmnopqrstuvwxyz\{\|\}\~ \relax % Only in Code B
+\definetable{A:}0  \NUL \SOH \STX \ETX \EOT \ENQ \ACK \BEL \BS \HT \LF \VT
+  \FF \CR \SO \SI \DLE \DCone \DCtwo \DCthree \DCfour \NAK \SYN \ETB \CAN
+  \EM \SUB \ESC \FS \GS \RS \US \relax % only in code A.
+\def\tableofcode#1{\ifcase#1 % The output characters:
+  212222\or 222122\or 222221\or 121223\or 121322\or % 0-4
+  131222\or 122213\or 122312\or 132212\or 221213\or % 5-9
+  221312\or 231212\or 112232\or 122132\or 122231\or % 10-14
+  113222\or 123122\or 123221\or 223211\or 221132\or % 15-19
+  221231\or 213212\or 223112\or 312131\or 311222\or % 20-24
+  321122\or 321221\or 312212\or 322112\or 322211\or % 25-29
+  212123\or 212321\or 232121\or 111323\or 131123\or % 30-34
+  131321\or 112313\or 132113\or 132311\or 211313\or % 35-39
+  231113\or 231311\or 112133\or 112331\or 132131\or % 40-44
+  113123\or 113321\or 133121\or 313121\or 211331\or % 45-49
+  231131\or 213113\or 213311\or 213131\or 311123\or % 50-54
+  311321\or 331121\or 312113\or 312311\or 332111\or % 55-59
+  314111\or 221411\or 431111\or 111224\or 111422\or % 60-64
+  121124\or 121421\or 141122\or 141221\or 112214\or % 65-69
+  112412\or 122114\or 122411\or 142112\or 142211\or % 70-74
+  241211\or 221114\or 413111\or 241112\or 134111\or % 75-79
+  111242\or 121142\or 121241\or 114212\or 124112\or % 80-84
+  124211\or 411212\or 421112\or 421211\or 212141\or % 85-89
+  214121\or 412121\or 111143\or 111341\or 131141\or % 90-94
+  114113\or 114311\or 411113\or 411311\or 113141\or % 95-99
+  114131\or 311141\or 411131\else 00000\fi }        % 100-102
+\def\startA{211412}
+\def\startB{211214}
+\def\startC{211232}
+\def\stop{23311120}
+
+% Implementations of tests:
+% After "\testchar <lab><token>" the "\ifchar" has following meaning:
+% true, if <token> is in digits, only in A or only in B respectively with
+% <lab> is {D:}, {A:} or {B:}.
+\def\testchar #1#2{\expandafter\ifx\csname#1\string#2\endcsname \relax
+          \charfalse \else \chartrue \fi}
+
+% After "\numofdigits string\stop" is used, the \tempnum register contain
+% the number of digits from first continuosly group of digits from left in
+% "string". If "string" starts with no digit then \tempnum=0.
+\def\numofdigits{\tempnum=0 \let\next=\cyklnumber \cyklnumber}
+\def\cyklnumber#1{\testchar {D:}#1%
+   \ifchar \advance\tempnum by1
+   \else \ifx #1\stop\def\next{}%
+         \else \def\next##1\stop{}\fi
+   \fi \next}
+
+% After "\testnext<lab> string\stop" is used, the "\ifnext" has
+% the following meaning:
+% 1. <lab> is {A:}: "\ifnext" is true, if first character from "string" is
+% only in A code after skip all common characters shared in codeA and B.
+% 2. <lab> is {B:}: "\ifnext" is true, if first character from "string" is
+% only in B code after skip all common characters shared in codeA and B.
+\def\testA{A:}
+\def\testnext#1{\nextfalse
+  \def\tempA{#1}%
+  \ifx\tempA\testA \def\tempB{B:}\else \def\tempB{A:}\fi
+  \let\next=\cyklcontrol \next}
+\def\cyklcontrol#1{%
+   \ifx#1\stop \let\next=\relax
+   \else \testchar \tempB #1%
+         \ifchar \def\next##1\stop{}%
+         \else \testchar \tempA #1%
+               \ifchar \nexttrue \def\next##1\stop{}\fi \fi
+   \fi \next}
+
+% "\addtok \cs" adds the "\cs," into \icode.
+% "\addtoks{string} adds the "string," into \icode. "string" is the number
+% of line in table 1, so we re-calculate the current check sum.
+% "\addchar <token> adds the numerical value of <token> (declared in
+% \definetable:) into \icode. This value is followed by comma too.
+\def\addtok#1{\edef\act{\noexpand\icode={\the\icode\noexpand#1,}}\act}
+\def\addtoks#1{\edef\act{\noexpand\icode={\the\icode#1,}}\act
+  \tempnum=#1 \multiply\tempnum by\chnum
+  \advance\chtotal by\tempnum \advance\chnum by 1\relax}
+\def\addchar#1{\expandafter\ifx\csname:\string#1\endcsname\relax
+  \errmessage{The input token "\string#1" is not included in Code 128
+              table, will ignored}%
+  \else \expandafter\addtoks\expandafter{\csname:\string#1\endcsname}\fi}
+
+% \code{text} first converts the input text into internal representation in
+% \icode. The format of \icode is: "\start,num,num,num,\stop,", where
+% "\start" is one of "\startA" or "\startB" or "\startC". The <num>
+% represents the line of code table (see standard of Code 128 bellow)
+% of output characters. The amount of <num>s is not restricted. The sequence
+% is terminated by "\stop,". See to .log for example of this format.
+%
+% The choice of the start character:
+% If next 4 input characters are digits then \startC
+% else if the next uncommon char is from code A and not from B then \startA
+%      else \startB
+% The "next uncommon char" is first character from left which falls
+% into code A xor code B
+\def\code#1{\inputtext={#1}\wlog{** Code 128 ** input: \the\inputtext}%
+  \icode={}\chnum=1
+  \numofdigits#1\stop % in \tempnum is the number of digits now
+  \ifnum\tempnum>3 \addtok\startC \chtotal=2 \let\Next=\codeC \else
+     \testnext{A:} #1\stop
+     \ifnext \addtok\startA \chtotal=0 \let\Next=\codeA
+     \else \addtok\startB \chtotal=1 \let\Next=\codeB \fi
+  \fi \Next #1\End\End}
+
+% There is mode A. Test to change the mode:
+% If the next 4 input characters are digits and the number of digits are even
+%    then switch to modeC using <codeC>.
+% If the next char is from Code B and not from Code A then:
+%    if the next uncommnon char is from Code B and not from A then
+%         switch to mode B using <codeB>.
+%    else include <SHIFT> and stay in mode A.
+\def\codeA #1#2#3\End{\addchar#1%
+  \numofdigits#2#3\stop
+  \ifnum\tempnum>3 \ifodd\tempnum\else
+      \addtoks{99}\let\Next=\codeC \fi
+  \else \ifx#2\End \let\Next=\finalcode
+        \else \testchar{B:} #2%
+              \ifchar \testnext{B:} #3\stop
+                      \ifnext \addtoks{100}\let\Next=\codeB
+                      \else \addtoks{98}\fi \fi \fi \fi
+  \Next #2#3\End}
+
+% There is mode B. Test to change the mode:
+% If the next 4 input characters are digits and the number of digits are even
+%    then switch to modeC using <codeC>
+% If the next char is from Code A and not from Code B then:
+%    if the next uncommnon char is from Code A and not from B then
+%         switch to mode A using <codeA>.
+%    else include <SHIFT> and stay in mode B.
+\def\codeB #1#2#3\End{\addchar#1%
+  \numofdigits#2#3\stop
+  \ifnum\tempnum>3 \ifodd\tempnum\else
+      \addtoks{99}\let\Next=\codeC \fi
+  \else \ifx#2\End \let\Next=\finalcode
+        \else \testchar{A:} #2%
+              \ifchar \testnext{A:} #3\stop
+                      \ifnext \addtoks{101}\let\Next=\codeA
+                      \else \addtoks{98}\fi \fi \fi \fi
+  \Next #2#3\End}
+
+% There is mode C. Test to change the mode:
+% If not next two chars are digits switch to code A or B by following rule:
+% If the next uncommon char is from Code A and not from B then
+%      switch to mode A using <codeA>
+% else switch to mode B using <codeB>
+\def\codeC #1#2#3#4\End{\addtoks{#1#2}%
+  \ifx#3\End \let\Next=\finalcode
+  \else \testchar{D:} #3%
+        \ifchar \def\temp{#4}%
+                \ifx\temp\empty \switchtoAorB #3\stop
+                \else \separate #4\stop
+                      \edef\act{\noexpand\testchar{D:}\temp}\act
+                      \ifchar
+                      \else \switchtoAorB #3#4\stop \fi \fi
+        \else \switchtoAorB #3#4\stop \fi
+  \fi \Next #3#4\End}
+\def\separate#1#2\stop{\def\temp{#1}}
+\def\switchtoAorB #1\stop{\testnext{A:} #1\stop
+   \ifnext \addtoks{101}\let\Next=\codeA
+   \else \addtoks{100}\let\Next=\codeB \fi}
+
+\def\finalcode\End\End{\addchecksum \addtok\stop \wlog{encoded: \the\icode}%
+  \expandafter\makecode\the\icode}
+
+% \addchecksum adds the check sum into \icode
+\def\addchecksum{\tempnum=\chtotal
+  \divide\tempnum by 103 \multiply\tempnum by 103
+  \advance\chtotal by-\tempnum \addtoks{\the\chtotal}}
+
+% The \makecode converts the \icode from format "\start,num,num,\stop,"
+% into sequence of digits. Each digit repersents the multiple of X module
+% size for bar or space if it is at odd or even position. For example
+% 21141223311120. It means bar of 2X, space 1X, bar 1X, space 4X and so on.
+% This representation of code is stored in macro \internalcode.
+\def\makecode#1,{\let\next=\cyklcode \edef\internalcode{#1}\next}
+\def\cyklcode#1,{%
+  \ifx\stop#1\let\next=\finalmakecode \edef\internalcode{\internalcode\stop}%
+  \else \edef\internalcode{\internalcode\tableofcode{#1}}\fi \next}
+\def\finalmakecode{\wlog{black-white: \internalcode}%
+  \begcode \let\next=\makebars \expandafter\makebars\internalcode}
+
+% \makebars simply makes the \vrules and \kerns of appropriate sizes from
+% \internalcode representation. Each width of \vrule is corrected by \bcorr
+% and opposite for \kern.
+\def\makebars#1#2{\if0#2\let\next=\endcode\fi
+  \workdimen=#1\X \advance\workdimen by-\bcorr \vrule width\workdimen
+  \workdimen=#2\X \advance\workdimen by \bcorr \kern\workdimen
+  \next}
+
+% The begin and end of completed \hbox:
+\def\begcode{\hbox\bgroup\vrule height\barheight width0pt}
+\def\endcode{\egroup}
+
+% User can use \codetext or \codeothertext instead \code:
+\def\codeothertext#1#2{\vbox{\halign{\hfil##\hfil\cr\code{#1}\cr{\tt#2}\cr}}}
+\def\codetext#1{\codeothertext{#1}{#1}}
+
+\endinput %%%%%%%%%%%%%%% End of macros %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+               The description of Code 128 standard
+               ************************************
+
+The characters from input string are converted from left to right
+to so called "output characters" by table 1 (see below). Each output
+character has three bars and spaces. The width of bars and the dimensions
+of spaces between bars are significant. This values are expresed by
+multiples of basic dimension: so called X module size (see \X in macro).
+The multiples varies from 1 to 4. We are using the six digits expression of
+output character. It expressed dimensions of bar, space, bar, space, bar,
+space. For example 122412 means one output character drawn as: 1X bar, 2X
+space, 2X bar, 4X space, 1X bar and 2X space.
+
+The table 1:
+
+num.line  code A    code B    codeC     output character
+-------------------------------------------------------
+  0       [space]    [space]    00        212222
+  1            !         !      01        222122
+  2            "         "      02        222221
+  3            #         #      03        121223
+... and so on ...
+ 63            _         _      63        111224
+ 64         \NUL         `      64        111422
+ 65         \SOH         a      65        121124
+ 66         \STX         b      66        121421
+... and so on ...
+ 93          \GS         }      93        111341
+ 94          \RS         ~      94        131141
+ 95          \US      \DEL      95        114113
+ 96       <FNC3>    <FNC3>      96        113311
+ 97       <FNC2>    <FNC2>      97        411113
+ 98      <SHIFT>   <SHIFT>      98        411311
+ 99      <codeC>   <codeC>      99        113141
+100      <codeB>    <FNC4>   <codeB>      114131
+101       <FNC4>   <codeA>   <codeA>      311141
+102       <FNC1>    <FNC1>    <FNC1>      411131
+
+The whole table is not presented here because you can simply reconstruct it
+from macros.
+
+The columns "code A", "code B", and "code C" inlude all possible input
+characters in input string (excluding "special commands" in lines 96--102
+written in <angle> braces). The special \TeX{} characters must be escaped
+(i.e. user have to write \# and no #). The escaped words (in capitals)
+represents so called "control characters" from ASCII. The meaning of this
+sequences depends on application. The "special commands" in lines 96--102
+written in <angle> braces are not possible in input. They are special for
+decoder. The <SHIFT> and <codeA-C> are used in our macro, but <FNC?> are
+not because they are reserved for special purposes.
+
+The Code B column is whole expressed in \definetable:0 (see in macro). The
+Code A column has the same values in lines 0--63 (capitals, digits and some
+other ASCII characters are included here). Code A differ from Code B in
+lines 64--102. The lowercase letters and another ASCII characters are
+included in "Code B", but control sequences are included in "Code A".
+The different part of "Code A" column is expressed in \definetable:64 (see
+in macro). The "code C" column includes the digits pairs and corresponds to
+number of line in table 1. Two digits in input go to one output character.
+The whole "output character" column is expressed in \tableofcode (see in
+macro).
+
+The "start character" is appended before each barcode. There are three
+types of start character depending on which column of table is used for
+next encoding (so called mode). See macros \startA, \startB and \startC.
+For examlpe: If \startC is used, next output character represents two
+digits in input (codeC mode). If \startB is used, next output character
+represents one ASCII character in input (codeB mode). If \startA is used,
+next output character represents probably the control sequence in input or
+capitals, but not lowercase ASCII (codeA mode).
+
+If some mode for encoding is currently used and next input character is not
+included in appropriate column, the "switching command" is included into
+sequence of output character. The <SHIFT> command switches from mode A to
+B or from B to A only for one next input character and the other input
+characters are coded in the same mode (A or B). The <codeA> command
+switches to codeA mode definitively unless next switch command is used.
+The commands <codeB> and <codeC> makes the same service, but to switch into
+mode B or C respectively. All these commands are expessed in table 1.
+
+It is recomended to chose the start character and switching commands by the
+way, that the resulting length of code is minimised. There are some
+recommendations of this choice. These recommendations are included into our
+macros (see the commnets and macros for \code, \codeA, \codeB and \codeC).
+
+The checksum character is added after the end of input string. Finally,
+the "stop character" is appended after the end of barcode. This character
+has exclusively four bars and not only three. See macro \stop.
+
+The checksum character is calculated from output characters used in the
+code. The number of line in table 1 of each output character is asumed. The
+start character is covered too, but checksum character itself and the
+stop character are not included into calculation. The startA or startB or
+startC characters has its number of line 103 or 104 or 105 respectively. Each
+output character (more exactly its number of line in table 1) is multiplied
+by "weight number" and the total sum is calculated. The weight number of
+start character is one. The weight number of first output characet after
+start is one too. Second character has weight number two, and so on. The
+n-th character has weight number n. The total sum modulo 103 is the line of
+the calculated checksum character.
+
+Example for checksum calculation:
+Input:    123456
+Encoded:  StartC, 12, 34, 56
+Total sum of checksum: 105 + 1*12 + 2*34 + 3*56 = 535
+Modulo 103: 44
+The character from line 44 is apended as checksum.
+The whole encoded code: StartC, 12, 34, 56, 44, Stop
+
+%%% End of file.
+
+
+
+
diff --git a/tex/po/POTFILES.in b/tex/po/POTFILES.in
new file mode 100644
index 0000000..71a10f7
--- /dev/null
+++ b/tex/po/POTFILES.in
@@ -0,0 +1 @@
+[type: gettext/ini] tex_translations.in
diff --git a/tex/po/de.po b/tex/po/de.po
new file mode 100644
index 0000000..5d1b6e4
--- /dev/null
+++ b/tex/po/de.po
@@ -0,0 +1,88 @@
+#
+# Benjamin Berg <benjamin at sipsolutions.net>, 2011, 2012, 2013.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: SDAPS 0.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-03-13 19:45+0100\n"
+"PO-Revision-Date: 2013-04-04 15:09+0200\n"
+"Last-Translator: Benjamin Berg <benjamin at sipsolutions.net>\n"
+"Language-Team: German <http://hosted.weblate.org/projects/sdaps/tex/de/>\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 1.5-dev\n"
+"X-Poedit-Language: German\n"
+"X-Poedit-Country: GERMANY\n"
+"X-Poedit-SourceCharset: utf-8\n"
+
+#. The language name of the LaTeX dictionary file for translator
+#: ../tex_translations.in.h:2
+msgid "English"
+msgstr "German"
+
+#. Used as an overlay for questionnaires that are not yet done
+#: ../tex_translations.in.h:4
+msgid "draft"
+msgstr "Entwurf"
+
+#. Label for the survey-id on the questionnaire (in "classic" mode)
+#: ../tex_translations.in.h:6
+msgid "Survey-ID:"
+msgstr "Umfrage-ID:"
+
+#. Label for the questionnaire-id on the questionnaire (in "classic" mode)
+#: ../tex_translations.in.h:8
+msgid "Questionnaire-ID:"
+msgstr "Fragebogen-ID:"
+
+#. Default instructions on the questionnaire
+#: ../tex_translations.in.h:10
+msgid ""
+"This questionnaire is automatically read by a computer program. Please use a "
+"pen for filling in your answers."
+msgstr ""
+"Der Fragebogen wird maschinell erfasst. Bitte mit Kugelschreiber oder nicht "
+"zu dickem Filzstift ausfüllen."
+
+#. Instruction on the questionnaire: Description of a crossed checkbox
+#: ../tex_translations.in.h:12
+msgid "Check"
+msgstr "Ankreuzen"
+
+#. Instruction on the questionnaire: Description of a crossed and filled checkbox
+#: ../tex_translations.in.h:14
+msgid "Uncheck to correct"
+msgstr "Korrigieren"
+
+#. Instruction on the questionnaire: Multiple choice question
+#: ../tex_translations.in.h:16
+msgid "You can check any number of boxes in selection questions."
+msgstr "Bei Auswahlfeldern dürfen mehrere Antworten angekreuzt werden."
+
+#. Instruction on the questionnaire: Mark/range question.
+#: ../tex_translations.in.h:18
+msgid ""
+"For questions with a range (1--\\arabic{markcheckboxcount}) choose the "
+"answer the mark that fits best."
+msgstr ""
+"Bei Bewertungsfragen (Skala 1--\\arabic{markcheckboxcount}) darf nur ein "
+"Kästchen angekreuzt werden."
+
+#. LaTeX based report: Label for the number of people that answered
+#: ../tex_translations.in.h:20
+msgid "Answers"
+msgstr "Antworten"
+
+#. LaTeX based report: The calculated mean value
+#: ../tex_translations.in.h:22
+msgid "Mean"
+msgstr "Durchschnitt"
+
+#. LaTeX based report: The standard deviation
+#: ../tex_translations.in.h:24
+msgid "Standard-Deviation"
+msgstr "Standardabweichung"
diff --git a/tex/po/es.po b/tex/po/es.po
new file mode 100644
index 0000000..37e6778
--- /dev/null
+++ b/tex/po/es.po
@@ -0,0 +1,88 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: SDAPS 0.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-03-13 19:45+0100\n"
+"PO-Revision-Date: 2014-09-19 15:15+0200\n"
+"Last-Translator: Benjamin Berg <benjamin at sipsolutions.net>\n"
+"Language-Team: Spanish <https://hosted.weblate.org/projects/sdaps/tex/es/>\n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 1.10-dev\n"
+
+#. The language name of the LaTeX dictionary file for translator
+#: ../tex_translations.in.h:2
+msgid "English"
+msgstr "Spanish"
+
+#. Used as an overlay for questionnaires that are not yet done
+#: ../tex_translations.in.h:4
+msgid "draft"
+msgstr "borrador"
+
+#. Label for the survey-id on the questionnaire (in "classic" mode)
+#: ../tex_translations.in.h:6
+msgid "Survey-ID:"
+msgstr "ID de encuesta:"
+
+#. Label for the questionnaire-id on the questionnaire (in "classic" mode)
+#: ../tex_translations.in.h:8
+msgid "Questionnaire-ID:"
+msgstr "ID de cuestionario:"
+
+#. Default instructions on the questionnaire
+#: ../tex_translations.in.h:10
+msgid ""
+"This questionnaire is automatically read by a computer program. Please use a "
+"pen for filling in your answers."
+msgstr ""
+"El cuestionario se lee automáticamente por un programa computarizado.  Por "
+"favor, utilice un bolígrafo para completar sus respuestas."
+
+#. Instruction on the questionnaire: Description of a crossed checkbox
+#: ../tex_translations.in.h:12
+msgid "Check"
+msgstr "Marque"
+
+#. Instruction on the questionnaire: Description of a crossed and filled checkbox
+#: ../tex_translations.in.h:14
+msgid "Uncheck to correct"
+msgstr "Desmarque para corregir"
+
+#. Instruction on the questionnaire: Multiple choice question
+#: ../tex_translations.in.h:16
+msgid "You can check any number of boxes in selection questions."
+msgstr ""
+"Puede marcar varios encasillados en las preguntas de selección multiple."
+
+#. Instruction on the questionnaire: Mark/range question.
+#: ../tex_translations.in.h:18
+msgid ""
+"For questions with a range (1--\\arabic{markcheckboxcount}) choose the "
+"answer the mark that fits best."
+msgstr ""
+"Para preguntas con un rango (1--\\arabic{markcheckboxcount}), escoja la "
+"mejor contestación."
+
+#. LaTeX based report: Label for the number of people that answered
+#: ../tex_translations.in.h:20
+msgid "Answers"
+msgstr "Respuestas"
+
+#. LaTeX based report: The calculated mean value
+#: ../tex_translations.in.h:22
+msgid "Mean"
+msgstr "Media"
+
+#. LaTeX based report: The standard deviation
+#: ../tex_translations.in.h:24
+msgid "Standard-Deviation"
+msgstr "Desviación estándar"
diff --git a/tex/po/fi.po b/tex/po/fi.po
new file mode 100644
index 0000000..19f2732
--- /dev/null
+++ b/tex/po/fi.po
@@ -0,0 +1,87 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: SDAPS\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-03-13 19:45+0100\n"
+"PO-Revision-Date: 2014-08-17 12:53+0200\n"
+"Last-Translator: Joonas Joensuu <joonas.joensuu at gmail.com>\n"
+"Language-Team: Finnish <https://hosted.weblate.org/projects/sdaps/tex/fi/>\n"
+"Language: fi\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 1.10-dev\n"
+
+#. The language name of the LaTeX dictionary file for translator
+#: ../tex_translations.in.h:2
+msgid "English"
+msgstr "Finnish"
+
+#. Used as an overlay for questionnaires that are not yet done
+#: ../tex_translations.in.h:4
+msgid "draft"
+msgstr "luonnos"
+
+#. Label for the survey-id on the questionnaire (in "classic" mode)
+#: ../tex_translations.in.h:6
+msgid "Survey-ID:"
+msgstr "Kyselyn tunnus:"
+
+#. Label for the questionnaire-id on the questionnaire (in "classic" mode)
+#: ../tex_translations.in.h:8
+msgid "Questionnaire-ID:"
+msgstr "Kyselylomakkeen tunnus:"
+
+#. Default instructions on the questionnaire
+#: ../tex_translations.in.h:10
+msgid ""
+"This questionnaire is automatically read by a computer program. Please use a "
+"pen for filling in your answers."
+msgstr ""
+"Tämä kyselykaavake luetaan automaattisesti tietokoneohjelman avulla. Käytä "
+"kuulakärkikynää vastatessasi kysymyksiin."
+
+#. Instruction on the questionnaire: Description of a crossed checkbox
+#: ../tex_translations.in.h:12
+msgid "Check"
+msgstr "Valitse"
+
+#. Instruction on the questionnaire: Description of a crossed and filled checkbox
+#: ../tex_translations.in.h:14
+msgid "Uncheck to correct"
+msgstr "Poista valinta korjataksesi"
+
+#. Instruction on the questionnaire: Multiple choice question
+#: ../tex_translations.in.h:16
+msgid "You can check any number of boxes in selection questions."
+msgstr "Voit valita haluamasi määrän laatikoita valintakysymyksissä."
+
+#. Instruction on the questionnaire: Mark/range question.
+#: ../tex_translations.in.h:18
+msgid ""
+"For questions with a range (1--\\arabic{markcheckboxcount}) choose the "
+"answer the mark that fits best."
+msgstr ""
+"Kysymyksissä, jotka vastaus kysytään vaihteluvälillä (1--"
+"\\arabic{markcheckboxcount}), valitse vastaus, joka sopii parhaiten."
+
+#. LaTeX based report: Label for the number of people that answered
+#: ../tex_translations.in.h:20
+msgid "Answers"
+msgstr "Vastaukset"
+
+#. LaTeX based report: The calculated mean value
+#: ../tex_translations.in.h:22
+msgid "Mean"
+msgstr "Keskiarvo"
+
+#. LaTeX based report: The standard deviation
+#: ../tex_translations.in.h:24
+msgid "Standard-Deviation"
+msgstr "Keskihajonta"
diff --git a/tex/po/fr.po b/tex/po/fr.po
new file mode 100644
index 0000000..245dcee
--- /dev/null
+++ b/tex/po/fr.po
@@ -0,0 +1,88 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: SDAPS 0.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2014-11-01 10:52+0100\n"
+"PO-Revision-Date: 2017-05-30 17:39+0000\n"
+"Last-Translator: Berteh <berteh at gmail.com>\n"
+"Language-Team: French <https://hosted.weblate.org/projects/sdaps/tex/fr/>\n"
+"Language: fr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n > 1;\n"
+"X-Generator: Weblate 2.14.1\n"
+
+#. The language name of the LaTeX dictionary file for translator
+#: ../tex_translations.in.h:2
+msgid "English"
+msgstr "French"
+
+#. Used as an overlay for questionnaires that are not yet done
+#: ../tex_translations.in.h:4
+msgid "draft"
+msgstr "brouillon"
+
+#. Label for the survey-id on the questionnaire (in "classic" mode)
+#: ../tex_translations.in.h:6
+msgid "Survey-ID:"
+msgstr "ID-Sondage :"
+
+#. Label for the questionnaire-id on the questionnaire (in "classic" mode)
+#: ../tex_translations.in.h:8
+msgid "Questionnaire-ID:"
+msgstr "ID-Questionnaire :"
+
+#. Default instructions on the questionnaire
+#: ../tex_translations.in.h:10
+msgid ""
+"This questionnaire is automatically read by a computer program. Please use a "
+"pen for filling in your answers."
+msgstr ""
+"Ce questionnaire est traité automatiquement. Veuillez utiliser un stylo à "
+"bille pour le compléter."
+
+#. Instruction on the questionnaire: Description of a crossed checkbox
+#: ../tex_translations.in.h:12
+msgid "Check"
+msgstr "Cocher"
+
+#. Instruction on the questionnaire: Description of a crossed and filled checkbox
+#: ../tex_translations.in.h:14
+msgid "Uncheck to correct"
+msgstr "Noircir pour corriger"
+
+#. Instruction on the questionnaire: Multiple choice question
+#: ../tex_translations.in.h:16
+msgid "You can check any number of boxes in selection questions."
+msgstr ""
+"Cochez toutes les réponses voulues dans les questions à choix multiples."
+
+#. Instruction on the questionnaire: Mark/range question.
+#: ../tex_translations.in.h:18
+msgid ""
+"For questions with a range (1--\\arabic{markcheckboxcount}) choose the "
+"answer the mark that fits best."
+msgstr ""
+"Ne cochez qu'une seule réponse pour les questions d'échelles (1--\\"
+"arabic{markcheckboxcount})."
+
+#. LaTeX based report: Label for the number of people that answered
+#: ../tex_translations.in.h:20
+msgid "Answers"
+msgstr "Réponses"
+
+#. LaTeX based report: The calculated mean value
+#: ../tex_translations.in.h:22
+msgid "Mean"
+msgstr "Moyenne"
+
+#. LaTeX based report: The standard deviation
+#: ../tex_translations.in.h:24
+msgid "Standard-Deviation"
+msgstr "Écart-type"
diff --git a/tex/po/nl.po b/tex/po/nl.po
new file mode 100644
index 0000000..6c18ceb
--- /dev/null
+++ b/tex/po/nl.po
@@ -0,0 +1,85 @@
+# Benjamin Berg <benjamin at sipsolutions.net>, 2011, 2012, 2013.
+# Serge Stroobandt <serge at stroobandt.com>, 2013.
+msgid ""
+msgstr ""
+"Project-Id-Version: SDAPS 0.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-03-13 19:45+0100\n"
+"PO-Revision-Date: 2015-04-02 14:28+0200\n"
+"Last-Translator: Rik Starmans <hstarmans at gmail.com>\n"
+"Language-Team: Dutch <https://hosted.weblate.org/projects/sdaps/tex/nl/>\n"
+"Language: nl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 2.3-dev\n"
+"X-Poedit-SourceCharset: utf-8\n"
+
+#. The language name of the LaTeX dictionary file for translator
+#: ../tex_translations.in.h:2
+msgid "English"
+msgstr "Dutch"
+
+#. Used as an overlay for questionnaires that are not yet done
+#: ../tex_translations.in.h:4
+msgid "draft"
+msgstr "voorontwerp"
+
+#. Label for the survey-id on the questionnaire (in "classic" mode)
+#: ../tex_translations.in.h:6
+msgid "Survey-ID:"
+msgstr "Onderzoeks-ID:"
+
+#. Label for the questionnaire-id on the questionnaire (in "classic" mode)
+#: ../tex_translations.in.h:8
+msgid "Questionnaire-ID:"
+msgstr "Vragenlijst-ID:"
+
+#. Default instructions on the questionnaire
+#: ../tex_translations.in.h:10
+msgid ""
+"This questionnaire is automatically read by a computer program. Please use a "
+"pen for filling in your answers."
+msgstr ""
+"De vragenlijst wordt elektronisch ingelezen. Gelieve met een balpen of dunne "
+"viltstift te antwoorden."
+
+#. Instruction on the questionnaire: Description of a crossed checkbox
+#: ../tex_translations.in.h:12
+msgid "Check"
+msgstr "Aankruisen"
+
+#. Instruction on the questionnaire: Description of a crossed and filled checkbox
+#: ../tex_translations.in.h:14
+msgid "Uncheck to correct"
+msgstr "Corrigeren"
+
+#. Instruction on the questionnaire: Multiple choice question
+#: ../tex_translations.in.h:16
+msgid "You can check any number of boxes in selection questions."
+msgstr "Bij meerkeuzevragen mogen meerdere antwoorden worden aangekruist."
+
+#. Instruction on the questionnaire: Mark/range question.
+#: ../tex_translations.in.h:18
+msgid ""
+"For questions with a range (1--\\arabic{markcheckboxcount}) choose the "
+"answer the mark that fits best."
+msgstr ""
+"Bij waarderingsvragen met een bereik (1--\\arabic{markcheckboxcount}) mag "
+"slechts een hokje worden aangekruist."
+
+#. LaTeX based report: Label for the number of people that answered
+#: ../tex_translations.in.h:20
+msgid "Answers"
+msgstr "Antwoorden"
+
+#. LaTeX based report: The calculated mean value
+#: ../tex_translations.in.h:22
+msgid "Mean"
+msgstr "Gemiddelde"
+
+#. LaTeX based report: The standard deviation
+#: ../tex_translations.in.h:24
+msgid "Standard-Deviation"
+msgstr "Standaardafwijking"
diff --git a/tex/po/pt.po b/tex/po/pt.po
new file mode 100644
index 0000000..4068f9c
--- /dev/null
+++ b/tex/po/pt.po
@@ -0,0 +1,88 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: SDAPS 0.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-03-13 19:45+0100\n"
+"PO-Revision-Date: 2014-09-19 15:15+0200\n"
+"Last-Translator: Benjamin Berg <benjamin at sipsolutions.net>\n"
+"Language-Team: Portuguese <https://hosted.weblate.org/projects/sdaps/tex/pt/"
+">\n"
+"Language: pt\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 1.10-dev\n"
+
+#. The language name of the LaTeX dictionary file for translator
+#: ../tex_translations.in.h:2
+msgid "English"
+msgstr "Portuguese"
+
+#. Used as an overlay for questionnaires that are not yet done
+#: ../tex_translations.in.h:4
+msgid "draft"
+msgstr "projeto"
+
+#. Label for the survey-id on the questionnaire (in "classic" mode)
+#: ../tex_translations.in.h:6
+msgid "Survey-ID:"
+msgstr "ID da Pesquisa:"
+
+#. Label for the questionnaire-id on the questionnaire (in "classic" mode)
+#: ../tex_translations.in.h:8
+msgid "Questionnaire-ID:"
+msgstr "ID do Questionário:"
+
+#. Default instructions on the questionnaire
+#: ../tex_translations.in.h:10
+msgid ""
+"This questionnaire is automatically read by a computer program. Please use a "
+"pen for filling in your answers."
+msgstr ""
+"Este questionário é lido automaticamente por um programa. Por favor, utilize "
+"uma caneta para preenchê-lo."
+
+#. Instruction on the questionnaire: Description of a crossed checkbox
+#: ../tex_translations.in.h:12
+msgid "Check"
+msgstr "Marcar"
+
+#. Instruction on the questionnaire: Description of a crossed and filled checkbox
+#: ../tex_translations.in.h:14
+msgid "Uncheck to correct"
+msgstr "Corrigir"
+
+#. Instruction on the questionnaire: Multiple choice question
+#: ../tex_translations.in.h:16
+msgid "You can check any number of boxes in selection questions."
+msgstr "Você pode marcar qualquer número de caixas em questões de seleção."
+
+#. Instruction on the questionnaire: Mark/range question.
+#: ../tex_translations.in.h:18
+msgid ""
+"For questions with a range (1--\\arabic{markcheckboxcount}) choose the "
+"answer the mark that fits best."
+msgstr ""
+"Em questões com um intervalo (1 - \\arabic{markcheckboxcount}) escolha a "
+"resposta que se encaixa melhor."
+
+#. LaTeX based report: Label for the number of people that answered
+#: ../tex_translations.in.h:20
+msgid "Answers"
+msgstr "Respostas"
+
+#. LaTeX based report: The calculated mean value
+#: ../tex_translations.in.h:22
+msgid "Mean"
+msgstr "Média"
+
+#. LaTeX based report: The standard deviation
+#: ../tex_translations.in.h:24
+msgid "Standard-Deviation"
+msgstr "Desvio Padrão"
diff --git a/tex/po/pt_BR.po b/tex/po/pt_BR.po
new file mode 100644
index 0000000..a6b613b
--- /dev/null
+++ b/tex/po/pt_BR.po
@@ -0,0 +1,88 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: SDAPS 0.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-03-13 19:45+0100\n"
+"PO-Revision-Date: 2014-09-19 15:15+0200\n"
+"Last-Translator: Benjamin Berg <benjamin at sipsolutions.net>\n"
+"Language-Team: Portuguese (Brazil) <https://hosted.weblate.org/projects/"
+"sdaps/tex/pt_BR/>\n"
+"Language: pt_BR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 1.10-dev\n"
+
+#. The language name of the LaTeX dictionary file for translator
+#: ../tex_translations.in.h:2
+msgid "English"
+msgstr "Brazilian"
+
+#. Used as an overlay for questionnaires that are not yet done
+#: ../tex_translations.in.h:4
+msgid "draft"
+msgstr "projeto"
+
+#. Label for the survey-id on the questionnaire (in "classic" mode)
+#: ../tex_translations.in.h:6
+msgid "Survey-ID:"
+msgstr "ID da Pesquisa:"
+
+#. Label for the questionnaire-id on the questionnaire (in "classic" mode)
+#: ../tex_translations.in.h:8
+msgid "Questionnaire-ID:"
+msgstr "ID do Questionário:"
+
+#. Default instructions on the questionnaire
+#: ../tex_translations.in.h:10
+msgid ""
+"This questionnaire is automatically read by a computer program. Please use a "
+"pen for filling in your answers."
+msgstr ""
+"Este questionário é lido automaticamente por um programa. Por favor, utilize "
+"uma caneta para preenchê-lo."
+
+#. Instruction on the questionnaire: Description of a crossed checkbox
+#: ../tex_translations.in.h:12
+msgid "Check"
+msgstr "Marcar"
+
+#. Instruction on the questionnaire: Description of a crossed and filled checkbox
+#: ../tex_translations.in.h:14
+msgid "Uncheck to correct"
+msgstr "Corrigir"
+
+#. Instruction on the questionnaire: Multiple choice question
+#: ../tex_translations.in.h:16
+msgid "You can check any number of boxes in selection questions."
+msgstr "Você pode marcar qualquer número de caixas em questões de seleção."
+
+#. Instruction on the questionnaire: Mark/range question.
+#: ../tex_translations.in.h:18
+msgid ""
+"For questions with a range (1--\\arabic{markcheckboxcount}) choose the "
+"answer the mark that fits best."
+msgstr ""
+"Em questões com um intervalo (1 - \\arabic{markcheckboxcount}) escolha a "
+"resposta que se encaixa melhor."
+
+#. LaTeX based report: Label for the number of people that answered
+#: ../tex_translations.in.h:20
+msgid "Answers"
+msgstr "Respostas"
+
+#. LaTeX based report: The calculated mean value
+#: ../tex_translations.in.h:22
+msgid "Mean"
+msgstr "Média"
+
+#. LaTeX based report: The standard deviation
+#: ../tex_translations.in.h:24
+msgid "Standard-Deviation"
+msgstr "Desvio Padrão"
diff --git a/tex/po/sdaps-tex.pot b/tex/po/sdaps-tex.pot
new file mode 100644
index 0000000..b56fb88
--- /dev/null
+++ b/tex/po/sdaps-tex.pot
@@ -0,0 +1,82 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-08-20 22:30+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#. The language name of the LaTeX dictionary file for translator
+#: ../tex_translations.in.h:2
+msgid "English"
+msgstr ""
+
+#. Used as an overlay for questionnaires that are not yet done
+#: ../tex_translations.in.h:4
+msgid "draft"
+msgstr ""
+
+#. Label for the survey-id on the questionnaire (in "classic" mode)
+#: ../tex_translations.in.h:6
+msgid "Survey-ID:"
+msgstr ""
+
+#. Label for the questionnaire-id on the questionnaire (in "classic" mode)
+#: ../tex_translations.in.h:8
+msgid "Questionnaire-ID:"
+msgstr ""
+
+#. Default instructions on the questionnaire
+#: ../tex_translations.in.h:10
+msgid ""
+"This questionnaire is automatically read by a computer program. Please use a "
+"pen for filling in your answers."
+msgstr ""
+
+#. Instruction on the questionnaire: Description of a crossed checkbox
+#: ../tex_translations.in.h:12
+msgid "Check"
+msgstr ""
+
+#. Instruction on the questionnaire: Description of a crossed and filled checkbox
+#: ../tex_translations.in.h:14
+msgid "Uncheck to correct"
+msgstr ""
+
+#. Instruction on the questionnaire: Multiple choice question
+#: ../tex_translations.in.h:16
+msgid "You can check any number of boxes in selection questions."
+msgstr ""
+
+#. Instruction on the questionnaire: Mark/range question.
+#: ../tex_translations.in.h:18
+msgid ""
+"For questions with a range (1--\\arabic{markcheckboxcount}) mark the answer "
+"that fits best."
+msgstr ""
+
+#. LaTeX based report: Label for the number of people that answered
+#: ../tex_translations.in.h:20
+msgid "Answers"
+msgstr ""
+
+#. LaTeX based report: The calculated mean value
+#: ../tex_translations.in.h:22
+msgid "Mean"
+msgstr ""
+
+#. LaTeX based report: The standard deviation
+#: ../tex_translations.in.h:24
+msgid "Standard-Deviation"
+msgstr ""
diff --git a/tex/po/sv.po b/tex/po/sv.po
new file mode 100644
index 0000000..37b8293
--- /dev/null
+++ b/tex/po/sv.po
@@ -0,0 +1,83 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-03-13 19:45+0100\n"
+"PO-Revision-Date: 2014-09-19 15:16+0200\n"
+"Last-Translator: Benjamin Berg <benjamin at sipsolutions.net>\n"
+"Language-Team: Swedish <https://hosted.weblate.org/projects/sdaps/tex/sv/>\n"
+"Language: sv\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 1.10-dev\n"
+
+#. The language name of the LaTeX dictionary file for translator
+#: ../tex_translations.in.h:2
+msgid "English"
+msgstr "Swedish"
+
+#. Used as an overlay for questionnaires that are not yet done
+#: ../tex_translations.in.h:4
+msgid "draft"
+msgstr ""
+
+#. Label for the survey-id on the questionnaire (in "classic" mode)
+#: ../tex_translations.in.h:6
+msgid "Survey-ID:"
+msgstr ""
+
+#. Label for the questionnaire-id on the questionnaire (in "classic" mode)
+#: ../tex_translations.in.h:8
+msgid "Questionnaire-ID:"
+msgstr ""
+
+#. Default instructions on the questionnaire
+#: ../tex_translations.in.h:10
+msgid ""
+"This questionnaire is automatically read by a computer program. Please use a "
+"pen for filling in your answers."
+msgstr ""
+
+#. Instruction on the questionnaire: Description of a crossed checkbox
+#: ../tex_translations.in.h:12
+msgid "Check"
+msgstr ""
+
+#. Instruction on the questionnaire: Description of a crossed and filled checkbox
+#: ../tex_translations.in.h:14
+msgid "Uncheck to correct"
+msgstr ""
+
+#. Instruction on the questionnaire: Multiple choice question
+#: ../tex_translations.in.h:16
+msgid "You can check any number of boxes in selection questions."
+msgstr ""
+
+#. Instruction on the questionnaire: Mark/range question.
+#: ../tex_translations.in.h:18
+msgid ""
+"For questions with a range (1--\\arabic{markcheckboxcount}) choose the "
+"answer the mark that fits best."
+msgstr ""
+
+#. LaTeX based report: Label for the number of people that answered
+#: ../tex_translations.in.h:20
+msgid "Answers"
+msgstr ""
+
+#. LaTeX based report: The calculated mean value
+#: ../tex_translations.in.h:22
+msgid "Mean"
+msgstr ""
+
+#. LaTeX based report: The standard deviation
+#: ../tex_translations.in.h:24
+msgid "Standard-Deviation"
+msgstr ""
diff --git a/tex/qrcode.sty b/tex/qrcode.sty
new file mode 100644
index 0000000..71504c6
--- /dev/null
+++ b/tex/qrcode.sty
@@ -0,0 +1,3023 @@
+%%
+%% This is file `qrcode.sty',
+%% generated with the docstrip utility.
+%%
+%% The original source files were:
+%%
+%% qrcode.dtx  (with options: `package')
+%% 
+%% This is a generated file.
+%% 
+%% Copyright (C) 2014 by Anders Hendrickson <ahendric at cord.edu>
+%% 
+%% This work may be distributed and/or modified under the
+%% conditions of the LaTeX Project Public License, either version 1.3
+%% of this license or (at your option) any later version.
+%% The latest version of this license is in
+%%   http://www.latex-project.org/lppl.txt
+%% and version 1.3 or later is part of all distributions of LaTeX
+%% version 2005/12/01 or later.
+%% 
+\NeedsTeXFormat{LaTeX2e}[1999/12/01]
+\ProvidesPackage{qrcode}
+    [2014/09/26 v1.0 QR code generation]
+%%PACKAGE LOADING
+\RequirePackage{xcolor}%
+\RequirePackage{xkeyval}%
+
+%%INITIAL CODE
+\newif\ifqr at draft@mode
+\newif\ifqr at forget@mode
+
+%%DECLARATION OF OPTIONS
+\define at boolkey{qr}[qr@]{draft}[true]{\ifqr at draft\qr at draft@modetrue\else\qr at draft@modefalse\fi}%
+\define at boolkey{qr}[qr@]{final}[true]{\ifqr at final\qr at draft@modefalse\else\qr at draft@modetrue\fi}%
+\define at boolkey{qr}[qr@]{forget}[true]{\ifqr at forget\qr at forget@modetrue\else\qr at forget@modefalse\fi}%
+
+%%EXECUTION OF OPTIONS
+\qr at draft@modefalse
+\qr at forget@modefalse
+
+\ProcessOptionsX<qr>
+
+\newcounter{qr at i}%
+\newcounter{qr at j}%
+\newcount\qr at a
+\newcount\qr at b
+\newcount\qr at c
+
+\let\xa=\expandafter
+
+\newlinechar=`\^^J
+
+\def\@relax{\relax}%
+
+\def\preface at macro#1#2{%
+  % #1 = macro name
+  % #2 = text to add to front of macro
+  \def\tempb{#2}%
+  \xa\xa\xa\def\xa\xa\xa#1\xa\xa\xa{\xa\tempb #1}%
+}%
+
+\def\g at preface@macro#1#2{%
+  % #1 = macro to be appended to
+  % #2 = code to add
+  \edef\codeA{#2}%
+  \xa\xa\xa\gdef\xa\xa\xa#1\xa\xa\xa{\xa\codeA#1}%
+}
+
+\def\qr at getstringlength#1{%
+  \bgroup
+    \qr at a=0%
+    \xdef\thestring{#1}%
+    \xa\qr at stringlength@recursive\xa(\thestring\relax\relax)%
+    \xdef\qr at stringlength{\the\qr at a}%
+  \egroup
+}%
+
+\def\qr at stringlength@recursive(#1#2){%
+  \def\testi{#1}%
+  \ifx\testi\@relax
+    %we are done.
+    \let\qr at next=\relax%
+  \else
+    \advance\qr at a by 1%
+    \def\qr at next{\qr at stringlength@recursive(#2)}%
+  \fi
+  \qr at next
+}%
+\newcount\qr at for@depth%
+\newcount\qr at for@maxdepth%
+\qr at for@depth=0%
+\qr at for@maxdepth=0%
+\newcount\qr at for@start%
+\newcount\qr at for@end%
+\newcount\qr at for@step%
+\def\qr at allocate@new at for@counter{%
+  \global\advance\qr at for@maxdepth by 1%
+  \newcount\qr at newforcount%
+  \xa\global\xa\let\csname qr at for@var@\the\qr at for@maxdepth\endcsname=\qr at newforcount%
+}%
+
+\newif\ifqr at loopshouldrun
+\def\qr at for #1=#2to#3by#4#{%
+  \qr at for@int{#1}{#2}{#3}{#4}%
+}%
+\long\def\qr at for@int#1#2#3#4#5{%
+  \bgroup
+    %Because we're working within a TeX group,
+    %any values of \qr at for@start, \qr at for@end, and \qr at for@step from an outer loop
+    %will be restored after the \egroup.
+    %
+    %For the \qr at for@var itself, however, we need a different counter,
+    %because the user's text within the loop might need to access the variable from the outer loop.
+    \advance\qr at for@depth by 1\relax% This is a local change.
+    \ifnum\qr at for@depth>\qr at for@maxdepth%
+      %This is the first time we have gone to this depth of nesting!
+      %We should only be over by one.
+      \qr at allocate@new at for@counter%
+    \fi
+    \xa\let\xa\qr at for@var\xa=\csname qr at for@var@\the\qr at for@depth\endcsname%
+    %Now \qr at for@var points to the same register as \qr at for@var at 3 or something.
+    %The next line lets the user-level variable (e.g., \i or \j) point to the same count register.
+    \let#1=\qr at for@var%
+    %Now establish the looping parameters.
+    \edef\qr at for@start at text{#2}%
+    \edef\qr at for@end at text{#3}%
+    \edef\qr at for@step at text{#4}%
+    \def\qr at for@body{\bgroup #5\egroup}%
+    \xa\qr at for@start\qr at for@start at text\relax%
+    \xa\qr at for@end  \qr at for@end at text\relax%
+    \xa\qr at for@step \qr at for@step at text\relax%
+    %
+    %Next, test whether the loop should run at all.
+    % * "\qr at for \i = 1 to 0 by 1" should fail.
+    % * "\qr at for \i = 3 to 5 by -1" should fail.
+    % * "\qr at for \i = 6 to 2 by 1" should fail.
+    % * "\qr at for \i = 4 to 4 by -1" should run.
+    % * "\qr at for \i = 4 to 4 by 1" should run.
+    % * "\qr at for \i = 5 to 7 by 0" should fail.
+    %The loop should fail if (step)=0 or if (step) and (end-start) have opposite signs.
+    %The loop will fail if (step=0) or (step)*(end-start)<0.
+    % TODO: "\qr at for \i = 5 to 5 by 0" should run (just one iteration).
+    \qr at loopshouldruntrue
+    \ifnum\qr at for@step=0\relax
+      \qr at loopshouldrunfalse
+    \fi
+    \qr at a=\qr at for@end%
+    \advance\qr at a by -\qr at for@start%
+    \multiply\qr at a by \qr at for@step%
+    \ifnum\qr at a<0\relax
+      \qr at loopshouldrunfalse
+    \fi
+    \ifqr at loopshouldrun
+      \qr at for@var=\qr at for@start%
+      \ifnum\qr at for@step>0\relax
+        \def\qr at for@recursive{%
+          \qr at for@body%
+          \advance\qr at for@var by \qr at for@step%
+          \ifnum\qr at for@var>\qr at for@end%
+            \let\qr at for@next=\relax%
+          \else%
+            \let\qr at for@next=\qr at for@recursive%
+          \fi%
+          \qr at for@next%
+        }%
+      \else
+        \def\qr at for@recursive{%
+          \qr at for@body%
+          \advance\qr at for@var by \qr at for@step%
+          \ifnum\qr at for@var<\qr at for@end%
+            \let\qr at for@next=\relax%
+          \else%
+            \let\qr at for@next=\qr at for@recursive%
+          \fi%
+          \qr at for@next%
+        }%
+      \fi
+      \qr at for@recursive%
+    \fi
+  \egroup
+}%
+\def\qr at padatfront#1#2{%
+  % #1 = macro containing text to pad
+  % #2 = desired number of characters
+  % Pads a number with initial zeros.
+  \qr at getstringlength{#1}%
+  \qr at a=\qr at stringlength\relax%
+  \advance\qr at a by 1\relax%
+  \qr at for \i = \qr at a to #2 by 1\relax%
+    {\g at preface@macro{#1}{0}}%
+}
+
+\qr at a=-1\relax%
+\def\qr at savehexsymbols(#1#2){%
+  \advance\qr at a by 1\relax%
+  \xa\def\csname qr at hexchar@\the\qr at a\endcsname{#1}%
+  \xa\edef\csname qr at hextodecimal@#1\endcsname{\the\qr at a}%
+  \ifnum\qr at a=15\relax
+    %Done.
+    \let\qr at next=\relax%
+  \else
+    \def\qr at next{\qr at savehexsymbols(#2)}%
+  \fi%
+  \qr at next%
+}%
+\qr at savehexsymbols(0123456789abcdef\relax\relax)%
+
+\def\qr at decimaltobase#1#2#3{%
+  % #1 = macro to store result
+  % #2 = decimal representation of a positive integer
+  % #3 = new base
+  \bgroup
+    \edef\qr at newbase{#3}%
+    \gdef\qr at base@result{}%
+    \qr at a=#2\relax%
+    \qr at decimaltobase@recursive%
+    \xdef#1{\qr at base@result}%
+  \egroup
+}
+\def\qr at decimaltobase@recursive{%
+  \qr at b=\qr at a%
+  \divide\qr at b by \qr at newbase\relax
+  \multiply\qr at b by -\qr at newbase\relax
+  \advance\qr at b by \qr at a\relax%
+  \divide\qr at a by \qr at newbase\relax%
+  \ifnum\qr at b<10\relax
+    \edef\newdigit{\the\qr at b}%
+  \else
+    \edef\newdigit{\csname qr at hexchar@\the\qr at b\endcsname}%
+  \fi
+  \edef\qr at argument{{\noexpand\qr at base@result}{\newdigit}}%
+  \xa\g at preface@macro\qr at argument%
+  \ifnum\qr at a=0\relax
+    \relax
+  \else
+    \xa\qr at decimaltobase@recursive
+  \fi
+}
+
+\newcommand\qr at decimaltohex[3][0]{%
+  % #1 (opt.) = number of hex digits to create
+  % #2 = macro to store result
+  % #3 = decimal digits to convert
+  \qr at decimaltobase{#2}{#3}{16}%
+  \qr at padatfront{#2}{#1}%
+}
+
+\newcommand\qr at decimaltobinary[3][0]{%
+  % #1 (opt.) = number of bits to create
+  % #2 = macro to store result
+  % #3 = decimal digits to convert
+  \qr at decimaltobase{#2}{#3}{2}%
+  \qr at padatfront{#2}{#1}%
+}
+
+\qr at for \i = 0 to 15 by 1%
+  {%
+   \qr at decimaltohex[1]{\qr at hexchar}{\the\i}%
+   \qr at decimaltobinary[4]{\qr at bits}{\the\i}%
+   \xa\xdef\csname qr at b2h@\qr at bits\endcsname{\qr at hexchar}%
+   \xa\xdef\csname qr at h2b@\qr at hexchar\endcsname{\qr at bits}%
+  }%
+
+\newcommand\qr at binarytohex[3][\relax]{%
+  % #1 (optional) = # digits desired
+  % #2 = macro to save to
+  % #3 = binary string (must be multiple of 4 bits)
+  \def\test at i{#1}%
+  \ifx\test at i\@relax%
+    %No argument specified
+    \def\qr at desireddigits{0}%
+  \else
+    \def\qr at desireddigits{#1}%
+  \fi
+  \gdef\qr at base@result{}%
+  \edef\qr at argument{(#3\relax\relax\relax\relax\relax)}%
+  \xa\qr at binarytohex@int\qr at argument%
+  \qr at padatfront{\qr at base@result}{\qr at desireddigits}%
+  \xdef#2{\qr at base@result}%
+}
+\def\qr at binarytohex@int(#1#2#3#4#5){%
+  % #1#2#3#4 = 4 bits
+  % #5 = remainder, including \relax\relax\relax\relax\relax terminator
+  \def\test at i{#1}%
+  \ifx\test at i\@relax%
+    %Done.
+    \def\qr at next{\relax}%
+  \else%
+    \xdef\qr at base@result{\qr at base@result\csname qr at b2h@#1#2#3#4\endcsname}%
+    \def\qr at next{\qr at binarytohex@int(#5)}%
+  \fi%
+  \qr at next%
+}
+
+\newcommand\qr at hextobinary[3][\relax]{%
+  % #1 (optional) = # bits desired
+  % #2 = macro to save to
+  % #3 = hexadecimal string
+  \bgroup
+  \def\test at i{#1}%
+  \ifx\test at i\@relax%
+    %No argument specified
+    \def\qr at desireddigits{0}%
+  \else
+    \def\qr at desireddigits{#1}%
+  \fi
+  \gdef\qr at base@result{}%
+  \edef\qr at argument{(#3\relax\relax)}%
+  \xa\qr at hextobinary@int\qr at argument%
+  \qr at padatfront{\qr at base@result}{\qr at desireddigits}%
+  \xdef#2{\qr at base@result}%
+  \egroup
+}
+\def\qr at hextobinary@int(#1#2){%
+  % #1 = hexadecimal character
+  % #2 = remainder, including \relax\relax terminator
+  \def\test@@i{#1}%
+  \ifx\test@@i\@relax%
+    %Done.
+    \def\qr at next{\relax}%
+  \else%
+    \xdef\qr at base@result{\qr at base@result\csname qr at h2b@#1\endcsname}%
+    \def\qr at next{\qr at hextobinary@int(#2)}%
+  \fi%
+  \qr at next%
+}
+
+\def\qr at hextodecimal#1#2{%
+  \edef\qr at argument{#2}%
+  \xa\qr at a\xa=\xa\number\xa"\qr at argument\relax%
+  \edef#1{\the\qr at a}%
+}
+
+\def\qr at hextodecimal#1#2{%
+  % #1 = macro to store result
+  % #2 = hexadecimal representation of a positive integer
+  \bgroup
+    \qr at a=0\relax%
+    \edef\qr at argument{(#2\relax)}%
+    \xa\qr at hextodecimal@recursive\qr at argument%
+    \xdef#1{\the\qr at a}%
+  \egroup
+}
+\def\qr at hextodecimal@recursive(#1#2){%
+  % #1 = first hex char
+  % #2 = remainder
+  \advance \qr at a by \csname qr at hextodecimal@#1\endcsname\relax%
+  \edef\testii{#2}%
+  \ifx\testii\@relax%
+    %Done.
+    \let\qr at next=\relax%
+  \else
+    %There's at least one more digit.
+    \multiply\qr at a by 16\relax
+    \edef\qr at next{\noexpand\qr at hextodecimal@recursive(#2)}%
+  \fi%
+  \qr at next%
+}
+{\catcode`\ =12\relax\gdef\qr at otherspace{ }}%
+{\catcode`\%=12\relax\gdef\qr at otherpercent{%}}%
+{\catcode`\#=12\relax\gdef\qr at otherpound{#}}%
+{\catcode`\|=0\relax|catcode`|\=12|relax|gdef|qr at otherbackslash{\}}%
+\bgroup
+ \catcode`\<=1\relax
+ \catcode`\>=2\relax
+ \catcode`\{=12\relax\gdef\qr at otherleftbrace<{>%
+ \catcode`\}=12\relax\gdef\qr at otherrightbrace<}>%
+\egroup%
+{\catcode`\&=12\relax\gdef\qr at otherampersand{&}}%
+{\catcode`\~=12\relax\gdef\qr at othertilde{~}}%
+{\catcode`\^=12\relax\gdef\qr at othercaret{^}}%
+{\catcode`\_=12\relax\gdef\qr at otherunderscore{_}}%
+{\catcode`\$=12\relax\gdef\qr at otherdollar{$}}%
+
+\def\qr at verbatimcatcodes{%
+  \catcode`\#=12\relax
+  \catcode`\$=12\relax
+  \catcode`\&=12\relax
+  \catcode`\^=12\relax
+  \catcode`\_=12\relax
+  \catcode`\~=12\relax
+  \catcode`\%=12\relax
+  \catcode`\ =12\relax}%
+
+\def\qr at setescapedspecials{%
+  \let\ =\qr at otherspace%
+  \let\%=\qr at otherpercent%
+  \let\#=\qr at otherpound%
+  \let\&=\qr at otherampersand%
+  \let\^=\qr at othercaret%
+  \let\_=\qr at otherunderscore%
+  \let\~=\qr at othertilde%
+  \let\$=\qr at otherdollar%
+  \let\\=\qr at otherbackslash%
+  \let\{=\qr at otherleftbrace%
+  \let\}=\qr at otherrightbrace%
+}%
+\def\qr at creatematrix#1{%
+  \xa\gdef\csname #1\endcsname##1##2{%
+    \csname #1@##1@##2\endcsname
+  }%
+}%
+
+\def\qr at storetomatrix#1#2#3#4{%
+  % #1 = matrix name
+  % #2 = row number
+  % #3 = column number
+  % #4 = value of matrix entry
+  \xa\gdef\csname #1@#2@#3\endcsname{#4}%
+}%
+
+\def\qr at estoretomatrix#1#2#3#4{%
+  % This version performs exactly one expansion on #4.
+  % #1 = matrix name
+  % #2 = row number
+  % #3 = column number
+  % #4 = value of matrix
+  \xa\xa\xa\gdef\xa\xa\csname #1@#2@#3\endcsname\xa{#4}%
+}%
+
+\def\qr at matrixentry#1#2#3{%
+  % #1 = matrix name
+  % #2 = row number
+  % #3 = column number
+  \csname #1@#2@#3\endcsname%
+}%
+
+\def\qr at createsquareblankmatrix#1#2{%
+  \qr at creatematrix{#1}%
+  \xa\gdef\csname #1 at numrows\endcsname{#2}%
+  \xa\gdef\csname #1 at numcols\endcsname{#2}%
+  \qr at for \i = 1 to #2 by 1%
+    {\qr at for \j = 1 to #2 by 1%
+      {\qr at storetomatrix{#1}{\the\i}{\the\j}{\@blank}}}%
+}%
+
+\def\qr at numberofrowsinmatrix#1{%
+  \csname #1 at numrows\endcsname%
+}%
+
+\def\qr at numberofcolsinmatrix#1{%
+  \csname #1 at numcols\endcsname%
+}%
+
+\def\qr at setnumberofrows#1#2{%
+  \xa\xdef\csname #1 at numrows\endcsname{#2}%
+}%
+
+\def\qr at setnumberofcols#1#2{%
+  \xa\xdef\csname #1 at numcols\endcsname{#2}%
+}%
+
+\newlength\qr at desiredheight
+\setlength\qr at desiredheight{2cm}%
+\newlength\qr at modulesize
+\newlength\qr at minipagewidth
+
+\def\qr at printmatrix#1{%
+  \def\qr at black{\rule{\qr at modulesize}{\qr at modulesize}}%
+  \def\@white{\rule{\qr at modulesize}{0pt}}%
+  \def\qr at black@fixed{\rule{\qr at modulesize}{\qr at modulesize}}%
+  \def\qr at white@fixed{\rule{\qr at modulesize}{0pt}}%
+  \def\qr at black@format{\rule{\qr at modulesize}{\qr at modulesize}}%
+  \def\qr at white@format{\rule{\qr at modulesize}{0pt}}%
+  %Set module size
+  \setlength{\qr at modulesize}{\qr at desiredheight}%
+  \divide\qr at modulesize by \qr at size\relax%
+  %
+  \setlength{\qr at minipagewidth}{\qr at modulesize}%
+  \multiply\qr at minipagewidth by \qr at size\relax%
+  \ifqr at tight
+  \else
+    \advance\qr at minipagewidth by 8\qr at modulesize%
+  \fi
+  \begin{minipage}{\qr at minipagewidth}%
+    \baselineskip=\qr at modulesize%
+    \ifqr at tight\else\rule{0pt}{4\qr at modulesize}\par\fi% %Blank space at top.
+    \qr at for \i = 1 to \qr at numberofrowsinmatrix{#1} by 1%
+      {\ifqr at tight\else\rule{4\qr at modulesize}{0pt}\fi% %Blank space at left.
+       \qr at for \j = 1 to \qr at numberofcolsinmatrix{#1} by 1%
+         {\qr at matrixentry{#1}{\the\i}{\the\j}}%
+       \par}%
+    \ifqr at tight\else\rule{0pt}{4\qr at modulesize}\par\fi%
+  \end{minipage}%
+}%
+
+\def\qr at printsavedbinarymatrix#1{%
+  \edef\qr at binarystring{#1\relax\relax}%
+  %Set module size
+  \setlength{\qr at modulesize}{\qr at desiredheight}%
+  \divide\qr at modulesize by \qr at size\relax%
+  %
+  \setlength{\qr at minipagewidth}{\qr at modulesize}%
+  \multiply\qr at minipagewidth by \qr at size\relax%
+  \ifqr at tight
+  \else
+    \advance\qr at minipagewidth by 8\qr at modulesize%
+  \fi
+  \begin{minipage}{\qr at minipagewidth}%
+    \baselineskip=\qr at modulesize%
+    \ifqr at tight\else\rule{0pt}{4\qr at modulesize}\par\fi% %Blank space at top.
+    \qr at for \i = 1 to \qr at size by 1%
+      {\ifqr at tight\else\rule{4\qr at modulesize}{0pt}\fi% %Blank space at left.
+       \qr at for \j = 1 to \qr at size by 1%
+         {\edef\theargument{(\qr at binarystring)}%
+          \xa\qr at printsavedbinarymatrix@int\theargument
+         }%
+       \par}%
+    \ifqr at tight\else\rule{0pt}{4\qr at modulesize}\par\fi%
+  \end{minipage}%
+}%
+
+\def\qr at printsavedbinarymatrix@int(#1#2){%
+  % #1 = first bit, either 1 or 0.
+  % #2 = remainder of string, terminating with \relax\relax
+  % There's no need to check for EOF here, because
+  % we'll only call this n^2 times.
+  \ifcase #1\relax
+    \rule{\qr at modulesize}{0pt}% % 0: white square
+  \or
+    \rule{\qr at modulesize}{\qr at modulesize}% % 1: black square
+  \fi
+  \xdef\qr at binarystring{#2}%
+}%
+
+\def\qr at createliteralmatrix#1#2#3{%
+  % #1 = matrix name
+  % #2 = m, the number of rows and columns in the square matrix
+  % #3 = a string of m^2 tokens to be written into the matrix
+  \qr at creatematrix{#1}%
+  \xa\xdef\csname #1 at numrows\endcsname{#2}%
+  \xa\xdef\csname #1 at numcols\endcsname{#2}%
+  \gdef\qr at literalmatrix@tokens{#3}%
+  \qr at for \i = 1 to #2 by 1%
+    {\qr at for \j = 1 to #2 by 1%
+      {\xa\qr at createliteralmatrix@int\xa(\qr at literalmatrix@tokens)%
+       \qr at estoretomatrix{#1}{\the\i}{\the\j}{\qr at entrytext}%
+      }%
+    }%
+}
+\def\qr at createliteralmatrix@int(#1#2){%
+  \def\qr at entrytext{#1}%
+  \gdef\qr at literalmatrix@tokens{#2}%
+}
+
+\qr at createliteralmatrix{finderpattern}{8}{%
+  \qr at black@fixed\qr at black@fixed\qr at black@fixed\qr at black@fixed\qr at black@fixed\qr at black@fixed\qr at black@fixed\qr at white@fixed%
+  \qr at black@fixed\qr at white@fixed\qr at white@fixed\qr at white@fixed\qr at white@fixed\qr at white@fixed\qr at black@fixed\qr at white@fixed%
+  \qr at black@fixed\qr at white@fixed\qr at black@fixed\qr at black@fixed\qr at black@fixed\qr at white@fixed\qr at black@fixed\qr at white@fixed%
+  \qr at black@fixed\qr at white@fixed\qr at black@fixed\qr at black@fixed\qr at black@fixed\qr at white@fixed\qr at black@fixed\qr at white@fixed%
+  \qr at black@fixed\qr at white@fixed\qr at black@fixed\qr at black@fixed\qr at black@fixed\qr at white@fixed\qr at black@fixed\qr at white@fixed%
+  \qr at black@fixed\qr at white@fixed\qr at white@fixed\qr at white@fixed\qr at white@fixed\qr at white@fixed\qr at black@fixed\qr at white@fixed%
+  \qr at black@fixed\qr at black@fixed\qr at black@fixed\qr at black@fixed\qr at black@fixed\qr at black@fixed\qr at black@fixed\qr at white@fixed%
+  \qr at white@fixed\qr at white@fixed\qr at white@fixed\qr at white@fixed\qr at white@fixed\qr at white@fixed\qr at white@fixed\qr at white@fixed%
+}%
+
+\qr at createliteralmatrix{alignmentpattern}{5}{%
+  \qr at black@fixed\qr at black@fixed\qr at black@fixed\qr at black@fixed\qr at black@fixed%
+  \qr at black@fixed\qr at white@fixed\qr at white@fixed\qr at white@fixed\qr at black@fixed%
+  \qr at black@fixed\qr at white@fixed\qr at black@fixed\qr at white@fixed\qr at black@fixed%
+  \qr at black@fixed\qr at white@fixed\qr at white@fixed\qr at white@fixed\qr at black@fixed%
+  \qr at black@fixed\qr at black@fixed\qr at black@fixed\qr at black@fixed\qr at black@fixed%
+}%
+
+\def\qr at copymatrixentry#1#2#3#4#5#6{%
+  % Copy the (#2,#3) entry of matrix #1
+  % to the (#5,#6) position of matrix #4.
+  \xa\xa\xa\global%
+  \xa\xa\xa\let\xa\xa\csname #4@#5@#6\endcsname%
+                     \csname #1@#2@#3\endcsname%
+}%
+
+\def\qr at createduplicatematrix#1#2{%
+  % #1 = name of copy
+  % #2 = original matrix to be copied
+  \qr at creatematrix{#1}%
+  \qr at for \i = 1 to \qr at numberofrowsinmatrix{#2} by 1%
+    {\qr at for \j = 1 to \qr at numberofcolsinmatrix{#2} by 1%
+      {\qr at copymatrixentry{#2}{\the\i}{\the\j}{#1}{\the\i}{\the\j}%
+      }%
+    }%
+  \qr at setnumberofrows{#1}{\qr at numberofrowsinmatrix{#2}}%
+  \qr at setnumberofcols{#1}{\qr at numberofcolsinmatrix{#2}}%
+}%
+
+\def\qr at placefinderpattern@int#1#2#3#4#5{%
+  % Work on matrix #1.
+  % Start in position (#2, #3) -- should be a corner
+  % #4 indicates horizontal direction (1=right, -1=left)
+  % #5 indicates vertical direction (1=down, -1=up)
+  %
+  % In this code, \sourcei and \sourcej are TeX counts working through the finderpattern matrix,
+  % and i and j are LaTeX counters indicating positions in the big matrix.
+  \setcounter{qr at i}{#2}%
+  \qr at for \sourcei=1 to 8 by 1%
+    {\setcounter{qr at j}{#3}%
+     \qr at for \sourcej=1 to 8 by 1%
+       {\qr at copymatrixentry{finderpattern}{\the\sourcei}{\the\sourcej}%
+                        {#1}{\theqr at i}{\theqr at j}%
+        \addtocounter{qr at j}{#5}%
+       }%
+     \addtocounter{qr at i}{#4}%
+    }%
+}%
+
+\def\qr at placefinderpatterns#1{%
+  % #1=matrix name
+  \qr at placefinderpattern@int{#1}{1}{1}{1}{1}%
+  \qr at placefinderpattern@int{#1}{\qr at numberofrowsinmatrix{#1}}{1}{-1}{1}%
+  \qr at placefinderpattern@int{#1}{1}{\qr at numberofcolsinmatrix{#1}}{1}{-1}%
+}%
+
+\def\qr at placetimingpatterns#1{%
+  %Set \endingcol to n-8.
+  \qr at a=\qr at size\relax%
+  \advance\qr at a by -8\relax%
+  \edef\endingcol{\the\qr at a}%
+  \qr at for \j = 9 to \endingcol by 1%
+    {\ifodd\j\relax%
+       \qr at storetomatrix{#1}{7}{\the\j}{\qr at black@fixed}%
+       \qr at storetomatrix{#1}{\the\j}{7}{\qr at black@fixed}%
+     \else%
+       \qr at storetomatrix{#1}{7}{\the\j}{\qr at white@fixed}%
+       \qr at storetomatrix{#1}{\the\j}{7}{\qr at white@fixed}%
+     \fi%
+    }%
+}%
+
+\def\qr at placealignmentpattern@int#1#2#3{%
+  % Work on matrix #1.
+  % Write an alignment pattern into the matrix, centered on (#2,#3).
+  \qr at a=#2\relax%
+  \advance\qr at a by -2\relax%
+  \qr at b=#3\relax%
+  \advance\qr at b by -2\relax%
+  \setcounter{qr at i}{\the\qr at a}%
+  \qr at for \i=1 to 5 by 1%
+    {\setcounter{qr at j}{\the\qr at b}%
+     \qr at for \j=1 to 5 by 1%
+      {\qr at copymatrixentry{alignmentpattern}{\the\i}{\the\j}%
+                       {#1}{\theqr at i}{\theqr at j}%
+       \stepcounter{qr at j}%
+      }%
+     \stepcounter{qr at i}%
+    }%
+}%
+
+\newif\ifqr at incorner%
+\def\qr at placealignmentpatterns#1{%
+  %There are k^2-3 alignment patterns,
+  %arranged in a (k x k) grid within the matrix.
+  %They begin in row 7, column 7,
+  %except that the ones in the NW, NE, and SW corners
+  %are omitted because of the finder patterns.
+  %Recall that
+  %  * \qr at k stores k,
+  %  * \qr at alignment@firstskip stores how far between the 1st and 2nd row/col, &
+  %  * \qr at alignment@generalskip stores how far between each subsequent row/col.
+  \xa\ifnum\qr at k>0\relax
+    %There will be at least one alignment pattern.
+    %N.B. k cannot equal 1.
+    \xa\ifnum\qr at k=2\relax
+      % 2*2-3 = exactly 1 alignment pattern.
+      \qr at a=7\relax
+      \advance\qr at a by \qr at alignment@firstskip\relax
+      \xdef\qr at target@ii{\the\qr at a}%
+      \qr at placealignmentpattern@int{#1}{\qr at target@ii}{\qr at target@ii}%
+    \else
+      % k is at least 3, so the following loops should be safe.
+      \xdef\qr at target@ii{7}%
+      \qr at for \ii = 1 to \qr at k by 1%
+        {\ifcase\ii\relax%
+           \relax% \ii should never equal 0.
+         \or
+           \xdef\qr at target@ii{7}% If \ii = 1, we start in row 7.
+         \or
+           %If \ii = 2, we add the firstskip.
+           \qr at a=\qr at target@ii\relax%
+           \advance\qr at a by \qr at alignment@firstskip\relax%
+           \xdef\qr at target@ii{\the\qr at a}%
+         \else
+           %If \ii>2, we add the generalskip.
+           \qr at a=\qr at target@ii\relax%
+           \advance\qr at a by \qr at alignment@generalskip\relax%
+           \xdef\qr at target@ii{\the\qr at a}%
+         \fi
+         \qr at for \jj = 1 to \qr at k by 1%
+           {\ifcase\jj\relax%
+              \relax% \jj should never equal 0.
+            \or
+              \xdef\qr at target@jj{7}% If \jj=1, we start in row 7.
+            \or
+              %If \jj=2, we add the firstskip.
+              \qr at a=\qr at target@jj\relax%
+              \advance\qr at a by \qr at alignment@firstskip%
+              \xdef\qr at target@jj{\the\qr at a}%
+            \else
+              %If \jj>2, we add the generalskip.
+              \qr at a=\qr at target@jj\relax%
+              \advance\qr at a by \qr at alignment@generalskip%
+              \xdef\qr at target@jj{\the\qr at a}%
+            \fi
+            \qr at incornerfalse%
+            \ifnum\ii=1\relax
+              \ifnum\jj=1\relax
+                \qr at incornertrue
+              \else
+                \ifnum\qr at k=\jj\relax
+                  \qr at incornertrue
+                \fi
+              \fi
+            \else
+              \xa\ifnum\qr at k=\ii\relax
+                \ifnum\jj=1\relax
+                  \qr at incornertrue
+                \fi
+              \fi
+            \fi
+            \ifqr at incorner
+              \relax
+            \else
+              \qr at placealignmentpattern@int{#1}{\qr at target@ii}{\qr at target@jj}%
+            \fi
+           }% ends \qr at for \jj
+        }% ends \qr at for \ii
+    \fi
+  \fi
+}%
+
+\def\qr at placedummyformatpatterns#1{%
+  \qr at for \j = 1 to 9 by 1%
+    {\ifnum\j=7\relax%
+     \else%
+       \qr at storetomatrix{#1}{9}{\the\j}{\qr at format@square}%
+       \qr at storetomatrix{#1}{\the\j}{9}{\qr at format@square}%
+     \fi%
+    }%
+  \setcounter{qr at j}{\qr at size}%
+  \qr at for \j = 1 to 8 by 1%
+    {\qr at storetomatrix{#1}{9}{\theqr at j}{\qr at format@square}%
+     \qr at storetomatrix{#1}{\theqr at j}{9}{\qr at format@square}%
+     \addtocounter{qr at j}{-1}%
+    }%
+  %Now go back and change the \qr at format@square in (n-8,9) to \qr at black@fixed.
+  \addtocounter{qr at j}{1}%
+  \qr at storetomatrix{#1}{\theqr at j}{9}{\qr at black@fixed}%
+}%
+
+\def\qr at placedummyversionpatterns#1{%
+  \xa\ifnum\qr at version>6\relax
+    %Must include version information.
+    \global\c at qr@i=\qr at size%
+    \global\advance\c at qr@i by -10\relax%
+    \qr at for \i = 1 to 3 by 1%
+      {\qr at for \j = 1 to 6 by 1%
+        {\qr at storetomatrix{#1}{\theqr at i}{\the\j}{\qr at format@square}%
+         \qr at storetomatrix{#1}{\the\j}{\theqr at i}{\qr at format@square}%
+        }%
+       \stepcounter{qr at i}%
+      }%
+  \fi
+}%
+
+\def\qr at writebit(#1#2)#3{%
+  % #3 = matrix name
+  % (qr at i,qr at j) = position to write in (LaTeX counters)
+  % #1 = bit to be written
+  % #2 = remaining bits plus '\relax' as an end-of-file marker
+  \edef\qr at datatowrite{#2}%
+  \ifnum#1=1
+    \qr at storetomatrix{#3}{\theqr at i}{\theqr at j}{\qr at black}%
+  \else
+    \qr at storetomatrix{#3}{\theqr at i}{\theqr at j}{\@white}%
+  \fi
+}%
+
+\newif\ifqr at rightcol
+\newif\ifqr at goingup
+
+\def\qr at writedata@hex#1#2{%
+  % #1 = name of a matrix that has been prepared with finder patterns, timing patterns, etc.
+  % #2 = a string consisting of bytes to write into the matrix, in two-char hex format.
+  \setcounter{qr at i}{\qr at numberofrowsinmatrix{#1}}%
+  \setcounter{qr at j}{\qr at numberofcolsinmatrix{#1}}%
+  \qr at rightcoltrue%
+  \qr at goinguptrue%
+  \edef\qr at argument{{#1}(#2\relax\relax\relax)}%
+  \xa\qr at writedata@hex at recursive\qr at argument%
+}%
+
+\def\qr at writedata@hex at recursive#1(#2#3#4){%
+  % #1 = name of a matrix that has been prepared with finder patterns, timing patterns, etc.
+  % (qr at i,qr at j) = position to write in LaTeX counters
+  % #2#3#4 contains the hex codes of the bytes to be written, plus \relax\relax\relax
+  % as an end-of-file marker
+  \edef\testii{#2}%
+  \ifx\testii\@relax%
+    % #2 is \relax, so there is nothing more to write.
+    \relax
+    \let\go=\relax
+  \else
+    % #2 is not \relax, so there is another byte to write.
+    \qr at hextobinary[8]{\bytetowrite}{#2#3}%
+    \xdef\qr at datatowrite{\bytetowrite\relax}% %Add terminating "\relax"
+    \qr at writedata@recursive{#1}% %This function actually writes the 8 bits.
+    \edef\qr at argument{{#1}(#4)}%
+    \xa\def\xa\go\xa{\xa\qr at writedata@hex at recursive\qr at argument}% %Call self to write the next bit.
+  \fi
+  \go
+}%
+
+\def\qr at writedata#1#2{%
+  % #1 = name of a matrix that has been prepared with finder patterns, timing patterns, etc.
+  % #2 = a string consisting of 0's and 1's to write into the matrix.
+  \setcounter{qr at i}{\qr at numberofrowsinmatrix{#1}}%
+  \setcounter{qr at j}{\qr at numberofcolsinmatrix{#1}}%
+  \qr at rightcoltrue
+  \qr at goinguptrue
+  \edef\qr at datatowrite{#2\relax}%
+  \qr at writedata@recursive{#1}%
+}%
+
+\def\@@blank{\@blank}%
+
+\def\qr at writedata@recursive#1{%
+  % #1 = matrix name
+  % (qr at i,qr at j) = position to write in (LaTeX counters)
+  % \qr at datatowrite contains the bits to be written, plus '\relax' as an end-of-file marker
+  \xa\let\xa\squarevalue\csname #1@\theqr at i @\theqr at j\endcsname%
+  \ifx\squarevalue\@@blank
+    %Square is blank, so write data in it.
+    \xa\qr at writebit\xa(\qr at datatowrite){#1}%
+    %The \qr at writebit macro not only writes the first bit of \qr at datatowrite into the matrix,
+    %but also removes the bit from the 'bitstream' of \qr at datatowrite.
+  \fi
+  %Now adjust our position in the matrix.
+  \ifqr at rightcol
+    %From the right-hand half of the two-bit column, we always move left.  Easy peasy.
+    \addtocounter{qr at j}{-1}%
+    \qr at rightcolfalse
+  \else
+    %If we're in the left-hand column, things are harder.
+    \ifqr at goingup
+      %First, suppose we're going upwards.
+      \ifnum\c at qr@i>1\relax%
+        %If we're not in the first row, things are easy.
+        %We move one to the right and one up.
+        \addtocounter{qr at j}{1}%
+        \addtocounter{qr at i}{-1}%
+        \qr at rightcoltrue
+      \else
+        %If we are in the first row, then we move to the left,
+        %and we are now in the right-hand column on a downward pass.
+        \addtocounter{qr at j}{-1}%
+        \qr at goingupfalse
+        \qr at rightcoltrue
+      \fi
+    \else
+      %Now, suppose we're going downwards.
+      \xa\ifnum\qr at size>\c at qr@i\relax%
+        %If we're not yet in the bottom row, things are easy.
+        %We move one to the right and one down.
+        \addtocounter{qr at j}{1}%
+        \addtocounter{qr at i}{1}%
+        \qr at rightcoltrue
+      \else
+        %If we are in the bottom row, then we move to the left,
+        %and we are now in the right-hand column on an upward pass.
+        \addtocounter{qr at j}{-1}%
+        \qr at rightcoltrue
+        \qr at goinguptrue
+      \fi
+    \fi
+    %One problem: what if we just moved into the 7th column?
+    %Das ist verboten.
+    %If we just moved (left) into the 7th column, we should move on into the 6th column.
+    \ifnum\c at qr@j=7\relax%
+      \setcounter{qr at j}{6}%
+    \fi
+  \fi
+  %Now check whether there are any more bits to write.
+  \ifx\qr at datatowrite\@relax
+    % \qr at datatowrite is just `\relax', so we're done.
+    \let\nexttoken=\relax
+    \relax
+  \else
+    % Write some more!
+    \def\nexttoken{\qr at writedata@recursive{#1}}%
+  \fi
+  \nexttoken
+}%
+
+\def\qr at writeremainderbits#1{%
+  % #1 = name of a matrix that has been prepared and partly filled.
+  % (qr at i,qr at j) = position to write in LaTeX counters
+  \xa\ifnum\qr at numremainderbits>0\relax
+    \def\qr at datatowrite{}%
+    \qr at for \i = 1 to \qr at numremainderbits by 1%
+      {\g at addto@macro{\qr at datatowrite}{0}}%
+    \g at addto@macro{\qr at datatowrite}{\relax}% terminator
+    \qr at writedata@recursive{#1}%
+  \fi
+}%
+
+\newif\ifqr at cellinmask
+
+\def\qr at setmaskingfunction#1{%
+  % #1 = 1 decimal digit for the mask. (I see no reason to use the 3-bit binary code.)
+  % The current position is (\themaski,\themaskj), with indexing starting at 0.
+  \edef\maskselection{#1}%
+  \xa\ifcase\maskselection\relax
+    %Case 0: checkerboard
+    \def\qr at parsemaskingfunction{%
+      % Compute mod(\themaski+\themaskj,2)%
+      \qr at a=\c at maski%
+      \advance\qr at a by \c at maskj%
+      \qr at b=\qr at a%
+      \divide\qr at b by 2%
+      \multiply\qr at b by 2%
+      \advance\qr at a by -\qr at b%
+      \edef\qr at maskfunctionresult{\the\qr at a}%
+    }%
+  \or
+    %Case 1: horizontal stripes
+    \def\qr at parsemaskingfunction{%
+      % Compute mod(\themaski,2)%
+      \ifodd\c at maski\relax%
+        \def\qr at maskfunctionresult{1}%
+      \else%
+        \def\qr at maskfunctionresult{0}%
+      \fi%
+    }%
+  \or
+    %Case 2: vertical stripes
+    \def\qr at parsemaskingfunction{%
+      % Compute mod(\themaskj,3)%
+      \qr at a=\c at maskj%
+      \divide\qr at a by 3%
+      \multiply\qr at a by 3%
+      \advance\qr at a by -\c at maskj%
+      \edef\qr at maskfunctionresult{\the\qr at a}%
+    }%
+  \or
+    %Case 3: diagonal stripes
+    \def\qr at parsemaskingfunction{%
+      % Compute mod(\themaski+\themaskj,3)%
+      \qr at a=\c at maski%
+      \advance\qr at a by \c at maskj%
+      \qr at b=\qr at a%
+      \divide\qr at b by 3%
+      \multiply\qr at b by 3%
+      \advance\qr at b by -\qr at a%
+      \edef\qr at maskfunctionresult{\the\qr at b}%
+    }%
+  \or
+    %Case 4: wide checkerboard
+    \def\qr at parsemaskingfunction{%
+      % Compute mod(floor(\themaski/2) + floor(\themaskj/3),2) %
+      \qr at a=\c at maski%
+      \divide\qr at a by 2%
+      \qr at b=\c at maskj%
+      \divide\qr at b by 3%
+      \advance\qr at a by \qr at b%
+      \qr at b=\qr at a%
+      \divide\qr at a by 2%
+      \multiply\qr at a by 2%
+      \advance\qr at a by -\qr at b%
+      \edef\qr at maskfunctionresult{\the\qr at a}%
+    }%
+  \or
+    %Case 5: quilt
+    \def\qr at parsemaskingfunction{%
+      % Compute mod(\themaski*\themaskj,2) + mod(\themaski*\themaskj,3) %
+      \qr at a=\c at maski%
+      \multiply\qr at a by \c at maskj%
+      \qr at b=\qr at a%
+      \qr at c=\qr at a%
+      \divide\qr at a by 2%
+      \multiply\qr at a by 2%
+      \advance\qr at a by -\qr at c% (result will be -mod(i*j,2), which is negative.)
+      \divide\qr at b by 3%
+      \multiply\qr at b by 3%
+      \advance\qr at b by -\qr at c% (result will be -mod(i*j,3), which is negative.)
+      \advance\qr at a by \qr at b% (result is negative of what's in the spec.)
+      \edef\qr at maskfunctionresult{\the\qr at a}%
+    }%
+  \or
+    %Case 6: arrows
+    \def\qr at parsemaskingfunction{%
+      % Compute mod( mod(\themaski*\themaskj,2) + mod(\themaski*\themaskj,3) , 2 ) %
+      \qr at a=\c at maski%
+      \multiply\qr at a by \c at maskj%
+      \qr at b=\qr at a%
+      \qr at c=\qr at a%
+      \multiply\qr at c by 2% % \qr at c equals 2*i*j.
+      \divide\qr at a by 2%
+      \multiply\qr at a by 2%
+      \advance\qr at c by -\qr at a% Now \qr at c equals i*j + mod(i*j,2).
+      \divide\qr at b by 3%
+      \multiply\qr at b by 3%
+      \advance\qr at c by -\qr at b% (Now \qr at c equals mod(i*j,2) + mod(i*j,3).
+      \qr at a=\qr at c%
+      \divide\qr at a by 2%
+      \multiply\qr at a by 2%
+      \advance\qr at c by-\qr at a%
+      \edef\qr at maskfunctionresult{\the\qr at c}%
+    }%
+  \or
+    %Case 7: shotgun
+    \def\qr at parsemaskingfunction{%
+      % Compute mod( mod(\themaski+\themaskj,2) + mod(\themaski*\themaskj,3) , 2 ) %
+      \qr at a=\c at maski%
+      \advance\qr at a by \c at maskj% %So \qr at a = i+j
+      \qr at b=\c at maski%
+      \multiply\qr at b by \c at maskj% %So \qr at b = i*j
+      \qr at c=\qr at a%
+      \advance\qr at c by \qr at b% So \qr at c = i+j+i*j
+      \divide\qr at a by 2%
+      \multiply\qr at a by 2%
+      \advance\qr at c by -\qr at a% So \qr at c = mod(i+j,2) + i*j
+      \divide\qr at b by 3%
+      \multiply\qr at b by 3%
+      \advance\qr at c by -\qr at b% So \qr at c = mod(i+j,2) + mod(i*j,3)
+      \qr at a=\qr at c%
+      \divide\qr at c by 2%
+      \multiply\qr at c by 2%
+      \advance\qr at a by -\qr at c%
+      \edef\qr at maskfunctionresult{\the\qr at a}%
+    }%
+  \fi
+}%
+
+\def\qr at checkifcellisinmask{%
+  % The current position is (\i,\j), in TeX counts,
+  % but the LaTeX counters (maski,maskj) should contain
+  % the current position with indexing starting at 0.
+  % That is, maski = \i-1 and maskj = \j-1.
+  %
+  % \qr at parsemaskingfunction must have been set by a call to \qr at setmaskingfunction
+  \qr at parsemaskingfunction
+  \xa\ifnum\qr at maskfunctionresult=0\relax
+    \qr at cellinmasktrue
+  \else
+    \qr at cellinmaskfalse
+  \fi
+}%
+
+\newcounter{maski}%
+\newcounter{maskj}%
+
+\def\qr at applymask#1#2#3{%
+  % #1 = name of a matrix that should be filled out completely
+  %      except for the format and/or version information.
+  % #2 = name of a new matrix to contain the masked version
+  % #3 = 1 decimal digit naming the mask
+  \qr at createduplicatematrix{#2}{#1}%
+  \qr at setmaskingfunction{#3}%
+  \setcounter{maski}{-1}%
+  \qr at for \i = 1 to \qr at size by 1%
+    {\stepcounter{maski}%
+     \setcounter{maskj}{-1}%
+     \qr at for \j = 1 to \qr at size by 1%
+     {\stepcounter{maskj}%
+      \qr at checkifcellisinmask
+      \ifqr at cellinmask
+        \qr at checkifcurrentcellcontainsdata{#2}%
+        \ifqr at currentcellcontainsdata
+          \qr at flipcurrentcell{#2}%
+        \fi
+      \fi
+      }%
+    }%
+}%
+
+\newif\ifqr at currentcellcontainsdata
+\qr at currentcellcontainsdatafalse
+
+\def\@@white{\@white}%
+\def\@@black{\qr at black}%
+
+\def\qr at checkifcurrentcellcontainsdata#1{%
+  % #1 = name of matrix
+  \qr at currentcellcontainsdatafalse
+  \xa\ifx\csname #1@\the\i @\the\j\endcsname\@@white
+    \qr at currentcellcontainsdatatrue
+  \fi
+  \xa\ifx\csname #1@\the\i @\the\j\endcsname\@@black
+    \qr at currentcellcontainsdatatrue
+  \fi
+}%
+
+\def\qr at flipped@black{\qr at black}%
+\def\qr at flipped@white{\@white}%
+
+\def\qr at flipcurrentcell#1{%
+  % #1 = name of matrix
+  % (\i, \j) = current position, in TeX counts.
+  % This assumes the cell contains data, either black or white!
+  \xa\ifx\csname #1@\the\i @\the\j\endcsname\@@white
+    \qr at storetomatrix{#1}{\the\i}{\the\j}{\qr at flipped@black}%
+  \else
+    \qr at storetomatrix{#1}{\the\i}{\the\j}{\qr at flipped@white}%
+  \fi
+}%
+
+\def\qr at chooseandapplybestmask#1{%
+  % #1 = name of a matrix that should be filled out completely
+  %      except for the format and/or version information.
+  % This function applies all eight masks in succession,
+  % calculates their penalties, and remembers the best.
+  % The number indicating which mask was used is saved in \qr at mask@selected.
+  \qr at createduplicatematrix{originalmatrix}{#1}%
+  \message{<Applying Mask 0...}%
+  \qr at applymask{originalmatrix}{#1}{0}%
+  \message{done. Calculating penalty...}%
+  \qr at evaluatemaskpenalty{#1}%
+  \xdef\currentbestpenalty{\qr at penalty}%
+  \message{penalty is \qr at penalty>^^J}%
+  \gdef\currentbestmask{0}%
+  \qr at for \i = 1 to 7 by 1%
+    {\message{<Applying Mask \the\i...}%
+     \qr at applymask{originalmatrix}{currentmasked}{\the\i}%
+     \message{done. Calculating penalty...}%
+     \qr at evaluatemaskpenalty{currentmasked}%
+     \message{penalty is \qr at penalty>^^J}%
+     \xa\xa\xa\ifnum\xa\qr at penalty\xa<\currentbestpenalty\relax
+       %We found a better mask.
+       \xdef\currentbestmask{\the\i}%
+       \qr at createduplicatematrix{#1}{currentmasked}%
+       \xdef\currentbestpenalty{\qr at penalty}%
+     \fi
+    }%
+  \xdef\qr at mask@selected{\currentbestmask}%
+  \message{<Selected Mask \qr at mask@selected>^^J}%
+}%
+
+\def\qr at Ni{3}%
+\def\qr at Nii{3}%
+\def\qr at Niii{40}%
+\def\qr at Niv{10}%
+\def\@fiveones{11111}%
+\def\@fivezeros{11111}%
+\def\@twoones{11}%
+\def\@twozeros{00}%
+\def\@finderA{00001011101}%
+\def\@finderB{10111010000}%
+\def\@finderB at three{1011101000}%
+\def\@finderB at two{101110100}%
+\def\@finderB at one{10111010}%
+\def\@finderB at zero{1011101}%
+\newif\ifstringoffive
+\def\addpenaltyiii{%
+  \addtocounter{penaltyiii}{\qr at Niii}%
+}%
+\newcounter{totalones}%
+\newcounter{penaltyi}%
+\newcounter{penaltyii}%
+\newcounter{penaltyiii}%
+\newcounter{penaltyiv}%
+\def\qr at evaluatemaskpenalty#1{%
+  % #1 = name of a matrix that we will test for the penalty
+  % according to the specs.
+  \setcounter{penaltyi}{0}%
+  \setcounter{penaltyii}{0}%
+  \setcounter{penaltyiii}{0}%
+  \setcounter{penaltyiv}{0}%
+  \bgroup%localize the meanings we give to the symbols
+    \def\qr at black{1}\def\@white{0}%
+    \def\qr at black@fixed{1}\def\qr at white@fixed{0}%
+    \def\qr at format@square{0}% This is not stated in the specs, but seems
+                            % to be the standard implementation.
+    \def\@blank{0}% These would be any bits at the end.
+    %
+    \setcounter{totalones}{0}%
+    \qr at for \i=1 to \qr at size by 1%
+      {\def\lastfive{z}% %The z is a dummy, that will be removed before any testing.
+       \stringoffivefalse
+       \def\lasttwo at thisrow{z}% %The z is a dummy.
+       \def\lasttwo at nextrow{z}% %The z is a dummy.
+       \def\lastnine{z0000}% %The 0000 stands for the white space to the left. The z is a dummy.
+       \def\ignore at finderB@at{0}%
+       \qr at for \j=1 to \qr at size by 1%
+         {\edef\newbit{\qr at matrixentry{#1}{\the\i}{\the\j}}%
+          %
+          % LASTFIVE CODE FOR PENALTY 1
+          % First, add the new bit to the end.
+          \xa\g at addto@macro\xa\lastfive\xa{\newbit}%
+          \ifnum\j<5\relax%
+            %Not yet on the 5th entry.
+            %Don't do any testing.
+          \else
+            % 5th entry or later.
+            % Remove the old one, and then test.
+            \removefirsttoken\lastfive%
+            \ifx\lastfive\@fiveones%
+              \ifstringoffive%
+                %This is a continuation of a previous block of five or more 1's.
+                \stepcounter{penaltyi}%
+              \else
+                %This is a new string of five 1's.
+                \addtocounter{penaltyi}{\qr at Ni}%
+                \global\stringoffivetrue
+              \fi
+            \else
+              \ifx\lastfive\@fivezeros%
+                \ifstringoffive
+                  %This is a continuation of a previous block of five or more 0's.
+                  \stepcounter{penaltyi}%
+                \else
+                  %This is a new string of five 0's.
+                  \addtocounter{penaltyi}{\qr at Ni}%
+                  \global\stringoffivetrue
+                \fi
+              \else
+                %This is not a string of five 1's or five 0's.
+                \global\stringoffivefalse
+              \fi
+            \fi
+          \fi
+          %
+          % 2x2 BLOCKS FOR PENALTY 2
+          % Every 2x2 block of all 1's counts for \qr at Nii penalty points.
+          % We do not need to run this test in the last row.
+          \xa\ifnum\xa\i\xa<\qr at size\relax
+            \xa\g at addto@macro\xa\lasttwo at thisrow\xa{\newbit}%
+            %Compute \iplusone
+            \qr at a=\i\relax%
+            \advance\qr at a by 1%
+            \edef\iplusone{\the\qr at a}%
+            %
+            \edef\nextrowbit{\qr at matrixentry{#1}{\iplusone}{\the\j}}%
+            \xa\g at addto@macro\xa\lasttwo at nextrow\xa{\nextrowbit}%
+            \ifnum\j<2\relax%
+              %Still in the first column; no check.
+            \else
+              %Second column or later.  Remove the old bits, and then test.
+              \removefirsttoken\lasttwo at thisrow
+              \removefirsttoken\lasttwo at nextrow
+              \ifx\lasttwo at thisrow\@twoones
+                \ifx\lasttwo at nextrow\@twoones
+                  \addtocounter{penaltyii}{\qr at Nii}%
+                \fi
+              \else
+                \ifx\lasttwo at thisrow\@twozeros
+                  \ifx\lasttwo at nextrow\@twozeros
+                    \addtocounter{penaltyii}{\qr at Nii}%
+                  \fi
+                \fi
+              \fi
+            \fi
+          \fi
+          %
+          % LASTNINE CODE FOR PENALTY 3
+          % First, add the new bit to the end.
+          \xa\g at addto@macro\xa\lastnine\xa{\newbit}%
+          \ifnum\j<7\relax%
+            %Not yet on the 7th entry.
+            %Don't do any testing.
+          \else
+            % 7th entry or later.
+            % Remove the old one, and then test.
+            \removefirsttoken\lastnine
+            \xa\ifnum\qr at size=\j\relax%
+              % Last column.  Any of the following should count:
+              %     1011101 (\@finderB at zero)
+              %    10111010 (\@finderB at one)
+              %   101110100 (\@finderB at two)
+              %  1011101000 (\@finderB at three)
+              % 10111010000 (\@finderB)
+              \ifx\lastnine\@finderB
+                \addpenaltyiii
+              \else
+                \removefirsttoken\lastnine
+                \ifx\lastnine\@finderB at three
+                  \addpenaltyiii
+                \else
+                  \removefirsttoken\lastnine
+                  \ifx\lastnine\@finderB at two
+                    \addpenaltyiii
+                  \else
+                    \removefirsttoken\lastnine
+                    \ifx\lastnine\@finderB at one
+                      \addpenaltyiii
+                    \else
+                      \removefirsttoken\lastnine
+                      \ifx\lastnine\@finderB at zero
+                        \addpenaltyiii
+                      \fi
+                    \fi
+                  \fi
+                \fi
+              \fi
+            \else
+              \ifx\lastnine\@finderA% %Matches 0000 1011101
+                \addpenaltyiii
+                %Also, we record our discovery, so that we can't count this pattern again
+                %if it shows up four columns later as 1011101 0000.
+                %
+                %Set \ignore at finderB@at to \j+4.
+                \qr at a=\j\relax%
+                \advance\qr at a by 4%
+                \xdef\ignore at finderB@at{\the\qr at a}%
+              \else
+                \ifx\lastfive\@finderB% %Matches 1011101 0000.
+                  \xa\ifnum\ignore at finderB@at=\j\relax
+                    %This pattern was *not* counted already earlier.
+                    \addpenaltyiii
+                  \fi
+                \fi
+              \fi
+            \fi
+          \fi
+          %
+          %COUNT 1's FOR PENALTY 4
+          \xa\ifnum\newbit=1\relax%
+            \stepcounter{totalones}%
+          \fi
+         }% end of j-loop
+      }% end of i-loop
+    %
+    %NOW WE ALSO NEED TO RUN DOWN THE COLUMNS TO FINISH CALCULATING PENALTIES 1 AND 3.
+    \qr at for \j=1 to \qr at size by 1%
+      {\def\lastfive{z}% %The z is a dummy, that will be removed before any testing.
+       \stringoffivefalse
+       \def\lastnine{z0000}% %The 0000 stands for the white space to the left. The z is a dummy.
+       \def\ignore at finderB@at{0}%
+       \qr at for \i=1 to \qr at size by 1%
+         {\edef\newbit{\qr at matrixentry{#1}{\the\i}{\the\j}}%
+          %
+          % LASTFIVE CODE FOR PENALTY 1
+          % First, add the new bit to the end.
+          \xa\g at addto@macro\xa\lastfive\xa{\newbit}%
+          \ifnum\i<5\relax%
+            %Not yet on the 5th entry.
+            %Don't do any testing.
+          \else
+            % 5th entry or later.
+            % Remove the old one, and then test.
+            \removefirsttoken\lastfive%
+            \ifx\lastfive\@fiveones%
+              \ifstringoffive%
+                %This is a continuation of a previous block of five or more 1's.
+                \stepcounter{penaltyi}%
+              \else
+                %This is a new string of five 1's.
+                \addtocounter{penaltyi}{\qr at Ni}%
+                \global\stringoffivetrue
+              \fi
+            \else
+              \ifx\lastfive\@fivezeros%
+                \ifstringoffive
+                  %This is a continuation of a previous block of five or more 0's.
+                  \stepcounter{penaltyi}%
+                \else
+                  %This is a new string of five 0's.
+                  \addtocounter{penaltyi}{\qr at Ni}%
+                  \global\stringoffivetrue
+                \fi
+              \else
+                %This is not a string of five 1's or five 0's.
+                \global\stringoffivefalse
+              \fi
+            \fi
+          \fi
+          %
+          % HAPPILY, WE DON'T NEED TO CALCULATE PENALTY 2 AGAIN.
+          %
+          % LASTNINE CODE FOR PENALTY 3
+          % First, add the new bit to the end.
+          \xa\g at addto@macro\xa\lastnine\xa{\newbit}%
+          \ifnum\i<7\relax%
+            %Not yet on the 7th entry.
+            %Don't do any testing.
+          \else
+            % 7th entry or later.
+            % Remove the old one, and then test.
+            \removefirsttoken\lastnine
+            \xa\ifnum\qr at size=\i\relax%
+              % Last column.  Any of the following should count:
+              %     1011101 (\@finderB at zero)
+              %    10111010 (\@finderB at one)
+              %   101110100 (\@finderB at two)
+              %  1011101000 (\@finderB at three)
+              % 10111010000 (\@finderB)
+              \ifx\lastnine\@finderB
+                \addpenaltyiii
+              \else
+                \removefirsttoken\lastnine
+                \ifx\lastnine\@finderB at three
+                  \addpenaltyiii
+                \else
+                  \removefirsttoken\lastnine
+                  \ifx\lastnine\@finderB at two
+                    \addpenaltyiii
+                  \else
+                    \removefirsttoken\lastnine
+                    \ifx\lastnine\@finderB at one
+                      \addpenaltyiii
+                    \else
+                      \removefirsttoken\lastnine
+                      \ifx\lastnine\@finderB at zero
+                        \addpenaltyiii
+                      \fi
+                    \fi
+                  \fi
+                \fi
+              \fi
+            \else
+              \ifx\lastnine\@finderA% %Matches 0000 1011101
+                \addpenaltyiii
+                %Also, we record our discovery, so that we can't count this pattern again
+                %if it shows up four columns later as 1011101 0000.
+                %
+                %Set \ignore at finderB@at to \i+4.
+                \qr at a=\i\relax%
+                \advance\qr at a by 4%
+                \xdef\ignore at finderB@at{\the\qr at a}%
+              \else
+                \ifx\lastfive\@finderB% %Matches 1011101 0000.
+                  \xa\ifnum\ignore at finderB@at=\i\relax
+                    %This pattern was *not* counted already earlier.
+                    \addpenaltyiii
+                  \fi
+                \fi
+              \fi
+            \fi
+          \fi
+          %
+         }% end of i-loop
+      }% end of j-loop
+  \egroup%
+  %
+  %CALCULATE PENALTY 4
+  %According to the spec, penalty #4 is computed as
+  % floor( |(i/n^2)-0.5|/0.05 )
+  % where i is the total number of 1's in the matrix.
+  % This is equal to abs(20*i-10n^2) div n^2.
+  %
+  \qr at a=\c at totalones\relax
+  \multiply\qr at a by 20\relax
+  \qr at b=\qr at size\relax
+  \multiply\qr at b by \qr at size\relax
+  \qr at c=10\relax
+  \multiply\qr at c by \qr at b\relax
+  \advance\qr at a by -\qr at c\relax
+  \ifnum\qr at a<0\relax
+    \multiply\qr at a by -1\relax
+  \fi
+  \divide\qr at a by \qr at b\relax
+  \setcounter{penaltyiv}{\the\qr at a}%
+  %
+  %CALCULATE TOTAL PENALTY
+  \qr at a=\thepenaltyi\relax%
+  \advance\qr at a by \thepenaltyii\relax%
+  \advance\qr at a by \thepenaltyiii\relax%
+  \advance\qr at a by \thepenaltyiv\relax%
+  \edef\qr at penalty{\the\qr at a}%
+}%
+
+\def\removefirsttoken#1{%
+  %Removes the first token from the macro named in #1.
+  \edef\qr at argument{(#1)}%
+  \xa\removefirsttoken at int\qr at argument%
+  \xdef#1{\removefirsttoken at result}%
+}%
+\def\removefirsttoken at int(#1#2){%
+  \def\removefirsttoken at result{#2}%
+}%
+
+\def\qr at writeformatstring#1#2{%
+  % #1 = matrix name
+  % #2 = binary string representing the encoded and masked format information
+  \setcounter{qr at i}{9}%
+  \setcounter{qr at j}{1}%
+  \edef\qr at argument{{#1}(#2\relax)}%
+  \xa\qr at writeformatA@recursive\qr at argument
+  %
+  \setcounter{qr at i}{\qr at numberofrowsinmatrix{#1}}%
+  \setcounter{qr at j}{9}%
+  \xa\qr at writeformatB@recursive\qr at argument
+}%
+
+\def\qr at writeformatA@recursive#1(#2#3){%
+  % #1 = matrix name
+  % #2 = first bit of string
+  % #3 = rest of bitstream
+  % (qr at i,qr at j) = current (valid) position to write (in LaTeX counters)
+  \def\formattowrite{#3}%
+  \ifnum#2=1\relax
+    \qr at storetomatrix{#1}{\theqr at i}{\theqr at j}{\qr at black@format}%
+  \else
+    \qr at storetomatrix{#1}{\theqr at i}{\theqr at j}{\qr at white@format}%
+  \fi
+  % Now the tricky part--moving \i and \j to their next positions.
+  \ifnum\c at qr@j<9\relax
+    %If we're not yet in column 9, move right.
+    \stepcounter{qr at j}%
+    \ifnum\c at qr@j=7\relax
+      %But we skip column 7!
+      \stepcounter{qr at j}%
+    \fi
+  \else
+    %If we're in column 9, we move up.
+    \addtocounter{qr at i}{-1}%
+    \ifnum\c at qr@i=7\relax
+      %But we skip row 7!
+      \addtocounter{qr at i}{-1}%
+    \fi
+  \fi
+  %N.B. that at the end of time, this will leave us at invalid position (0,9).
+  %That makes for an easy test to know when we are done.
+  \ifnum\c at qr@i<1
+    \let\nexttoken=\relax
+  \else
+    \def\nexttoken{\qr at writeformatA@recursive{#1}(#3)}%
+  \fi
+  \nexttoken
+}%
+
+\def\qr at writeformatB@recursive#1(#2#3){%
+  % #1 = matrix name
+  % #2 = first bit of string
+  % #3 = rest of bitstream
+  % (qr at i,qr at j) = current (valid) position to write (in LaTeX counters)
+  \def\formattowrite{#3}%
+  \ifnum#2=1\relax
+    \qr at storetomatrix{#1}{\theqr at i}{\theqr at j}{\qr at black@format}%
+  \else
+    \qr at storetomatrix{#1}{\theqr at i}{\theqr at j}{\qr at white@format}%
+  \fi
+  % Now the tricky part--moving counters i and j to their next positions.
+  \qr at a=\qr at size%
+  \advance\qr at a by -6\relax%
+  \ifnum\qr at a<\c at qr@i\relax
+    %If we're not yet in row n-6, move up.
+    \addtocounter{qr at i}{-1}%
+  \else
+    \ifnum\qr at a=\c at qr@i\relax
+      %If we're actually in row n-6, we jump to position (9,n-7).
+      \setcounter{qr at i}{9}%
+      %Set counter j equal to \qr at size-7.
+      \global\c at qr@j=\qr at size\relax%
+      \global\advance\c at qr@j by -7\relax%
+    \else
+      %Otherwise, we must be in row 9.
+      %In this case, we move right.
+      \stepcounter{qr at j}%
+    \fi
+  \fi
+  %N.B. that at the end of time, this will leave us at invalid position (9,n+1).
+  %That makes for an easy test to know when we are done.
+  \xa\ifnum\qr at size<\c at qr@j\relax
+    \let\nexttoken=\relax
+  \else
+    \def\nexttoken{\qr at writeformatB@recursive{#1}(#3)}%
+  \fi
+  \nexttoken
+}%
+
+\def\qr at writeversionstring#1#2{%
+  % #1 = matrix name
+  % #2 = binary string representing the encoded version information
+  %
+  % Plot the encoded version string into the matrix.
+  % This is only done for versions 7 and higher.
+  \xa\ifnum\qr at version>6\relax
+    %Move to position (n-8,6).
+    \setcounter{qr at i}{\qr at size}\relax%
+    \addtocounter{qr at i}{-8}\relax%
+    \setcounter{qr at j}{6}%
+    \edef\qr at argument{{#1}(#2\relax)}%
+    \xa\qr at writeversion@recursive\qr at argument
+  \fi
+}%
+
+\def\qr at writeversion@recursive#1(#2#3){%
+  % #1 = matrix name
+  % #2 = first bit of string
+  % #3 = rest of bitstream
+  % (qr at i,qr at j) = current (valid) position to write (in LaTeX counters)
+  %
+  % The version information is stored symmetrically in the matrix
+  % In two transposed regions, so we can write both at the same time.
+  % In the comments, we describe what happens in the lower-left region,
+  % not the upper-right.
+  %
+  \def\versiontowrite{#3}%
+  %
+  %Set \topline equal to n-10.
+  \qr at a=\qr at size\relax%
+  \advance\qr at a by -10\relax%
+  \edef\topline{\the\qr at a}%
+  %
+  \ifnum#2=1\relax
+    \qr at storetomatrix{#1}{\theqr at i}{\theqr at j}{\qr at black@format}%
+    \qr at storetomatrix{#1}{\theqr at j}{\theqr at i}{\qr at black@format}%
+  \else
+    \qr at storetomatrix{#1}{\theqr at i}{\theqr at j}{\qr at white@format}%
+    \qr at storetomatrix{#1}{\theqr at j}{\theqr at i}{\qr at white@format}%
+  \fi
+  % Now the tricky part--moving counters i and j to their next positions.
+  \addtocounter{qr at i}{-1}%
+  \xa\ifnum\topline>\c at qr@i\relax
+    %We've overshot the top of the region.
+    %We need to move left one column and down three.
+    \addtocounter{qr at j}{-1}%
+    \addtocounter{qr at i}{3}%
+  \fi
+  %N.B. that at the end of time, this will leave us at invalid position (n-8,0).
+  %That makes for an easy test to know when we are done.
+  \ifnum\c at qr@j<1\relax
+    \let\nexttoken=\relax
+  \else
+    \def\nexttoken{\qr at writeversion@recursive{#1}(#3)}%
+  \fi
+  \nexttoken
+}%
+\newcounter{qr at hexchars}%
+
+\def\qr at string@binarytohex#1{%
+  \qr at binarytohex{\qr at hex@result}{#1}%
+}%
+
+\def\qr at encode@binary#1{%
+  % #1 = string of ascii characters, to be converted into bitstream
+  %
+  % We do this one entirely in hex, rather than binary, because we can.
+  \edef\plaintext{#1}%
+  %
+  %First, the mode indicator.
+  \def\qr at codetext{4}% %This means `binary'
+  %
+  %Next, the character count.
+  \qr at getstringlength{\plaintext}%
+  %Set \charactercountlengthinhex to \qr at charactercountbits@byte/4%
+  \qr at a=\qr at charactercountbits@byte\relax%
+  \divide \qr at a by 4\relax%
+  \edef\charactercountlengthinhex{\the\qr at a}%
+  \qr at decimaltohex[\charactercountlengthinhex]{\charactercount}{\qr at stringlength}%
+  \xa\g at addto@macro\xa\qr at codetext\xa{\charactercount}%
+  %
+  %Now comes the actual data.
+  \edef\qr at argument{(,\plaintext\relax\relax\relax)}%
+  \xa\qr at encode@ascii at recursive\qr at argument%
+  %
+  %Now the terminator.
+  \g at addto@macro\qr at codetext{0}% %This is '0000' in binary.
+  %
+  %There is no need to pad bits to make a multiple of 8,
+  %because the data length is already 4 + 8 + 8n + 4.
+  %
+  %Now add padding codewords if needed.
+  \setcounter{qr at hexchars}{0}%
+  \qr at getstringlength{\qr at codetext}%
+  \setcounter{qr at hexchars}{\qr at stringlength}%
+  %Set \qr at numpaddingcodewords equal to \qr at totaldatacodewords - hexchars/2.
+  \qr at a=-\c at qr@hexchars\relax
+  \divide\qr at a by 2\relax
+  \advance\qr at a by \qr at totaldatacodewords\relax
+  \edef\qr at numpaddingcodewords{\the\qr at a}%
+  %
+  \xa\ifnum\qr at numpaddingcodewords<0%
+    \edef\ds{ERROR: Too much data!  Over by \qr at numpaddingcodewords bytes.}\show\ds%
+  \fi%
+  \xa\ifnum\qr at numpaddingcodewords>0%
+    \qr at for \i = 2 to \qr at numpaddingcodewords by 2%
+      {\g at addto@macro{\qr at codetext}{ec11}}%
+    \xa\ifodd\qr at numpaddingcodewords\relax%
+      \g at addto@macro{\qr at codetext}{ec}%
+    \fi%
+  \fi%
+}%
+
+\def\qr at encode@ascii at recursive(#1,#2#3){%
+  % #1 = hex codes translated so far
+  % #2 = next plaintext character to translate
+  % #3 = remainder of plaintext
+  \edef\testii{#2}%
+  \ifx\testii\@relax%
+    % All done!
+    \g at addto@macro\qr at codetext{#1}%
+  \else%
+    % Another character to translate.
+    \edef\asciicode{\number`#2}%
+    \qr at decimaltohex[2]{\newhexcodes}{\asciicode}%
+    \edef\qr at argument{(#1\newhexcodes,#3)}%
+    %\show\qr at argument
+    \xa\qr at encode@ascii at recursive\qr at argument%
+  \fi%
+}%
+
+\def\qr at splitcodetextintoblocks{%
+  \setcounter{qr at i}{0}%
+  \qr at for \j = 1 to \qr at numshortblocks by 1%
+    {\stepcounter{qr at i}%
+     \qr at splitoffblock{\qr at codetext}{\theqr at i}{\qr at shortblock@size}%
+    }%
+  \xa\ifnum\qr at numlongblocks>0\relax%
+    \qr at for \j = 1 to \qr at numlongblocks by 1%
+      {\stepcounter{qr at i}%
+       \qr at splitoffblock{\qr at codetext}{\theqr at i}{\qr at longblock@size}%
+      }%
+  \fi%
+}%
+
+\def\qr at splitoffblock#1#2#3{%
+  % #1 = current codetext in hexadecimal
+  % #2 = number to use in csname "\datablock@#2".
+  % #3 = number of bytes to split off
+  \message{<Splitting off block #2>}%
+  \xa\gdef\csname datablock@#2\endcsname{}% %This line is important!
+  \qr at for \i = 1 to #3 by 1%
+    {\edef\qr at argument{{#2}(#1)}%
+     \xa\qr at splitoffblock@int\qr at argument%
+    }%
+}%
+
+\def\qr at splitoffblock@int#1(#2#3#4){%
+  % #1 = number to use in csname "\datablock@#1".
+  % #2#3 = next byte to split off
+  % #4 = remaining text
+  %
+  % We add the next byte to "\datablock@#1",
+  % and we remove it from the codetext.
+  \xa\xdef\csname datablock@#1\endcsname{\csname datablock@#1\endcsname#2#3}%
+  \xdef\qr at codetext{#4}%
+}%
+
+\def\qr at createerrorblocks{%
+  \qr at for \ii = 1 to \qr at numblocks by 1%
+    {\message{<Making error block \the\ii>}%
+     \FX at generate@errorbytes{\csname datablock@\the\ii\endcsname}{\qr at num@eccodewords}%
+     \xa\xdef\csname errorblock@\the\ii\endcsname{\FX at errorbytes}%
+    }%
+}%
+
+\def\qr at interleave{%
+  \setcounter{qr at i}{0}%
+  \def\qr at interleaved@text{}%
+  \message{<Interleaving datablocks of length \qr at shortblock@size\ and \qr at longblock@size: }%
+  \qr at for \ii = 1 to \qr at shortblock@size by 1%
+    {\qr at for \jj = 1 to \qr at numblocks by 1%
+      {\qr at writefromblock{datablock}{\the\jj}%
+      }%
+     \message{\the\ii,}%
+    }%
+  %The long blocks are numbered \qr at numshortblocks+1, \qr at numshortblocks+2, ..., \qr at numblocks.
+  \qr at a=\qr at numshortblocks\relax%
+  \advance\qr at a by 1\relax%
+  \qr at for \jj = \qr at a to \qr at numblocks by 1%
+      {\qr at writefromblock{datablock}{\the\jj}}%
+  \xa\ifnum\qr at numlongblocks>0\relax%
+    \message{\qr at longblock@size.>}%
+  \else
+    \message{.>}%
+  \fi
+  \message{<Interleaving errorblocks of length \qr at num@eccodewords: }%
+  \qr at for \ii = 1 to \qr at num@eccodewords by 1%
+    {\message{\the\ii,}%
+     \qr at for \jj = 1 to \qr at numblocks by 1%
+      {\qr at writefromblock{errorblock}{\the\jj}%
+      }%
+    }%
+  \message{.><Interleaving complete.>}%
+}%
+
+\def\qr at writefromblock#1#2{%
+  % #1 = either 'datablock' or 'errorblock'
+  % #2 = block number, in {1,...,\qr at numblocks}%
+  \edef\qr at argument{(\csname #1@#2\endcsname\relax\relax\relax)}%
+  \xa\qr at writefromblock@int\qr at argument
+  \xa\xdef\csname #1@#2\endcsname{\qr at writefromblock@remainder}%
+}%
+
+\def\qr at writefromblock@int(#1#2#3){%
+  % #1#2 = first byte (in hex) of text, which will be written to \qr at interleaved@text
+  % #3 = remainder, including \relax\relax\relax terminator.
+  \g at addto@macro{\qr at interleaved@text}{#1#2}%
+  \qr at writefromblock@intint(#3)%
+}%
+
+\def\qr at writefromblock@intint(#1\relax\relax\relax){%
+  \xdef\qr at writefromblock@remainder{#1}%
+}%
+\let\xa=\expandafter
+\makeatletter
+
+\def\preface at macro#1#2{%
+  % #1 = macro name
+  % #2 = text to add to front of macro
+  \def\tempb{#2}%
+  \xa\xa\xa\gdef\xa\xa\xa#1\xa\xa\xa{\xa\tempb #1}%
+}%
+
+\newif\ifqr at leadingcoeff
+\def\qr at testleadingcoeff(#1#2){%
+  % Tests whether the leading digit of #1#2 is 1.
+  \ifnum#1=1\relax
+    \qr at leadingcoefftrue
+  \else
+    \qr at leadingcoefffalse
+  \fi
+}%
+
+\def\qr at polynomialdivide#1#2{%
+  \edef\qr at numerator{#1}%
+  \edef\qr at denominator{#2}%
+  \qr at divisiondonefalse%
+  \xa\xa\xa\qr at oneroundofdivision\xa\xa\xa{\xa\qr at numerator\xa}\xa{\qr at denominator}%
+}%
+
+\def\@qr at empty{}%
+\def\qr at oneroundofdivision#1#2{%
+  % #1 = f(x), of degree n
+  % #2 = g(x), of degree m
+  % Obtains a new polynomial h(x), congruent to f(x) modulo g(x),
+  % but of degree at most n-1.
+  %
+  % If leading coefficient of f(x) is 1, subtracts off g(x) * x^(n-m).
+  % If leading coefficient of f(x) is 0, strips off that leading zero.
+  %
+  \qr at testleadingcoeff(#1)%
+  \ifqr at leadingcoeff
+    \qr at xorbitstrings{#1}{#2}%
+    \ifqr at xorfailed
+      %If xor failed, that means our #1 was already the remainder!
+      \qr at divisiondonetrue
+      \edef\theremainder{#1}%
+    \else
+      %xor succeeded. We need to recurse.
+      \xa\xa\xa\edef\xa\xa\xa\qr at numerator\xa\xa\xa{\xa\qr at stripleadingzero\xa(\xorresult)}%
+    \fi
+  \else
+    \xa\def\xa\qr at numerator\xa{\qr at stripleadingzero(#1)}%
+    \ifx\qr at numerator\@qr at empty
+      \qr at divisiondonetrue
+      \def\theremainder{0}%
+    \fi
+  \fi
+  \ifqr at divisiondone
+    \relax
+  \else
+    \xa\qr at oneroundofdivision\xa{\qr at numerator}{#2}%
+  \fi
+}%
+
+\def\qr at stripleadingzero(0#1){#1}%Strips off a leading zero.
+
+\newif\ifqr at xorfailed% This flag will trigger when #2 is longer than #1.
+
+\def\qr at xorbitstrings#1#2{%
+ % #1 = bitstring
+ % #2 = bitstring no longer than #1
+ \qr at xorfailedfalse
+ \edef\qr at argument{(,#1\relax\relax)(#2\relax\relax)}%
+ \xa\qr at xorbitstrings@recursive\qr at argument
+ %\qr at xorbitstrings@recursive(,#1\relax\relax)(#2\relax\relax)%
+}%
+
+\def\qr at xorbitstrings@recursive(#1,#2#3)(#4#5){%
+ % #1#2#3 is the first bitstring, xor'ed up through #1.
+ % #4#5 is the remaining portion of the second bitstring.
+ \def\testii{#2}%
+ \def\testiv{#4}%
+ \ifx\testii\@relax
+   % #1 contains the whole string.
+   % Now if #4 is also \relax, that means the two strings started off with equal lengths.
+   % If, however, #4 is not \relax, that means the second string was longer than the first, a problem.
+   \ifx\testiv\@relax
+     %No problem.  We are done.
+     \qr at xorbit@saveresult(#1#2#3)%
+   \else
+     %Problem!  The second string was longer than the first.
+     \qr at xorfailedtrue
+     \def\xorresult{}%
+   \fi
+ \else
+   % There is still a bit to manipulate in #2.
+   % Check whether #4 contains anything.
+   \ifx\testiv\@relax
+     % No, #4 is empty.  We are done. "#2#3" contains the remainder of the first string,
+     % which we append untouched and then strip off the two \relax-es.
+     \qr at xorbit@saveresult(#1#2#3)%
+   \else
+     % Yes, #4 still has something to XOR. Do the task.
+     \ifnum#2=#4\relax
+       \qr at xorbitstrings@recursive(#1%
+                                 0,#3)(#5)%
+     \else
+       \qr at xorbitstrings@recursive(#1%
+                                 1,#3)(#5)%
+     \fi
+   \fi
+ \fi
+}%
+
+\def\qr at xorbit@saveresult(#1\relax\relax){%
+  %Strips off the extra '\relax'es at the end.
+  \def\xorresult{#1}%
+}%
+
+\newif\ifqr at divisiondone
+\def\dodivision#1#2{%
+  \qr at divisiondonefalse
+  \dodivision at recursive{#1}{#2}%
+}%
+
+\def\BCHcode#1{%
+  \edef\formatinfo{#1}%
+  \def\formatinfopadded{\formatinfo 0000000000}%
+  \def\qr at divisor{10100110111}%
+  \qr at divisiondonefalse
+  \qr at polynomialdivide{\formatinfopadded}{\qr at divisor}%
+  %
+  \qr at getstringlength{\theremainder}%
+  %Run loop from stringlength+1 to 10.
+  \qr at a=\qr at stringlength\relax%
+  \advance\qr at a by 1\relax%
+  \qr at for \i = \qr at a to 10 by 1%
+    {\preface at macro{\theremainder}{0}%
+     \xdef\theremainder{\theremainder}%
+    }%
+  \edef\BCHresult{\formatinfo\theremainder}%
+}%
+
+\def\qr at formatmask{101010000010010}%
+
+\def\qr at encodeandmaskformat#1{%
+  \BCHcode{#1}%
+  \qr at xorbitstrings{\BCHresult}{\qr at formatmask}%
+  \edef\qr at format@bitstring{\xorresult}%
+}%
+
+\def\qr at Golaycode#1{%
+  % #1 = 6-bit version number
+  \edef\qr at versioninfo{#1}%
+  \def\qr at versioninfopadded{\qr at versioninfo 000000000000}% %Append 12 zeros.
+  \def\qr at divisor{1111100100101}%
+  \qr at divisiondonefalse
+  \qr at polynomialdivide{\qr at versioninfopadded}{\qr at divisor}%
+  %
+  \qr at getstringlength{\theremainder}%
+  %Run loop from stringlength+1 to 12.
+  \qr at a=\qr at stringlength\relax%
+  \advance\qr at a by 1\relax%
+  \qr at for \i = \qr at a to 12 by 1%
+    {\preface at macro{\theremainder}{0}%
+     \xdef\theremainder{\theremainder}%
+    }%
+  \edef\Golayresult{\qr at versioninfo\theremainder}%
+}%
+\def\F at result{}%
+
+\def\qr at xorbitstring#1#2#3{%
+  % #1 = new macro to receive result
+  % #2, #3 = bitstrings to xor.  The second can be shorter than the first.
+  \def\qr at xor@result{}%
+  \edef\qr at argument{(#2\relax\relax)(#3\relax\relax)}%
+  \xa\qr at xorbitstring@recursive\qr at argument%
+  \edef#1{\qr at xor@result}%
+}%
+\def\qr at xorbitstring@recursive(#1#2)(#3#4){%
+  \edef\testi{#1}%
+  \ifx\testi\@relax%
+    %Done.
+    \let\qr at next=\relax%
+  \else
+    \if#1#3\relax
+      \g at addto@macro{\qr at xor@result}{0}%
+    \else
+      \g at addto@macro{\qr at xor@result}{1}%
+    \fi
+    \edef\qr at next{\noexpand\qr at xorbitstring@recursive(#2)(#4)}%
+  \fi
+  \qr at next
+}
+
+\def\F at addchar@raw#1#2{%
+  %Add two hexadecimal digits using bitwise xor
+  \qr at hextobinary[4]{\summandA}{#1}%
+  \qr at hextobinary[4]{\summandB}{#2}%
+  \qr at xorbitstring{\F at result}{\summandA}{\summandB}%
+  \qr at binarytohex[1]{\F at result}{\F at result}%
+}%
+
+\def\canceltwos#1{%
+  \edef\qr at argument{(#1\relax\relax)}%
+  \xa\canceltwos at int\qr at argument%
+}%
+
+\def\canceltwos at int(#1#2){%
+  \xa\canceltwos at recursion(,#1#2)%
+}%
+
+\def\canceltwos at recursion(#1,#2#3){%
+  \def\testii{#2}%
+  \ifx\testii\@relax
+    %Cancelling complete.
+    \striptworelaxes(#1#2#3)%
+    %Now \F at result contains the answer.
+  \else
+    \relax
+    \ifnum#2=2\relax
+      \canceltwos at recursion(#10,#3)%
+    \else
+      \canceltwos at recursion(#1#2,#3)%
+    \fi
+  \fi
+}%
+
+\def\striptworelaxes(#1\relax\relax){%
+  \gdef\F at result{#1}%
+}%
+
+\qr at for \i = 0 to 15 by 1%
+  {\qr at decimaltohex[1]{\qr at tempa}{\the\i}%
+   \qr at for \j = 0 to 15 by 1%
+    {\qr at decimaltohex[1]{\qr at tempb}{\the\j}%
+     \F at addchar@raw\qr at tempa\qr at tempb
+     \xa\xdef\csname F at addchar@\qr at tempa\qr at tempb\endcsname{\F at result}%
+    }%
+  }%
+
+\def\F at addchar#1#2{%
+  \xa\def\xa\F at result\xa{\csname F at addchar@#1#2\endcsname}%
+}%
+
+\def\F at addstrings#1#2{%
+  \edef\qr at argument{(,#1\relax\relax)(#2\relax\relax)}%
+  \xa\F at addstrings@recursion\qr at argument%
+}%
+
+\def\F at addstrings@recursion(#1,#2#3)(#4#5){%
+  %Adds two hexadecimal strings, bitwise, from left to right.
+  %The second string is allowed to be shorter than the first.
+  \def\testii{#2}%
+  \def\testiv{#4}%
+  \ifx\testii\@relax
+    %The entire string has been processed.
+    \gdef\F at result{#1}%
+  \else
+    \ifx\testiv\@relax
+      %The second string is over.
+      \striptworelaxes(#1#2#3)%
+      %Now \F at result contains the answer.
+    \else
+      %We continue to add.
+      \F at addchar{#2}{#4}%
+      \edef\qr at argument{(#1\F at result,#3)(#5)}%
+      \xa\F at addstrings@recursion\qr at argument%
+    \fi
+  \fi
+}%
+\gdef\F at stripleadingzero(0#1){\edef\F at result{#1}}%
+
+\setcounter{qr at i}{0}%
+\def\poweroftwo{1}%
+\qr at for \i = 1 to 254 by 1%
+  {\stepcounter{qr at i}%
+   \qr at a=\poweroftwo\relax
+   \multiply\qr at a by 2\relax
+   \edef\poweroftwo{\the\qr at a}%
+   %\show\poweroftwo
+   \qr at decimaltohex[2]{\poweroftwo at hex}{\poweroftwo}%
+   \xa\ifnum\poweroftwo>255\relax
+     %We need to bitwise add the polynomial represented by 100011101, i.e. 0x11d.
+     \F at addstrings{\poweroftwo at hex}{11d}%               %Now it should start with 0.
+     \xa\F at stripleadingzero\xa(\F at result)%              %Now it should be two hex digits.
+     \edef\poweroftwo at hex{\F at result}%                   %Save the hex version.
+     \qr at hextodecimal{\poweroftwo}{\F at result}%
+   \fi
+   \xdef\poweroftwo{\poweroftwo}%
+   \xa\xdef\csname F at twotothe@\theqr at i\endcsname{\poweroftwo at hex}%
+   \xa\xdef\csname F at logtwo@\poweroftwo at hex\endcsname{\theqr at i}%
+  }%
+\xa\xdef\csname F at twotothe@0\endcsname{01}%
+\xa\xdef\csname F at logtwo@01\endcsname{0}%
+
+\def\F at twotothe#1{%
+  \xa\xdef\xa\F at result\xa{\csname F at twotothe@#1\endcsname}%
+}%
+\def\F at logtwo#1{%
+  \xa\xdef\xa\F at result\xa{\csname F at logtwo@#1\endcsname}%
+}%
+
+\def\@zerozero{00}%
+
+\def\F at multiply#1#2{%
+  % #1 and #2 are two elements of F_256,
+  % given as two-character hexadecimal strings.
+  % Multiply them within F_256, and place the answer in \F at result
+  \edef\argA{#1}%
+  \edef\argB{#2}%
+  \ifx\argA\@zerozero
+    \def\F at result{00}%
+  \else
+    \ifx\argB\@zerozero
+      \def\F at result{00}%
+    \else
+      \xa\F at logtwo\xa{\argA}%
+        \edef\logA{\F at result}%
+      \xa\F at logtwo\xa{\argB}%
+        \edef\logB{\F at result}%
+      \xa\qr at a\xa=\logA\relax%  \qr at a = \logA
+      \xa\advance\xa\qr at a\logB\relax% \advance \qr at a by \logB
+      \ifnum\qr at a>254\relax%
+        \advance\qr at a by -255\relax%
+      \fi%
+      \xa\F at twotothe\xa{\the\qr at a}%
+      % Now \F at result contains the product, as desired.
+    \fi
+  \fi
+}%
+
+\def\F at multiply#1#2{%
+  % #1 and #2 are two elements of F_256,
+  % given as two-character hexadecimal strings.
+  % Multiply them within F_256, and place the answer in \F at result
+  \edef\argA{#1}%
+  \edef\argB{#2}%
+  \ifx\argA\@zerozero
+    \def\F at result{00}%
+  \else
+    \ifx\argB\@zerozero
+      \def\F at result{00}%
+    \else
+      \xa\F at logtwo\xa{\argA}%
+        \edef\logA{\F at result}%
+      \xa\F at logtwo\xa{\argB}%
+        \edef\logB{\F at result}%
+      \xa\qr at a\xa=\logA\relax%  \qr at a = \logA
+      \xa\advance\xa\qr at a\logB\relax% \advance \qr at a by \logB
+      \ifnum\qr at a>254\relax%
+        \advance\qr at a by -255\relax%
+      \fi%
+      \xa\F at twotothe\xa{\the\qr at a}%
+      % Now \F at result contains the product, as desired.
+    \fi
+  \fi
+}%
+
+\def\FX at getstringlength#1{%
+  %Count number of two-character coefficients
+  \setcounter{qr at i}{0}%
+  \xdef\qr at argument{(#1\relax\relax\relax)}%
+  \xa\FX at stringlength@recursive\qr at argument%
+  \xdef\stringresult{\arabic{qr at i}}%
+}%
+
+\def\FX at stringlength@recursive(#1#2#3){%
+  \def\testi{#1}%
+  \ifx\testi\@relax
+    %we are done.
+  \else
+    \stepcounter{qr at i}%
+    %\showthe\c at qr@i
+    \qr at stringlength@recursive(#3)%
+  \fi
+}%
+
+\newif\ifFX at leadingcoeff@zero
+\def\FX at testleadingcoeff(#1#2#3){%
+  % Tests whether the leading coefficient of the hex-string #1#2#3 is '00'.
+  \edef\FX at leadingcoefficient{#1#2}%
+  \FX at leadingcoeff@zerofalse
+  \ifx\FX at leadingcoefficient\@zerozero
+    \FX at leadingcoeff@zerotrue
+  \fi
+}%
+
+\newif\ifFX at divisiondone
+
+\newcounter{qr at divisionsremaining} %Keep track of how many divisions to go!
+\def\FX at polynomialdivide#1#2{%
+  \edef\FX at numerator{#1}%
+  \edef\denominator{#2}%
+  \qr at getstringlength\FX at numerator%
+  \setcounter{qr at divisionsremaining}{\qr at stringlength}%
+  \qr at getstringlength\denominator%
+  \addtocounter{qr at divisionsremaining}{-\qr at stringlength}%
+  \addtocounter{qr at divisionsremaining}{2}%
+  \divide\c at qr@divisionsremaining by 2\relax% %2 hex chars per number
+  \FX at divisiondonefalse%
+  \xa\xa\xa\FX at polynomialdivide@recursive\xa\xa\xa{\xa\FX at numerator\xa}\xa{\denominator}%
+}%
+
+\def\FX at polynomialdivide@recursive#1#2{%
+  % #1 = f(x), of degree n
+  % #2 = g(x), of degree m
+  % Obtains a new polynomial h(x), congruent to f(x) modulo g(x),
+  % but of degree at most n-1.
+  %
+  % If leading coefficient of f(x) is 0, strips off that leading zero.
+  % If leading coefficient of f(x) is a, subtracts off a * g(x) * x^(n-m).
+  % N.B. we assume g is monic.
+  %
+  \FX at testleadingcoeff(#1)%
+  \ifFX at leadingcoeff@zero%
+    %Leading coefficient is zero, so remove it.
+    \xa\def\xa\FX at numerator\xa{\FX at stripleadingzero(#1)}%
+  \else%
+    %Leading coefficient is nonzero, and contained in \FX at leadingcoefficient
+    \FX at subtractphase{#1}{#2}{\FX at leadingcoefficient}%
+    \ifFX at subtract@failed%
+      %If subtraction failed, that means our #1 was already the remainder!
+      \FX at divisiondonetrue%
+      \edef\theremainder{#1}%
+    \else%
+      %xor succeeded. We need to recurse.
+      \xa\xa\xa\edef\xa\xa\xa\FX at numerator\xa\xa\xa{\xa\FX at stripleadingzero\xa(\FX at subtraction@result)}%
+    \fi%
+  \fi%
+  \addtocounter{qr at divisionsremaining}{-1}%
+  \ifnum\c at qr@divisionsremaining=0\relax
+    %Division is done!
+    \FX at divisiondonetrue%
+    \edef\theremainder{\FX at numerator}%
+    \relax%
+  \else%
+    \xa\FX at polynomialdivide@recursive\xa{\FX at numerator}{#2}%
+  \fi%
+}%
+
+\def\FX at stripleadingzero(00#1){#1}%Strips off a single leading zero of F_256.
+
+\newif\ifFX at subtract@failed% This flag will trigger when #2 is longer than #1.
+
+\def\FX at subtractphase#1#2#3{%
+ % #1 = bitstring
+ % #2 = bitstring no longer than #1
+ % #3 = leading coefficient
+ \FX at subtract@failedfalse%
+ \edef\qr at argument{(,#1\relax\relax\relax)(#2\relax\relax\relax)(#3)}%
+ \xa\FX at subtract@recursive\qr at argument%
+}%
+
+\def\FX at subtract@recursive(#1,#2#3#4)(#5#6#7)(#8){%
+ % This is a recursive way to compute f(x) - a*g(x)*x^k.
+ % #1#2#3#4 is the first bitstring, subtracted up through #1.
+ %          Thus #2#3 constitutes the next two-character coefficient.
+ % #5#6#7 is the remaining portion of the second bitstring.
+ %          Thus #5#6 constitutes the next two-character coefficient
+ % #8 is the element a of F_256.  It should contain two characters.
+ \def\testii{#2}%
+ \def\testv{#5}%
+ \ifx\testii\@relax
+   % #1 contains the whole string.
+   % Now if #5 is also \relax, that means the two strings started off with equal lengths.
+   % If, however, #5 is not \relax, that means the second string was longer than the first, a problem.
+   \ifx\testv\@relax
+     %No problem.  We are done.
+     \FX at subtract@saveresult(#1#2#3#4)% %We keep the #2#3#4 to be sure we have all three relax-es to strip off.
+   \else
+     %Problem!  The second string was longer than the first.
+     %This usually indicates the end of the long division process.
+     \FX at subtract@failedtrue
+     \def\FX at subtraction@result{}%
+   \fi
+ \else
+   % There is still a coefficient to manipulate in #2#3.
+   % Check whether #5 contains anything.
+   \ifx\testv\@relax
+     % No, #5 is empty.  We are done. "#2#3#4" contains the remainder of the first string,
+     % which we append untouched and then strip off the three \relax-es.
+     \FX at subtract@saveresult(#1#2#3#4)%
+   \else
+     % Yes, #5#6 still has something to XOR. Do the task.
+     \F at multiply{#5#6}{#8}% Multiply by the factor 'a'.
+     \F at addstrings{#2#3}{\F at result}% Subtract.  (We're in characteristic two, so adding works.)
+     \edef\qr at argument{(#1\F at result,#4)(#7)(#8)}%
+     \xa\FX at subtract@recursive\qr at argument%
+   \fi
+ \fi
+}%
+
+\def\FX at subtract@saveresult(#1\relax\relax\relax){%
+  %Strips off the three extra '\relax'es at the end.
+  \def\FX at subtraction@result{#1}%
+}%
+
+\def\FX at creategeneratorpolynomial#1{%
+  % #1 = n, the number of error codewords desired.
+  % We need to create \prod_{j=0}^{n-1} (x-2^j).
+  \edef\FX at generator@degree{#1}%
+  \def\FX at generatorpolynomial{01}% Initially, set it equal to 1.
+  \setcounter{qr at i}{0}%
+  \FX at creategenerator@recursive%
+  %The result is now stored in \FX at generatorpolynomial
+}%
+
+\def\FX at creategenerator@recursive{%
+  % \c at qr@i contains the current value of i.
+  % \FX at generatorpolynomial contains the current polynomial f(x),
+  %   which should be a degree-i polynomial
+  %   equal to \prod_{j=0}^{i-1} (x-2^j).
+  %   (If i=0, then \FX at generatorpolynomial should be 01.)
+  % This recursion step should multiply the existing polynomial by (x-2^i),
+  % increment i by 1, and check whether we're done or not.
+  \edef\summandA{\FX at generatorpolynomial 00}% This is f(x) * x
+  \edef\summandB{00\FX at generatorpolynomial}% This is f(x), with a 0x^{i+1} in front.
+  \F at twotothe{\theqr at i}%
+  \edef\theconstant{\F at result}%
+  \FX at subtractphase{\summandA}{\summandB}{\theconstant}%
+     %This calculates \summandA + \theconstant * \summandB
+     %and stores the result in \FX at subtraction@result
+  \edef\FX at generatorpolynomial{\FX at subtraction@result}%
+  \stepcounter{qr at i}%
+  \xa\ifnum\FX at generator@degree=\c at qr@i\relax%
+    %We just multiplied by (x-2^{n-1}), so we're done.
+    \relax%
+  \else%
+    %We need to do this again!
+    \xa%
+    \FX at creategenerator@recursive%
+  \fi%
+}%
+
+\def\FX at generate@errorbytes#1#2{%
+  % #1 = datastream in hex
+  % #2 = number of error correction bytes requested
+  \edef\numerrorbytes{#2}%
+  \xa\FX at creategeneratorpolynomial\xa{\numerrorbytes}%
+  \edef\FX at numerator{#1}%
+  \qr at for \i = 1 to \numerrorbytes by 1%
+    {\g at addto@macro\FX at numerator{00}}% %One error byte means two hex codes.
+  \FX at polynomialdivide{\FX at numerator}{\FX at generatorpolynomial}%
+  \edef\FX at errorbytes{\theremainder}%
+}%
+\newif\ifqr at versionmodules
+
+\def\qr at level@char#1{%
+    \xa\ifcase#1
+      M\or L\or H\or Q\fi}%
+
+\newif\ifqr at versiongoodenough
+\def\qr at choose@best at version#1{%
+  % \qr at desiredversion = user-requested version
+  % \qr at desiredlevel = user-requested error-correction level
+  \edef\qr at plaintext{#1}%
+  \qr at getstringlength{\qr at plaintext}%
+  %
+  %Run double loop over levels and versions, looking for
+  %the smallest version that can contain our data,
+  %and then choosing the best error-correcting level at that version,
+  %subject to the level being at least as good as the user desires.
+  \global\qr at versiongoodenoughfalse%
+  \gdef\qr at bestversion{0}%
+  \gdef\qr at bestlevel{0}%
+  \ifnum\qr at desiredversion=0\relax
+    \qr at a=1\relax
+  \else
+    \qr at a=\qr at desiredversion\relax
+  \fi
+  \qr at for \i=\qr at a to 40 by 1
+    {\edef\qr at version{\the\i}%
+     \global\qr at versiongoodenoughfalse
+     \qr at for \j=0 to 3 by 1%
+      {%First, we map {0,1,2,3} to {1,0,4,3}, so that we loop through {M,L,H,Q}
+       %in order of increasing error-correction capabilities.
+       \qr at a = \j\relax
+       \divide \qr at a by 2\relax
+       \multiply \qr at a by 4\relax
+       \advance \qr at a by 1\relax
+       \advance \qr at a by -\j\relax
+       \edef\qr at level{\the\qr at a}%
+       \ifnum\qr at desiredlevel=\qr at a\relax
+         \global\qr at versiongoodenoughtrue
+       \fi
+       \ifqr at versiongoodenough
+         \qr at calculate@capacity{\qr at version}{\qr at level}%
+         \xa\xa\xa\ifnum\xa\qr at truecapacity\xa<\qr at stringlength\relax
+           %Too short
+           \relax
+         \else
+           %Long enough!
+           \xdef\qr at bestversion{\qr at version}%
+           \xdef\qr at bestlevel{\qr at level}%
+           \global\i=40%
+         \fi
+       \fi
+      }%
+     }%
+  \edef\qr at version{\qr at bestversion}%
+  \edef\qr at level{\qr at bestlevel}%
+  \xa\ifnum\qr at desiredversion>0\relax
+    \ifx\qr at bestversion\qr at desiredversion\relax
+      %No change from desired version.
+    \else
+      %Version was increased
+      \message{<Requested QR version '\qr at desiredversion' is too small for desired text.}%
+      \message{Version increased to '\qr at bestversion' to fit text.>^^J}%
+    \fi
+  \fi
+  \ifx\qr at bestlevel\qr at desiredlevel\relax
+    %No change in level.
+  \else
+    \message{<Error-correction level increased from \qr at level@char{\qr at desiredlevel}}%
+    \message{to \qr at level@char{\qr at bestlevel} at no cost.>^^J}%
+  \fi
+}%
+
+\def\qr at calculate@capacity#1#2{%
+  \edef\qr at version{#1}%
+  \edef\qr at level{#2}%
+  %Calculate \qr at size, the number of modules per side.
+  % The formula is 4\qr at version+17.
+  \qr at a=\qr at version\relax%
+  \multiply\qr at a by 4\relax%
+  \advance\qr at a by 17\relax%
+  \edef\qr at size{\the\qr at a}%
+  %
+  % Calculate \qr at k, which governs the number of alignment patterns.
+  % The alignment patterns lie in a kxk square, except for 3 that are replaced by finding patterns.
+  % The formula is 2 + floor( \qr at version / 7 ), except that k=0 for version 1.
+  \xa\ifnum\qr at version=1\relax%
+    \def\qr at k{0}%
+  \else%
+    \qr at a=\qr at version\relax
+    \divide \qr at a by 7\relax
+    \advance\qr at a by 2\relax
+    \edef\qr at k{\the\qr at a}%
+  \fi%
+  %
+  %Calculate number of function pattern modules.
+  %This consists of the three 8x8 finder patterns, the two timing strips, and the (k^2-3) 5x5 alignment patterns.
+  %The formula is 160+2n+25(k^2-3)-10(k-2), unless k=0 in which case we just have 160+2n.
+  \qr at a=\qr at size\relax
+  \multiply\qr at a by 2\relax
+  \advance\qr at a by 160\relax
+  \xa\ifnum\qr at k=0\relax\else
+    %\qr at k is nonzero, hence at least 2, so we continue to add 25(k^2-3)-10(k-2).
+    \qr at b=\qr at k\relax
+    \multiply\qr at b by \qr at k\relax
+    \advance\qr at b by -3\relax
+    \multiply\qr at b by 25\relax
+    \advance\qr at a by \qr at b\relax
+    \qr at b=\qr at k\relax
+    \advance\qr at b by -2\relax
+    \multiply\qr at b by 10\relax
+    \advance\qr at a by -\qr at b\relax
+  \fi
+  \edef\qr at numfunctionpatternmodules{\the\qr at a}%
+  %
+  %Calculate the number of version modules, either 36 or 0.
+  \xa\ifnum\qr at version>6\relax
+    \qr at versionmodulestrue
+    \def\qr at numversionmodules{36}%
+  \else
+    \qr at versionmodulesfalse
+    \def\qr at numversionmodules{0}%
+  \fi
+  %
+  %Now calculate the codeword capacity and remainder bits.
+  %Take n^2 modules, subtract all those dedicated to finder patterns etc., format information, and version information,
+  %and what's left is the number of bits we can play with.
+  %The number of complete bytes is \qr at numdatacodewords;
+  %the leftover bits are \qr at numremainderbits.
+  \qr at a=\qr at size\relax
+  \multiply \qr at a by \qr at size\relax
+  \advance \qr at a by -\qr at numfunctionpatternmodules\relax
+  \advance \qr at a by -31\relax% % There are 31 format modules.
+  \advance \qr at a by -\qr at numversionmodules\relax
+  \qr at b=\qr at a\relax
+  \divide \qr at a by 8\relax
+  \edef\qr at numdatacodewords{\the\qr at a}%
+  \multiply\qr at a by 8\relax
+  \advance \qr at b by -\qr at a\relax
+  \edef\qr at numremainderbits{\the\qr at b}%
+  %
+  %The size of the character count indicator also varies by version.
+  %There are only two options, so hardcoding seems easier than expressing these functionally.
+  \xa\ifnum\qr at version<10\relax
+    \def\qr at charactercountbytes@byte{1}%
+    \def\qr at charactercountbits@byte{8}%
+  \else
+    \def\qr at charactercountbytes@byte{2}%
+    \def\qr at charactercountbits@byte{16}%
+  \fi
+  %
+  %Now we call on the table, from the QR specification,
+  %of how many blocks to divide the message into, and how many error bytes each block gets.
+  %This affects the true capacity for data, which we store into \qr at totaldatacodewords.
+  % The following macro sets \qr at numblocks and \qr at num@eccodewords
+  % based on Table 9 of the QR specification.
+  \qr at settableix
+  \qr at a = -\qr at numblocks\relax
+  \multiply \qr at a by \qr at num@eccodewords\relax
+  \advance\qr at a by \qr at numdatacodewords\relax
+  \edef\qr at totaldatacodewords{\the\qr at a}%
+  \advance\qr at a by -\qr at charactercountbytes@byte\relax%Subtract character count
+  \advance\qr at a by -1\relax% Subtract 1 byte for the 4-bit mode indicator and the 4-bit terminator at the end.
+  \edef\qr at truecapacity{\the\qr at a}%
+}
+
+\def\qr at setversion#1#2{%
+  % #1 = version number, an integer between 1 and 40 inclusive.
+  % #2 = error-correction level, as an integer between 0 and 3 inclusive.
+  %      0 = 00 = M
+  %      1 = 01 = L
+  %      2 = 10 = H
+  %      3 = 11 = Q
+  % This macro calculates and sets a variety of global macros and/or counters
+  % storing version information that is used later in construction the QR code.
+  % Thus \setversion should be called every time!
+  %
+  \edef\qr at version{#1}%
+  \edef\qr at level{#2}%
+  %
+  \qr at calculate@capacity{\qr at version}{\qr at level}%
+  %The capacity-check code sets the following:
+  % * \qr at size
+  % * \qr at k
+  % * \ifqr at versionmodules
+  % * \qr at numversionmodules
+  % * \qr at numdatacodewords
+  % * \qr at numremainderbits
+  % * \qr at charactercountbits@byte
+  % * \qr at charactercountbytes@byte
+  % * \qr at numblocks (via \qr at settableix)
+  % * \qr at num@eccodewords (via \qr at settableix)
+  % * \qr at totaldatacodewords
+  %
+  % The alignment patterns' square is 7 modules in from each edge.
+  % They are spaced "as evenly as possible" with an even number of modules between each row/column,
+  % unevenness in division being accommodated by making the first such gap smaller.
+  % The formula seems to be
+  %    general distance = 2*round((n-13)/(k-1)/2+0.25)
+  %                     = 2*floor((n-13)/(k-1)/2+0.75)
+  %                     = 2*floor( (2*(n-13)/(k-1)+3) / 4 )
+  %                     = (((2*(n-13)) div (k-1) + 3 ) div 4 ) * 2
+  %    first distance = leftovers
+  % The 0.25 is to accommodate version 32, which is the only time we round down.
+  % Otherwise a simple 2*ceiling((n-13)/(k-1)/2) would have sufficed.
+  %
+  \qr at a = \qr at size\relax
+  \advance\qr at a by -13\relax
+  \multiply\qr at a by 2\relax
+  \qr at b = \qr at k\relax
+  \advance \qr at b by -1\relax
+  \divide\qr at a by \qr at b\relax
+  \advance\qr at a by 3\relax
+  \divide\qr at a by 4\relax
+  \multiply\qr at a by 2\relax
+  \edef\qr at alignment@generalskip{\the\qr at a}%
+  %
+  %Now set \qr at alignment@firstskip to (\qr at size-13)-(\qr at k-2)*\qr at alignment@generalskip %
+  \qr at a = \qr at k\relax
+  \advance\qr at a by -2\relax
+  \multiply\qr at a by -\qr at alignment@generalskip\relax
+  \advance\qr at a by \qr at size\relax
+  \advance\qr at a by -13\relax
+  \edef\qr at alignment@firstskip{\the\qr at a}%
+  %
+  %
+  %
+  % Our \qr at totaldatacodewords bytes of data are broken up as evenly as possible
+  % into \qr at numblocks datablocks; some may be one byte longer than others.
+  % We set \qr at shortblock@size to floor(\qr at totaldatacodewords / \qr at numblocks)
+  % and \qr at numlongblocks to mod(\qr at totaldatacodewords , \qr at numblocks).
+  \qr at a=\qr at totaldatacodewords\relax
+  \divide\qr at a by \qr at numblocks\relax
+  \edef\qr at shortblock@size{\the\qr at a}%
+  \multiply\qr at a by -\qr at numblocks\relax
+  \advance\qr at a by \qr at totaldatacodewords\relax
+  \edef\qr at numlongblocks{\the\qr at a}%
+  %
+  %Set \qr at longblock@size to \qr at shortblock@size+1.
+  \qr at a=\qr at shortblock@size\relax
+  \advance\qr at a by 1\relax
+  \edef\qr at longblock@size{\the\qr at a}%
+  %
+  %Set \qr at numshortblocks to \qr at numblocks - \qr at numlongblocks
+  \qr at b=\qr at numblocks\relax
+  \advance\qr at b by -\qr at numlongblocks\relax
+  \edef\qr at numshortblocks{\the\qr at b}%
+}%
+
+\def\qr at settableix@int(#1,#2){%
+  \edef\qr at numblocks{#1}%
+  \edef\qr at num@eccodewords{#2}%
+}%
+
+\def\qr at settableix{%
+\xa\ifcase\qr at level\relax
+  %00: Level 'M', medium error correction
+  \edef\tempdata{(%
+    \ifcase\qr at version\relax
+      \relax %There is no version 0.
+    \or1,10%
+    \or1,16%
+    \or1,26%
+    \or2,18%
+    \or2,24%
+    \or4,16%
+    \or4,18%
+    \or4,22%
+    \or5,22%
+    \or5,26%
+    \or5,30%
+    \or8,22%
+    \or9,22%
+    \or9,24%
+    \or10,24%
+    \or10,28%
+    \or11,28%
+    \or13,26%
+    \or14,26%
+    \or16,26%
+    \or17,26%
+    \or17,28%
+    \or18,28%
+    \or20,28%
+    \or21,28%
+    \or23,28%
+    \or25,28%
+    \or26,28%
+    \or28,28%
+    \or29,28%
+    \or31,28%
+    \or33,28%
+    \or35,28%
+    \or37,28%
+    \or38,28%
+    \or40,28%
+    \or43,28%
+    \or45,28%
+    \or47,28%
+    \or49,28%
+  \fi)}%
+\or
+  %01: Level 'L', low error correction
+  \edef\tempdata{%
+  (\ifcase\qr at version\relax
+    \relax %There is no version 0.
+  \or 1,7%
+  \or 1,10%
+  \or 1,15%
+  \or 1,20%
+  \or 1,26%
+  \or 2,18%
+  \or 2,20%
+  \or 2,24%
+  \or 2,30%
+  \or 4,18%
+  \or 4,20%
+  \or 4,24%
+  \or 4,26%
+  \or 4,30%
+  \or 6,22%
+  \or 6,24%
+  \or 6,28%
+  \or 6,30%
+  \or 7,28%
+  \or 8,28%
+  \or 8,28%
+  \or 9,28%
+  \or 9,30%
+  \or 10,30%
+  \or 12,26%
+  \or 12,28%
+  \or 12,30%
+  \or 13,30%
+  \or 14,30%
+  \or 15,30%
+  \or 16,30%
+  \or 17,30%
+  \or 18,30%
+  \or 19,30%
+  \or 19,30%
+  \or 20,30%
+  \or 21,30%
+  \or 22,30%
+  \or 24,30%
+  \or 25,30%
+  \fi)}%
+\or
+  %10: Level 'H', high error correction
+  \edef\tempdata{(%
+    \ifcase\qr at version\relax
+      \relax %There is no version 0.
+    \or1,17%
+    \or1,28%
+    \or2,22%
+    \or4,16%
+    \or4,22%
+    \or4,28%
+    \or5,26%
+    \or6,26%
+    \or8,24%
+    \or8,28%
+    \or11,24%
+    \or11,28%
+    \or16,22%
+    \or16,24%
+    \or18,24%
+    \or16,30%
+    \or19,28%
+    \or21,28%
+    \or25,26%
+    \or25,28%
+    \or25,30%
+    \or34,24%
+    \or30,30%
+    \or32,30%
+    \or35,30%
+    \or37,30%
+    \or40,30%
+    \or42,30%
+    \or45,30%
+    \or48,30%
+    \or51,30%
+    \or54,30%
+    \or57,30%
+    \or60,30%
+    \or63,30%
+    \or66,30%
+    \or70,30%
+    \or74,30%
+    \or77,30%
+    \or81,30%
+  \fi)}%
+\or
+  %11: Level 'Q', quality error correction
+  \edef\tempdata{(%
+    \ifcase\qr at version\relax
+      \relax %There is no version 0.
+    \or1,13%
+    \or1,22%
+    \or2,18%
+    \or2,26%
+    \or4,18%
+    \or4,24%
+    \or6,18%
+    \or6,22%
+    \or8,20%
+    \or8,24%
+    \or8,28%
+    \or10,26%
+    \or12,24%
+    \or16,20%
+    \or12,30%
+    \or17,24%
+    \or16,28%
+    \or18,28%
+    \or21,26%
+    \or20,30%
+    \or23,28%
+    \or23,30%
+    \or25,30%
+    \or27,30%
+    \or29,30%
+    \or34,28%
+    \or34,30%
+    \or35,30%
+    \or38,30%
+    \or40,30%
+    \or43,30%
+    \or45,30%
+    \or48,30%
+    \or51,30%
+    \or53,30%
+    \or56,30%
+    \or59,30%
+    \or62,30%
+    \or65,30%
+    \or68,30%
+    \fi)}%
+\fi
+\xa\qr at settableix@int\tempdata
+}%
+\define at key{qr}{version}{\edef\qr at desiredversion{#1}}%
+\define at key{qr}{level}{\qr at setlevel{#1}}%
+\define at key{qr}{height}{\qr at setheight{#1}}%
+\define at boolkey{qr}[qr@]{tight}[true]{}% %This creates \ifqr at tight and initializes it to false.
+\define at boolkey{qr}[qr@]{padding}[true]{\ifqr at padding\qr at tightfalse\else\qr at tighttrue\fi}% %Define 'padding' as antonym to 'tight'
+
+\def\@qr at M{M}\def\@qr at z{0}%
+\def\@qr at L{L}\def\@qr at i{1}%
+\def\@qr at H{H}\def\@qr at ii{2}%
+\def\@qr at Q{Q}\def\@qr at iii{3}%
+\def\qr at setlevel#1{%
+  \edef\qr at level@selected{#1}%
+  \ifx\qr at level@selected\@qr at M
+    \edef\qr at desiredlevel{0}%
+  \fi
+  \ifx\qr at level@selected\@qr at L
+    \edef\qr at desiredlevel{1}%
+  \fi
+  \ifx\qr at level@selected\@qr at H
+    \edef\qr at desiredlevel{2}%
+  \fi
+  \ifx\qr at level@selected\@qr at Q
+    \edef\qr at desiredlevel{3}%
+  \fi
+  \ifx\qr at level@selected\@qr at z
+    \edef\qr at desiredlevel{0}%
+  \fi
+  \ifx\qr at level@selected\@qr at i
+    \edef\qr at desiredlevel{1}%
+  \fi
+  \ifx\qr at level@selected\@qr at ii
+    \edef\qr at desiredlevel{2}%
+  \fi
+  \ifx\qr at level@selected\@qr at iii
+    \edef\qr at desiredlevel{3}%
+  \fi
+}%
+
+\def\qr at setheight#1{%
+  \setlength{\qr at desiredheight}{#1}%
+}%
+
+\newcommand\qrset[1]{%
+  \setkeys{qr}{#1}%
+}
+
+\qrset{version=0, level=0, tight}
+\newcommand\qrcode[1][]{%
+  \begingroup%
+    \setkeys{qr}{#1}%
+    \bgroup\qr at verbatimcatcodes\qr at setescapedspecials\qrcode at i}%
+
+\def\qrcode at i#1{\xdef\qr at texttoencode{#1}\egroup\qrcode at int\endgroup}%
+
+\def\qrcode at int{%
+  \message{^^J^^J<QR code requested for "\qr at texttoencode" in version
+           \qr at desiredversion-\qr at level@char{\qr at desiredlevel}.>^^J}%
+  %First, choose the version and level.
+  %Recall that \qr at choose@best at version sets \qr at version and \qr at level.
+  \xa\qr at choose@best at version\xa{\qr at texttoencode}%
+  \qr at setversion{\qr at version}{\qr at level}%
+  %
+  %Next, check whether we have already encoded this text at this version
+  %and level.
+  \xa\ifx\csname qr at savedbinarymatrix@\qr at texttoencode @\qr at version @\qr at level\endcsname
+         \relax%
+    %This text has not yet been encoded.
+    \qrcode at int@new%
+  \else
+    %This text has already been encoded!
+    \ifqr at forget@mode
+      %In 'forget' mode, we deliberately recalculate anyway.
+      \qrcode at int@new%
+    \else
+      \qrcode at int@remember%
+    \fi
+  \fi%
+}%
+
+\def\qrcode at int@new{%
+  \qr at createsquareblankmatrix{newqr}{\qr at size}%
+  \qr at placefinderpatterns{newqr}%
+  \qr at placetimingpatterns{newqr}%
+  \qr at placealignmentpatterns{newqr}%
+  \qr at placedummyformatpatterns{newqr}%
+  \qr at placedummyversionpatterns{newqr}%
+  \ifqr at draft@mode
+    \message{<Inserting dummy QR code in draft mode for "\qr at texttoencode" in
+              version \qr at version-\qr at level@char{\qr at level}.>^^J}%
+    \relax% Draft mode---don't load any data or do any work.  Also don't save!
+    \def\qr at format@square{\qr at black}%
+    \def\@blank{\@white}%
+    \fboxsep=-\fboxrule%
+    \fbox{\qr at printmatrix{newqr}}%
+  \else
+    \message{<Calculating QR code for "\qr at texttoencode" in
+              version \qr at version-\qr at level@char{\qr at level}.>^^J}%
+    \xa\qr at encode@binary\xa{\qr at texttoencode}%
+    \qr at splitcodetextintoblocks
+    \qr at createerrorblocks
+    \qr at interleave
+    \message{<Writing data...}%
+    \qr at writedata@hex{newqr}{\qr at interleaved@text}%
+    \message{done.>^^J}%
+    \qr at writeremainderbits{newqr}%
+    \qr at chooseandapplybestmask{newqr}%
+    \qr at decimaltobinary[2]{\level at binary}{\qr at level}%
+    \qr at decimaltobinary[3]{\mask at binary}{\qr at mask@selected}%
+    \edef\formatstring{\level at binary\mask at binary}%
+    \message{<Encoding and writing format string...}%
+    \xa\qr at encodeandmaskformat\xa{\formatstring}%
+    \qr at writeformatstring{newqr}{\qr at format@bitstring}%
+    \message{done.>^^J}%
+    \message{<Encoding and writing version information...}%
+    \qr at decimaltobinary[6]{\version at binary}{\qr at version}%
+    \qr at Golaycode{\version at binary}%
+    \qr at writeversionstring{newqr}{\Golayresult}%
+    \message{done.>^^J}%
+    \message{<Saving QR code to memory...}%
+    \qr at matrixtobinary{newqr}%
+    %
+    %Now save the binary version into TeX's memory for later use in this document.
+    \xa\xdef\csname qr at savedbinarymatrix@\qr at texttoencode @\qr at version @\qr at level\endcsname
+            {\qr at binarymatrix@result}%
+    \message{done.>^^J}%
+    %
+    %Also save the binary version into the aux file, for use in later runs.
+    \message{<Writing QR code to aux file...}%
+    \qr at writebinarymatrixtoauxfile{\qr at binarymatrix@result}%
+    \message{done.>^^J}%
+    \message{<Printing matrix...}%
+    \qr at printmatrix{newqr}%
+    \message{done.>^^J}%
+  \fi
+  \message{^^J}%
+}%
+\def\qrcode at int@remember{%
+  %This text has already been encoded,
+  %so we just copy it from the saved binary string.
+  \message{<Copying the QR code for "\qr at texttoencode" in version \qr at version-\qr at level@char{\qr at level} as previously calculated.>^^J}%
+  \xa\qr at printsavedbinarymatrix\xa{\csname qr at savedbinarymatrix@\qr at texttoencode @\qr at version @\qr at level\endcsname}%
+  %
+  % Now this still might need to be written to the aux file.
+  %
+  \xa\ifx\csname qr at savedflag@\qr at texttoencode @\qr at version @\qr at level\endcsname\@qr at TRUE
+    %Okay, this has already been written to aux file.
+    %Do nothing.
+    \relax%
+  \else%
+    %This has NOT been written to the aux file yet.
+    %We need to do so now.
+    \xa\qr at writebinarymatrixtoauxfile\xa{\csname qr at savedbinarymatrix@\qr at texttoencode @\qr at version @\qr at level\endcsname}%
+  \fi%
+}%
+
+\def\qr at matrixtobinary#1{%
+  \def\qr at binarymatrix@result{}%
+  \bgroup
+    \def\qr at black{1}%
+    \def\@white{0}%
+    \def\@blank{0}%
+    \def\qr at black@fixed{1}%
+    \def\qr at white@fixed{0}%
+    \def\qr at black@format{1}%
+    \def\qr at white@format{0}%
+    %
+    \qr at for \i = 1 to \qr at size by 1%
+      {\qr at for \j = 1 to \qr at size by 1%
+        {\edef\theentry{\qr at matrixentry{#1}{\the\i}{\the\j}}%
+         \xa\g at addto@macro\xa\qr at binarymatrix@result\xa{\theentry}%
+        }%
+      }%
+  \egroup%
+}%
+
+\def\qr at sanitize@output#1{%
+  %Read through ASCII text '#1' and escape backslashes and braces
+  \def\qr at sanitized@result{}%
+  \edef\qr at argument{(#1\relax\relax\relax)}%
+  \xa\qr at sanitize@output at int\qr at argument%
+}
+
+\def\qr at sanitize@output at int(#1#2){%
+  % #1 = first character
+  % #2 = rest of output, including terminator
+  \edef\testi{#1}%
+  \ifx\testi\@relax
+    % Done.
+    \let\qr at next=\relax
+  \else
+    \ifx\testi\qr at otherrightbrace
+      \edef\qr at sanitized@result{\qr at sanitized@result\qr at otherbackslash}%
+      \else\ifx\testi\qr at otherleftbrace
+        \edef\qr at sanitized@result{\qr at sanitized@result\qr at otherbackslash}%
+        \else\ifx\testi\qr at otherbackslash
+          \edef\qr at sanitized@result{\qr at sanitized@result\qr at otherbackslash}%
+        \fi
+      \fi
+    \fi
+    \edef\qr at sanitized@result{\qr at sanitized@result#1}%
+    \def\qr at next{\qr at sanitize@output at int(#2)}%
+  \fi
+  \qr at next
+}
+
+\def\@qr at TRUE{TRUE}%
+\def\qr at writebinarymatrixtoauxfile#1{%
+  \qr at sanitize@output{\qr at texttoencode}%
+  \edef\theargument{{\qr at sanitized@result}{\qr at version}{\qr at level}{#1}}%
+  \xa\write\xa\@auxout\xa{\xa\string\xa\qr at savematrix\theargument}%
+  %
+  % Now set a flag, so we don't write this again.
+  \xa\gdef\csname qr at savedflag@\qr at texttoencode @\qr at version @\qr at level\endcsname{TRUE}%
+}%
+
+\gdef\dummyqrsavedefinition{}%
+\begingroup
+  \catcode`\#=12\relax
+  \catcode`\<=1\relax
+  \catcode`\{=12\relax
+  \catcode`\>=2\relax
+  \catcode`\}=12\relax
+  \catcode`\|=0\relax
+  \catcode`\\=12|relax
+  |gdef|dummyqrsavedefinition<%
+    \ifx\qr at savematrix\@undefined%
+      \def\qr at savematrix{\begingroup\let\do\@makeother\dospecials\catcode`\{=1\catcode`\}=2\relax
+                         \qr at savematrix@int}%
+      \def\qr at savematrix@int#1#2#3#4{\endgroup}%
+    \fi%
+  >
+|endgroup
+
+\edef\qr at argument{(\dummyqrsavedefinition)}%
+\xa\write\xa\@auxout\xa{\dummyqrsavedefinition}%
+
+\def\qr at savematrix{\bgroup\qr at verbatimcatcodes\qr at setescapedspecials\qr at savematrix@int}%
+
+\def\qr at savematrix@int#1{\xdef\qr at savedmatrix@name{#1}\egroup\qr at savematrix@int at int}%
+
+\def\qr at savematrix@int at int#1#2#3{%
+  % \qr at savedmatrix@name = encoded text
+  % #1 = version
+  % #2 = level
+  % #3 = binary text
+  \def\ds{<Reading QR code for "\qr at savedmatrix@name" at level #1-\qr at level@char{#2} from aux file.>^^J}\xa\message\xa{\ds}%
+  {\let\%=\qr at otherpercent
+   \xa\gdef\csname qr at savedbinarymatrix@\qr at savedmatrix@name @#1@#2\endcsname{#3}%
+  }%
+}%
+\endinput
+%%
+%% End of file `qrcode.sty'.
diff --git a/tex/sdaps.cls b/tex/sdaps.cls
new file mode 100644
index 0000000..c67289b
--- /dev/null
+++ b/tex/sdaps.cls
@@ -0,0 +1,1266 @@
+%% start of file `sdaps.cls'.
+%% Copyright 2010-2013 Ferdinand Schwenk (ferdisdot at gmail.com).
+%% Copyright 2011-2013 Benjamin Berg (benjamin at sipsolutions.net).
+%% Copyright 2010 Tobias Simon <tobsimon at googlemail.com>
+%
+% This work may be distributed and/or modified under the
+% conditions of the LaTeX Project Public License, either version 1.3c
+% of this license or (at your option) any later version.
+% The latest version of this license is in
+%   http://www.latex-project.org/lppl.txt
+
+%-------------------------------------------------------------------------------
+% identification
+%-------------------------------------------------------------------------------
+\NeedsTeXFormat{LaTeX2e}
+\ProvidesClass{sdaps}[2013/03/10%
+                      v1.0 Class for composing questionairs for SDAPS]
+
+% Load scrbase for keyval support
+\RequirePackage{scrkbase}
+\RequirePackage{xargs}
+
+%-------------------------------------------------------------------------------
+% debugging
+%-------------------------------------------------------------------------------
+\newif\if at DEBUG\@DEBUGfalse
+
+
+%-------------------------------------------------------------------------------
+% option processing
+%-------------------------------------------------------------------------------
+% enable stamps
+\newif\if at STAMP\@STAMPfalse
+\DeclareOption{stamp}{\@STAMPtrue}
+\DeclareOption{nostamp}{\@STAMPfalse}
+
+% enable pagemarks
+\newif\if at PAGEMARK\@PAGEMARKfalse
+\DeclareOption{pagemark}{\@PAGEMARKtrue}
+\DeclareOption{nopagemark}{\@PAGEMARKfalse}
+
+% Whether sdaps should print the questionnaire id
+\newif\if at PrintQuestionnaireId\@PrintQuestionnaireIdfalse
+\DeclareOption{no_print_questionnaire_id}{\@PrintQuestionnaireIdfalse}
+\DeclareOption{print_questionnaire_id}{\@PrintQuestionnaireIdtrue}
+
+% Whether sdaps should print the questionnaire id
+\newif\if at PrintSurveyId\@PrintSurveyIdtrue
+\DeclareOption{no_print_survey_id}{\@PrintSurveyIdfalse}
+\DeclareOption{print_survey_id}{\@PrintSurveyIdtrue}
+
+% pass unknown options to scrartcl
+\DeclareOption*{\PassOptionsToClass{\CurrentOption}{scrartcl}}
+
+\newif\if at sdaps@draft\@sdaps at drafttrue
+\DeclareOption{final}{\@sdaps at draftfalse}
+
+\newif\if at style@classic\@style at classicfalse
+\newif\if at style@codecxxviii\@style at codecxxviiitrue
+\newif\if at style@qr\@style at qrfalse
+\def\@sdaps at style{code128}%
+
+\newif\if at checkmode@checkcorrect\@checkmode at checkcorrecttrue
+\newif\if at checkmode@check\@checkmode at checkfalse
+\newif\if at checkmode@fill\@checkmode at fillfalse
+\def\@sdaps at checkmode{checkcorrect}%
+
+
+\DefineFamily{SDAPS}
+\DefineFamilyMember{SDAPS}
+\DefineFamilyKey{SDAPS}{style}[code128]%
+{%
+  \KOMA at set@ncmdkey{style}{@tempa}{%
+    {classic}{0},%
+    {code128}{1},%
+    {custom}{2},%
+    {qr}{3}%
+  }{#1}%
+  \ifcase \@tempa\relax
+    \@style at classictrue%
+    \@style at codecxxviiifalse%
+    \@style at qrfalse%
+    \def\@sdaps at style{classic}%
+  \or
+    \@style at classicfalse%
+    \@style at codecxxviiitrue%
+    \@style at qrfalse%
+    \def\@sdaps at style{code128}%
+  \or
+    \@style at classicfalse%
+    \@style at codecxxviiifalse%
+    \@style at qrfalse%
+    \def\@sdaps at style{custom}%
+  \or
+    \@style at classicfalse%
+    \@style at codecxxviiifalse%
+    \@style at qrtrue%
+    \def\@sdaps at style{qr}%
+  \fi
+}
+
+\DefineFamilyKey{SDAPS}{checkmode}[checkcorrect]%
+{%
+  \KOMA at set@ncmdkey{checkmode}{@tempa}{%
+    {checkcorrect}{0},%
+    {check}{1},%
+    {fill}{2}%
+  }{#1}%
+  \ifcase \@tempa\relax
+    \def\@sdaps at checkmode{checkcorrect}%
+    \@checkmode at checkcorrecttrue%
+    \@checkmode at checkfalse%
+    \@checkmode at fillfalse%
+  \or
+    \def\@sdaps at checkmode{check}%
+    \@checkmode at checkcorrectfalse%
+    \@checkmode at checktrue%
+    \@checkmode at fillfalse%
+  \or
+    \def\@sdaps at checkmode{fill}%
+    \@checkmode at checkcorrectfalse%
+    \@checkmode at checkfalse%
+    \@checkmode at filltrue%
+  \fi
+}
+
+
+% If it is empty, it will not be printed out
+\def\globalid{}
+\def\globalidlabel{}
+
+\DefineFamilyKey{SDAPS}{globalid}[]%
+{%
+  \def\globalid{#1} %
+}
+
+\DefineFamilyKey{SDAPS}{globalidlabel}[]%
+{%
+  \def\globalidlabel{#1} %
+}
+
+% Set default options for scrartcl. Is this done correctly?
+\PassOptionsToClass{headings=small}{scrartcl}
+\PassOptionsToClass{twoside}{scrartcl}
+% Well, I don't think that this worked in the past ...
+%\PassOptionsToClass{10pt}{scrartcl}
+
+% process given options
+\FamilyProcessOptions{SDAPS}\relax
+
+%-------------------------------------------------------------------------------
+% load base-class
+%-------------------------------------------------------------------------------
+\LoadClass{scrartcl}
+
+
+%-------------------------------------------------------------------------------
+% required packages
+%-------------------------------------------------------------------------------
+% geometry package
+\RequirePackage{geometry}
+\geometry{hmargin=13mm}
+\geometry{vmargin=25mm}
+%\geometry{top=21mm}
+%\geometry{bottom=25mm}
+
+% ifthen package
+\RequirePackage{ifthen}
+
+% fontenc package
+\RequirePackage[T1]{fontenc}
+
+% color
+\RequirePackage{color}
+
+% Symbols (boxes)
+\RequirePackage{amssymb}
+
+% For writing out the page number of boxes
+\RequirePackage{refcount}
+
+% Defines the LastPage label
+\RequirePackage{lastpage}
+
+% Environment creation that gets the body as a macro
+\RequirePackage{environ}
+
+% headers and footers
+\usepackage{scrpage2}
+\clearscrheadings
+  \chead[\@author\\\@title]{\@author\\\@title}
+  \cfoot{\sdapspagemark}
+\pagestyle{scrheadings}
+
+% hyperrefs
+\RequirePackage{url}
+\RequirePackage{hyperref}
+\hypersetup{%
+  breaklinks,%
+  baseurl       = http://,%
+  pdfborder     = 0 0 0,%
+  pdfpagemode   = UseNone,%
+  pdfcreator    = \LaTeX{} with `sdaps' class,%
+  pdfproducer   = \LaTeX{}
+}
+
+% graphics
+\RequirePackage{graphicx}
+
+% Section formatting
+\RequirePackage{sectsty}
+
+% table of fixed width
+\RequirePackage{tabularx}
+% display content vertical centered
+\renewcommand\tabularxcolumn[1]{m{#1}}
+\newcolumntype{Y}{>{\raggedleft}X}
+
+% Babel (needed for the translator)
+\RequirePackage{babel}
+
+% Translation
+\RequirePackage{translator}
+\usedictionary{translator-sdaps-dictionary}
+
+% Zeichenprogramm
+\RequirePackage{tikz}
+\usetikzlibrary{calc}
+\usetikzlibrary{positioning}
+\usetikzlibrary{decorations.pathmorphing}
+
+% The pgfgetlastxy macro was only added on 2010-04-09
+% By copying its definition here we can support older PGF versions
+\ifx\pgfgetlastxy\undefined
+  % The definition from newer pgf versions.
+  \def\pgfgetlastxy#1#2{%
+          \edef#1{\the\pgf at x}%
+          \edef#2{\the\pgf at y}%
+  }%
+\fi
+
+% QR Code implementation
+\RequirePackage{qrcode}
+
+% Code 128 implementation in plain TeX
+\input{code128}
+
+% http://www.tex.ac.uk/cgi-bin/texfaq2html?label=isitanum
+\def\IsPositive#1{%
+  TT\fi
+  \ifcat_\ifnum0<0#1 _\else A\fi
+}
+
+\def\@sdaps at STAMP{%
+  \begingroup
+    \begin{scope}[line width=\elementlinewidth, shift={(current page.south west)}]%
+      \if at style@qr
+        \if at PrintQuestionnaireId
+          \begin{scope}[shift={($(\edgeleftmargin, \edgebottommargin)$)}]%
+            \node(barcode)[anchor=south west,outer sep=0,inner sep=0]{\qrcode[version=2,level=H,padding,height=10mm]{\qid}};%
+          \end{scope}
+        \fi
+
+        % We unconditionally print this barcode, it is required for the recognition
+        % process.
+        \begin{scope}[shift={($(\paperwidth, 0) + (-\edgerightmargin, \edgebottommargin)$)}]%
+          \pgfmathsetbasenumberlength{4}%
+          \pgfmathdectobase{\paddedpage}{\thepage}{10}% Yes, padding to 4 chars the hard way
+          \edef\barcodechars{\surveyid\paddedpage}%
+          \node(barcode)[anchor=south east,outer sep=0,inner sep=0]{\qrcode[version=2,level=H,padding,height=10mm]{\barcodechars}};%
+        \end{scope}
+
+        \ifstr{\globalid}{}{}{
+          \begin{scope}[shift={($(\paperwidth/2-\edgerightmargin/2+\edgeleftmargin/2, \edgebottommargin)$)}]%
+            \node(barcode)[anchor=south,outer sep=0,inner sep=0]{\qrcode[version=2,level=H,padding,height=10mm]{\globalid}};%
+          \end{scope}
+        }
+      \fi
+
+      \if at style@codecxxviii
+        \if at PrintQuestionnaireId
+          \begin{scope}[shift={($(\edgeleftmargin, \edgebottommargin) + (\barcodehpad, \barcodevpad)$)}]%
+            \node(barcode)[anchor=south west,outer sep=0,inner sep=0]{\X=\barcodebarwidth\bcorr=\barcodebcorr\barheight=\barcodeheight\expandafter\code\expandafter{\qid}};%
+            \node[below=1mm of barcode,distance=0,anchor=north,outer sep=0,inner sep=0]{\usekomafont{barcodefont}\qid};%
+          \end{scope}
+        \fi
+
+        % We unconditionally print this barcode, it is required for the recognition
+        % process.
+        \begin{scope}[shift={($(\paperwidth, 0) + (-\edgerightmargin, \edgebottommargin) + (-\barcodehpad, \barcodevpad)$)}]%
+          \pgfmathsetbasenumberlength{4}%
+          \pgfmathdectobase{\paddedpage}{\thepage}{10}% Yes, padding to 4 chars the hard way
+          \edef\barcodechars{\surveyid\paddedpage}%
+          \node(barcode)[anchor=south east,outer sep=0,inner sep=0]{\X=\barcodebarwidth\bcorr=\barcodebcorr\barheight=\barcodeheight\expandafter\code\expandafter{\barcodechars}};%
+          \node[below=1mm of barcode,distance=0,anchor=north,outer sep=0,inner sep=0]{\usekomafont{barcodefont}\surveyid\,\paddedpage};%
+        \end{scope}
+
+        \ifstr{\globalid}{}{}{
+          \begin{scope}[shift={($(\paperwidth/2-\edgerightmargin/2+\edgeleftmargin/2, 0) + (0, \edgebottommargin) + (0, \barcodevpad)$)}]%
+            \node(barcode)[anchor=south,outer sep=0,inner sep=0]{\X=\barcodebarwidth\bcorr=\barcodebcorr\barheight=\barcodeheight\expandafter\code\expandafter{\globalid}};%
+            \node[below=1mm of barcode,distance=0,anchor=north,outer sep=0,inner sep=0]{\usekomafont{barcodefont}\ifstr{\globalidlabel}{}{\globalid}{\globalidlabel}};%
+          \end{scope}
+        }
+      \fi
+
+      \if at style@classic
+        \if at PrintQuestionnaireId%
+          \if at PrintSurveyId%
+            \def\@questionnaire_id_shift{\boxpaddings+\codeboxheight}
+          \else
+            \def\@questionnaire_id_shift{0pt}
+          \fi%
+          \begin{scope}[shift={($(\edgeleftmargin, \edgebottommargin) + (2*\boxpaddings, \boxpaddings) + (\cornerboxsize, \@questionnaire_id_shift)$)}]%
+            \draw (0, 0) rectangle (\thecodeboxlength\codeboxstep, \codeboxheight);%
+            \@tempcnta=\value{questionnaireid}%
+            \begin{scope}[shift={(\thecodeboxlength\codeboxstep, 0)}]%
+              \foreach \x in {0,...,\numexpr\thecodeboxlength-1\relax}%
+              {%
+                \ifodd \@tempcnta%
+                  \draw[fill] (-\x\codeboxstep, 0) rectangle +(-\codeboxstep, \codeboxheight);%
+                \fi%
+                \global\divide\@tempcnta by 2
+              }%
+            \end{scope}%
+          \end{scope}%
+          \begin{scope}[shift={($(\paperwidth, 0) + (-\edgerightmargin, \edgebottommargin) + (-2*\boxpaddings, \boxpaddings) + (-\thecodeboxlength\codeboxstep, 0) + (-\cornerboxsize, \@questionnaire_id_shift)$)}]%
+            \draw (0, 0) rectangle (\thecodeboxlength\codeboxstep, \codeboxheight);%
+            \@tempcnta=\value{questionnaireid}%
+            \begin{scope}[shift={(\thecodeboxlength\codeboxstep, 0)}]%
+              \foreach \x in {0,...,\numexpr\thecodeboxlength-1\relax}%
+              {%
+                \ifodd \@tempcnta%
+                  \draw[fill] (-\x\codeboxstep, 0) rectangle +(-\codeboxstep, \codeboxheight);%
+                \fi%
+                \global\divide\@tempcnta by 2
+              }%
+            \end{scope}%
+          \end{scope}%
+          \begin{scope}[shift={($(current page.south) + (0, \edgebottommargin) + (0, \boxpaddings) + (0 ,\@questionnaire_id_shift) + (0, 0.5\codeboxheight)$)}]%
+            \pgfmathsetbasenumberlength{5}\pgfmathdectoBase{\@temphexa}{\value{questionnaireid}}{10}%
+            \node{\usekomafont{questionnaireidfont}{\translate{questionnaireid} \@temphexa}};%
+          \end{scope}%
+        \fi%
+        \if at PrintSurveyId%
+          % survey-id
+          \begin{scope}[shift={($(\edgeleftmargin, \edgebottommargin) + (2*\boxpaddings, \boxpaddings) + (\cornerboxsize, 0)$)}]%
+            \draw (0, 0) rectangle (\thecodeboxlength\codeboxstep, \codeboxheight);%
+            \@tempcnta=\value{surveyidmshw}%
+            \begin{scope}[shift={(\thecodeboxlength\codeboxstep, 0)}]%
+              \foreach \x in {0,...,\numexpr\thecodeboxlength-1\relax}%
+              {%
+                \ifodd \@tempcnta%
+                  \draw[fill] (-\x\codeboxstep, 0) rectangle +(-\codeboxstep, \codeboxheight);%
+                \fi%
+                \global\divide\@tempcnta by 2
+              }%
+            \end{scope}%
+          \end{scope}%
+          \begin{scope}[shift={($(\paperwidth, 0) + (-\edgerightmargin, \edgebottommargin) + (-2*\boxpaddings, \boxpaddings) + (-\thecodeboxlength\codeboxstep, 0) + (-\cornerboxsize, 0)$)}]%
+            \draw (0, 0) rectangle (\thecodeboxlength\codeboxstep, \codeboxheight);%
+            \@tempcnta=\value{surveyidlshw}%
+            \begin{scope}[shift={(\thecodeboxlength\codeboxstep, 0)}]%
+              \foreach \x in {0,...,\numexpr\thecodeboxlength-1\relax}%
+              {%
+                \ifodd \@tempcnta%
+                  \draw[fill] (-\x\codeboxstep, 0) rectangle +(-\codeboxstep, \codeboxheight);%
+                \fi%
+                \global\divide\@tempcnta by 2
+              }%
+            \end{scope}%
+          \end{scope}%
+          \begin{scope}[shift={($(current page.south) + (0, \edgebottommargin) + (0, \boxpaddings) + (0, 0.5\codeboxheight)$)}]%
+            \pgfmathsetbasenumberlength{4}\pgfmathdectoBase{\@temphexa}{\value{surveyidlshw}}{16}%
+            \pgfmathsetbasenumberlength{4}\pgfmathdectoBase{\@temphexb}{\value{surveyidmshw}}{16}%
+            \node{\usekomafont{surveyidfont}{\translate{surveyid} \@temphexb\@temphexa}};%
+          \end{scope}%
+        \fi%
+      \fi
+    \end{scope}%
+  \endgroup
+}
+
+% This needs to be called once for each page!
+\def\sdapspagemark{%
+  \normalfont%
+  \begin{tikzpicture}[remember picture,overlay]%
+    %---------------------------------------------------------------------------
+    % pagemark
+    %---------------------------------------------------------------------------
+    \if at PAGEMARK
+      \begin{scope}[line width=\elementlinewidth, line join=miter]%
+        \begin{scope}[shift={($(current page.north west) + (\edgeleftmargin, -\edgetopmargin)$)}]%
+          \draw (0,-\edgelen) -- (0, 0) -- (\edgelen,0);%
+          \if at style@classic%
+            \ifcase\thepage%
+              % Case 0
+              \draw (\boxpaddings, -\boxpaddings) rectangle +(\cornerboxsize,-\cornerboxsize);%
+            \or%
+              % Case 1
+              \draw (\boxpaddings, -\boxpaddings) rectangle +(\cornerboxsize,-\cornerboxsize);%
+            \or%
+              % Case 2
+              \fill (\boxpaddings, -\boxpaddings) rectangle +(\cornerboxsize,-\cornerboxsize);%
+            \or%
+              % Case 3
+              \fill (\boxpaddings, -\boxpaddings) rectangle +(\cornerboxsize,-\cornerboxsize);%
+            \or%
+              % Case 4
+              \fill (\boxpaddings, -\boxpaddings) rectangle +(\cornerboxsize,-\cornerboxsize);%
+            \or%
+              % Case 5
+              \fill (\boxpaddings, -\boxpaddings) rectangle +(\cornerboxsize,-\cornerboxsize);%
+            \or%
+              % Case 6
+              \draw (\boxpaddings, -\boxpaddings) rectangle +(\cornerboxsize,-\cornerboxsize);%
+            \else%
+              % Optional
+              \draw (\boxpaddings, -\boxpaddings) rectangle +(\cornerboxsize,-\cornerboxsize);%
+            \fi%
+          \fi%
+        \end{scope}%
+        \begin{scope}[shift={($(current page.north east) + (-\edgerightmargin, -\edgetopmargin)$)}]%
+          \draw (0,-\edgelen) -- (0,0) -- (-\edgelen,0);%
+          \if at style@classic%
+            \ifcase\thepage%
+              % Case 0
+              \draw (-\boxpaddings, -\boxpaddings) rectangle +(-\cornerboxsize,-\cornerboxsize);%
+            \or%
+              % Case 1
+              \fill (-\boxpaddings, -\boxpaddings) rectangle +(-\cornerboxsize,-\cornerboxsize);%
+            \or%
+              % Case 2
+              \fill (-\boxpaddings, -\boxpaddings) rectangle +(-\cornerboxsize,-\cornerboxsize);%
+            \or%
+              % Case 3
+              \draw (-\boxpaddings, -\boxpaddings) rectangle +(-\cornerboxsize,-\cornerboxsize);%
+            \or%
+              % Case 4
+              \draw (-\boxpaddings, -\boxpaddings) rectangle +(-\cornerboxsize,-\cornerboxsize);%
+            \or%
+              % Case 5
+              \draw (-\boxpaddings, -\boxpaddings) rectangle +(-\cornerboxsize,-\cornerboxsize);%
+            \or%
+              % Case 6
+              \draw (-\boxpaddings, -\boxpaddings) rectangle +(-\cornerboxsize,-\cornerboxsize);%
+            \else%
+              % Optional
+              \draw (-\boxpaddings, -\boxpaddings) rectangle +(-\cornerboxsize,-\cornerboxsize);%
+            \fi%
+          \fi%
+        \end{scope}%
+        \begin{scope}[shift={($(current page.south west) + (\edgeleftmargin, \edgebottommargin)$)}]%
+          \draw (0,\edgelen) -- (0, 0) -- (\edgelen,0);%
+          \if at style@classic%
+            \ifcase\thepage%
+              % Case 0
+              \draw (\boxpaddings, \boxpaddings) rectangle +(\cornerboxsize,\cornerboxsize);%
+            \or%
+              % Case 1
+              \fill (\boxpaddings, \boxpaddings) rectangle +(\cornerboxsize,\cornerboxsize);%
+            \or%
+              % Case 2
+              \draw (\boxpaddings, \boxpaddings) rectangle +(\cornerboxsize,\cornerboxsize);%
+            \or%
+              % Case 3
+              \fill (\boxpaddings, \boxpaddings) rectangle +(\cornerboxsize,\cornerboxsize);%
+            \or%
+              % Case 4
+              \fill (\boxpaddings, \boxpaddings) rectangle +(\cornerboxsize,\cornerboxsize);%
+            \or%
+              % Case 5
+              \draw (\boxpaddings, \boxpaddings) rectangle +(\cornerboxsize,\cornerboxsize);%
+            \or%
+              % Case 6
+              \draw (\boxpaddings, \boxpaddings) rectangle +(\cornerboxsize,\cornerboxsize);%
+            \else%
+              % Optional
+              \draw (\boxpaddings, \boxpaddings) rectangle +(\cornerboxsize,\cornerboxsize);%
+            \fi%
+          \fi%
+        \end{scope}%
+        \begin{scope}[shift={($(current page.south east) + (-\edgerightmargin, \edgebottommargin)$)}]%
+          \draw (0,\edgelen) -- (0mm, 0mm) -- (-\edgelen,0);%
+          \if at style@classic%
+            \ifcase\thepage%
+              % Case 0
+              \draw (-\boxpaddings, \boxpaddings) rectangle +(-\cornerboxsize,\cornerboxsize);%
+            \or%
+              % Case 1
+              \fill (-\boxpaddings, \boxpaddings) rectangle +(-\cornerboxsize,\cornerboxsize);%
+            \or%
+              % Case 2
+              \draw (-\boxpaddings, \boxpaddings) rectangle +(-\cornerboxsize,\cornerboxsize);%
+            \or%
+              % Case 3
+              \fill (-\boxpaddings, \boxpaddings) rectangle +(-\cornerboxsize,\cornerboxsize);%
+            \or%
+              % Case 4
+              \draw (-\boxpaddings, \boxpaddings) rectangle +(-\cornerboxsize,\cornerboxsize);%
+            \or%
+              % Case 5
+              \draw (-\boxpaddings, \boxpaddings) rectangle +(-\cornerboxsize,\cornerboxsize);%
+            \or%
+              % Case 6
+              \fill (-\boxpaddings, \boxpaddings) rectangle +(-\cornerboxsize,\cornerboxsize);%
+            \else%
+              % Optional
+              \draw (-\boxpaddings, \boxpaddings) rectangle +(-\cornerboxsize,\cornerboxsize);%
+            \fi%
+          \fi%
+        \end{scope}%
+      \end{scope}%
+    \fi
+    %---------------------------------------------------------------------------
+    % stamp
+    %---------------------------------------------------------------------------
+    \if at STAMP
+      \if at twoside
+        \ifodd \thepage%
+          \if at sdaps@lastpage%
+            \ifnum \thepage=1
+              % if we have a one page document, just always stamp the last
+              % page. This means that SDAPS can fall back to simplex mode
+              % automatically.
+              \@sdaps at STAMP
+            \fi
+          \fi
+        \else%
+          % questionnaire-id
+          \@sdaps at STAMP
+        \fi%
+      \else
+        % Always stamp in simplex mode
+        \@sdaps at STAMP
+      \fi
+    \fi
+    %---------------------------------------------------------------------------
+    % watermark for non final mode
+    %---------------------------------------------------------------------------
+    \if at sdaps@draft %
+      \node [rotate=60,scale=10,text opacity=0.2,color=red]%
+      at (current page.center) {\textsc{\translate{draft}}};%
+    \fi %
+  \end{tikzpicture}%
+}
+
+
+%-------------------------------------------------------------------------------
+% Declaration
+%-------------------------------------------------------------------------------
+\newcounter{question}
+\newlength{\boxheight}
+
+\NewEnviron{questionnaire}[1][]{
+  \hypersetup{%
+    pdfauthor     = \@author,%
+    pdftitle      = \@title,%
+    pdfsubject    = sdaps questionnaire \@title,%
+    pdfkeywords   = sdaps questionnaire \@title%
+  }%
+  %
+  \if at PrintQuestionnaireId
+    \immediate\write\sdapsoutfile{PrintQuestionnaireId=1}
+  \else
+    \immediate\write\sdapsoutfile{PrintQuestionnaireId=0}
+  \fi
+  \if at PrintSurveyId
+    \immediate\write\sdapsoutfile{PrintSurveyId=1}
+  \else
+    \immediate\write\sdapsoutfile{PrintSurveyId=0}
+  \fi
+  \immediate\write\sdapsoutfile{Pages=\getpagerefnumber{LastPage}}
+  \pgfpoint{\paperwidth}{\paperheight}
+  \pgfgetlastxy{\@sdaps at width}{\@sdaps at height}
+  \immediate\write\sdapsoutfile{PageSize=\@sdaps at width, \@sdaps at height}
+  \if at twoside
+    \immediate\write\sdapsoutfile{Duplex=True}
+  \else
+    \immediate\write\sdapsoutfile{Duplex=False}
+  \fi
+  \immediate\write\sdapsoutfile{Style=\@sdaps at style}%
+  \immediate\write\sdapsoutfile{CheckMode=\@sdaps at checkmode}%
+  \immediate\write\sdapsoutfile{GlobalID=\globalid}%
+  \immediate\write\sdapsoutfile{GlobalIDLabel=\globalidlabel}%
+  %
+  \foreach \qid in \questionnaireids{%
+    \@sdaps at lastpagefalse
+    \setcounter{page}{1}
+    \if\IsPositive{\qid}%
+      \setcounter{questionnaireid}{\qid}%
+    \else%
+      \setcounter{questionnaireid}{0}%
+    \fi%
+    \setcounter{section}{0}
+
+    \ifstr{#1}{noinfo}{}{
+      \begin{info}
+        \translate{infotext}\\
+        \if at checkmode@checkcorrect
+          \begin{tabularx}{\textwidth}{llcX}
+              \translate{info-cross}: & \checkedbox &\quad & \translate{info-select} \\
+              \translate{info-correct}: & \correctedbox & & \translate{info-mark} \\
+          \end{tabularx}
+        \else
+          \PackageError{sdaps}{Sorry, there is no help text for the checkmode you have choosen right now! Please pass the noinfo optional argument to the questionnaire environment!}\@ehb %
+        \fi
+      \end{info}
+    }
+
+    \BODY
+
+    % Force a stamp on the next last page of the questionnaire
+    \@sdaps at lastpagetrue
+
+    \clearpage
+
+    \immediate\write\sdapsoutfile{}
+    % Just close the file here so nothing is written anymore ...
+    \immediate\closeout\sdapsoutfile%
+    % Do not generate position information from now on
+    \global\@sdaps at outputfalse%
+    % Reset counters for testing purposes
+    \setcounter{checkboxnum}{0}%
+    \setcounter{textboxnum}{0}%
+  }
+}
+
+
+%-------------------------------------------------------------------------------
+%                class definition
+%-------------------------------------------------------------------------------
+% minimal base settings
+\setlength\lineskip{1\p@}
+\setlength\normallineskip{1\p@}
+\renewcommand\baselinestretch{}
+\setlength{\parindent}{0pt}
+\setlength{\parskip}{1.0ex \@plus 1.5ex \@minus -0.25ex}
+%\setlength{\parskip}{0pt}
+\setlength\columnsep{10\p@}
+\setlength\columnseprule{0\p@}
+% Redefine the spacings for \subsection ie. \questions.
+\renewcommand\section{\@startsection {section}{1}{\z@}%
+      {-\parskip}%
+      {\parskip}%
+      {\normalfont\Large\bfseries\SS at sectfont}}
+\renewcommand\subsection{
+  \@startsection{subsection}{2}{\z@}%
+      {0.5\parskip}%
+      {0.25\parskip}% These are deleted again for questions
+      {}%
+}
+\pagestyle{scrheadings}
+\pagenumbering{arabic}
+\raggedbottom
+%\flushbottom
+\onecolumn
+
+\newif\if at sdaps@lastpage\@sdaps at lastpagefalse
+
+% Most of these lengths are hardcoded in the defs.py too. So
+% any modification here will need a corresponding change in
+% defs.py.
+% If you want to make this more configurable, patches are always welcome :-)
+\newlength{\edgeleftmargin}
+\setlength{\edgeleftmargin}{10mm}
+\newlength{\edgerightmargin}
+\setlength{\edgerightmargin}{10mm}
+\newlength{\edgetopmargin}
+\setlength{\edgetopmargin}{12mm}
+\newlength{\edgebottommargin}
+\setlength{\edgebottommargin}{12mm}
+\newlength{\edgelen}
+\setlength{\edgelen}{2.00cm}
+\newlength{\elementlinewidth}
+\setlength{\elementlinewidth}{1.0bp}
+\newlength{\checkboxsize}
+\setlength{\checkboxsize}{3.5mm}
+\newlength{\boxpaddings}
+\setlength{\boxpaddings}{3mm}
+% Subtract outline width
+\addtolength{\checkboxsize}{-\elementlinewidth}
+
+% This padding is applied at the bottom of textboxes (similar to a strut)
+% as they do not have a sane baseline.
+\newlength{\textboxbottompad}
+\setlength{\textboxbottompad}{0.5ex \@plus 1ex \@minus 0.2ex}
+
+% How to draw the checkbox, either "box" or "ellipse" (though ellipse is a circle,
+% because we don't support non-square sizes here for now).
+\def\checkboxstyle{box}
+
+% Settings for cornerboxes (used in classic style)
+
+\newlength{\cornerboxsize}
+\setlength{\cornerboxsize}{3.5mm}
+
+% Settings for codeboxes (used in classic style)
+\newcounter{codeboxlength}
+\setcounter{codeboxlength}{16}
+\newlength{\codeboxstep}
+\setlength{\codeboxstep}{3.5mm}
+\newlength{\codeboxheight}
+\setlength{\codeboxheight}{3.5mm}
+
+% Settings for code128 barcodes (used in code128 style, who would have thought?)
+\newlength{\barcodeheight}
+\setlength{\barcodeheight}{6.5mm}
+% This is the same as the default
+\newlength{\barcodebarwidth}
+\setlength{\barcodebarwidth}{0.33mm}
+% Same as default. Barwidth is decreased for printing by this value.
+\newlength{\barcodebcorr}
+\setlength{\barcodebcorr}{0.020mm}
+
+% The padding on the left/right of a barcode. This is the distance that the
+% barcodes will be printed from the cornermarks
+% Choosen to be the same as the barcode height. Note that the Code-128
+% standard requires a quiet zone of max(10*modulesize, ~6.4mm).
+\newlength{\barcodehpad}
+\setlength{\barcodehpad}{6.5mm}
+
+% This needs to be smaller than 6.5mm because otherwise the content is too close.
+% We set it so that it forms a golden ratio 6.5mm*(sqrt(5/4)-0.5). This means
+% we have about 4.5mm padding to the content.
+\newlength{\barcodevpad}
+\setlength{\barcodevpad}{4.02mm}
+
+\newkomafont{barcodefont}{\ttfamily\footnotesize}
+
+% we need one to consume in the for loop
+\def\questionnaireids{NONE}
+
+% TODO: We could support arbitrary IDs in code128 mode, maybe get rid of
+% this counter, or add a macro for the id and simply not use it in code128 mode?
+\newcounter{questionnaireid}
+\setcounter{questionnaireid}{21845}
+\newkomafont{questionnaireidfont}{\ttfamily\textbf}
+
+\newcounter{surveyidmshw}
+\setcounter{surveyidmshw}{52197}
+\newcounter{surveyidlshw}
+\setcounter{surveyidlshw}{39284}
+\def\surveyid{3420821876}
+\newkomafont{surveyidfont}{\ttfamily\textbf}
+
+\newcounter{choicegroupnumchoices}
+\setcounter{choicegroupnumchoices}{-1}
+
+\newcounter{checkboxnum}
+\setcounter{checkboxnum}{0}
+
+\newcounter{textboxnum}
+\setcounter{textboxnum}{0}
+
+% Variables for markquestions with arbitrary range
+\newcounter{markcheckboxcount}
+\setcounter{markcheckboxcount}{5}
+\newif\if at sdaps@markenvactive\@sdaps at markenvactivefalse
+
+\definecolor{sectionbgcolor}{gray}{0.8}
+\definecolor{sectionfgcolor}{gray}{0.0}
+
+% Execute sdaps.opt file to allow SDAPS to override any options
+\InputIfFileExists{sdaps.opt}{}{}
+
+% Old helper macro, do not use anymore!
+\long\def \protectedimmediatewrite#1#2{%
+  \begingroup
+    \let\protect\noexpand%
+    \immediate\write#1{#2}%
+  \endgroup
+}
+
+%common font settings
+\newkomafont{questionfont}{}
+\newkomafont{choicefont}{}
+%singlemark
+\newkomafont{singlemarkquestionfont}{\usekomafont{questionfont}}
+\newkomafont{singlemarkchoicefont}{\usekomafont{choicefont}}
+%markgroup
+\newkomafont{marklinequestionfont}{\usekomafont{questionfont}}
+\newkomafont{marklinechoicefont}{\usekomafont{choicefont}}
+%choicequestion
+\newkomafont{choiceitemfont}{\usekomafont{choicefont}}
+%choicegroup
+\newkomafont{choicegroupchoicefont}{\usekomafont{questionfont}}
+\newkomafont{choicegrouplinefont}{\usekomafont{choicefont}}
+
+\newif\if at sdaps@output\@sdaps at outputtrue
+
+%-------------------------------------------------------------------------------
+%                style commands definitions
+%-------------------------------------------------------------------------------
+\providecommand{\addinfo}[2]{%
+  \immediate\write\sdapsoutfile{\unexpanded{#1}=\unexpanded{#2}}%
+}
+
+\providecommand{\checkbox}{\@ifstar
+    \checkboxStar%
+    \checkboxNoStar%
+}
+
+\providecommand{\@sdaps at drawbox}{
+  \ifstr{\checkboxstyle}{box}{
+    \draw[line width=\elementlinewidth,fill=white] (0.5\elementlinewidth, 0) rectangle +(\checkboxsize, \checkboxsize);%
+  }{}
+  \ifstr{\checkboxstyle}{ellipse}{
+    \draw[line width=\elementlinewidth,fill=white] ($(0.5\elementlinewidth, 0) + (0.5\checkboxsize, 0.5\checkboxsize)$) circle (0.5\checkboxsize);%
+  }{}
+}
+
+\providecommand{\@sdaps at drawfilledbox}{
+  \ifstr{\checkboxstyle}{box}{
+    \draw[fill,line width=\elementlinewidth] (0.5\elementlinewidth, 0) rectangle +(\checkboxsize, \checkboxsize);%
+  }{}
+  \ifstr{\checkboxstyle}{ellipse}{
+    \draw[fill,line width=\elementlinewidth] ($(0.5\elementlinewidth, 0) + (0.5\checkboxsize, 0.5\checkboxsize)$) circle (0.5\checkboxsize);%
+  }{}
+}
+
+\providecommand{\@sdaps at drawcheck}{
+  \begin{scope}[overlay,decoration={random steps,segment length=4pt,amplitude=1pt}]
+    \draw[line width=\elementlinewidth, decorate] ($(0, 0) - (2pt,2pt)$) -- (0.5\checkboxsize, 0.5\checkboxsize) -- ($(\checkboxsize, \checkboxsize) + (2pt,2pt)$);%
+    \draw[line width=\elementlinewidth, decorate] ($(0, \checkboxsize) + (-2pt,2pt)$) -- (0.5\checkboxsize, 0.5\checkboxsize) -- ($(\checkboxsize, 0) + (2pt,-2pt)$);%
+  \end{scope}
+}
+
+
+\providecommand{\checkboxNoStar}{%
+  \mbox{%
+  \def\@sdaps at tmp{}%
+  \if at sdaps@output%
+    \label{checkbox\thecheckboxnum}%
+    \def\@sdaps at tmp{remember picture}%
+  \fi%
+  \tikz[baseline={max(0ex, 0.5\checkboxsize-1ex+0.5\elementlinewidth)},\@sdaps at tmp]{%
+    \@sdaps at drawbox%
+    %
+    \if at sdaps@output%
+      \pgfsys at getposition{pgfpageorigin}{\@sdaps at pageorigin}%
+      \pgfsys at getposition{\pgfpictureid}{\@sdaps at checkboxpos}%
+      \pgfpointadd{\@sdaps at checkboxpos}{%
+        \pgfpointadd{\@sdaps at pageorigin}{\pgfpoint{0}{-max(0ex, 0.5\checkboxsize-1ex+0.5\elementlinewidth) + \checkboxsize + 0.5\elementlinewidth}}%
+      }%
+      \pgfgetlastxy{\@sdaps at x}{\@sdaps at y}%
+      \pgfpoint{\checkboxsize+\elementlinewidth}{\checkboxsize+\elementlinewidth}%
+      \pgfgetlastxy{\@sdaps at width}{\@sdaps at height}%
+      \immediate\write\sdapsoutfile{Box=Checkbox, \getpagerefnumber{checkbox\thecheckboxnum}, \@sdaps at x, \@sdaps at y, \@sdaps at height, \@sdaps at width, \checkboxstyle}%
+    \fi%
+  }%
+  \addtocounter{checkboxnum}{1}}%
+  \ignorespaces
+}
+\providecommand{\checkboxStar}{%
+  \tikz[baseline={max(0ex, 0.5\checkboxsize-1ex+0.5\elementlinewidth)}]{%
+    \@sdaps at drawbox
+  }%
+  \ignorespaces
+}
+\providecommand{\checkedbox}{%
+  \tikz[baseline={max(0ex, 0.5\checkboxsize-1ex+0.5\elementlinewidth)}]{%
+    \@sdaps at drawbox
+    \@sdaps at drawcheck
+  }%
+  \ignorespaces
+}
+\providecommand{\filledbox}{%
+  \tikz[baseline={max(0ex, 0.5\checkboxsize-1ex+0.5\elementlinewidth)}]{%
+    \@sdaps at drawfilledbox
+  }%
+  \ignorespaces
+}
+\providecommand{\correctedbox}{%
+  \tikz[baseline={max(0ex, 0.5\checkboxsize-1ex+0.5\elementlinewidth)}]{%
+    \@sdaps at drawfilledbox
+    \@sdaps at drawcheck
+  }%
+  \ignorespaces
+}
+
+\def\smallskip{\vspace\smallskipamount}
+\def\medskip{\vspace\medskipamount}
+\def\bigskip{\vspace\bigskipamount}
+\newskip\smallskipamount \smallskipamount=3pt  plus 1pt minus 1pt
+\newskip\medskipamount   \medskipamount  =6pt  plus 2pt minus 2pt
+\newskip\bigskipamount   \bigskipamount  =12pt plus 4pt minus 4pt
+
+%-------------------------------------------------------------------------------
+%                structure commands definitions
+%-------------------------------------------------------------------------------
+\renewcommand{\author}[1]{\def\@author{#1}\immediate\write\sdapsoutfile{Author=\unexpanded{#1}}}
+\renewcommand{\title}[1]{\def\@title{#1}\immediate\write\sdapsoutfile{Title=\unexpanded{#1}}}
+
+\def\question#1{%
+\ifx&#1&%
+  % #1 is empty
+  \refstepcounter{subsection}%
+ \else%
+  % #1 is nonempty
+  \subsection{\usekomafont{questionfont}\strut#1\strut}%
+  % This is extra spacing after the subsection is removed. By doing this we
+  % get exactly one \parskip
+  \nobreak%
+  \vspace{-0.25\parskip}%
+  \nobreak%
+\fi%
+}
+
+% http://mrunix.de/forums/showthread.php?t=59943
+\newenvironment{info}{%
+  \vspace{\baselineskip}\vspace{\lineskip}\vspace{-1.0em}%
+  \hrule height 1pt%
+  \vspace{\lineskip}%
+}{%
+  \vspace{\lineskip}%
+  \leavevmode\noindent\hrule height 1pt%
+  \vspace{\baselineskip}\vspace{\lineskip}\vspace{-1.0em}%
+}
+
+% commands
+\providecommand{\textbox}{\@ifstar
+    {\sdaps at textbox{}}%
+    {\sdaps at textbox{\cleaders\sides\vfill}}%
+}
+\newdimen\@tempdimfillwidth%
+\providecommand{\sdaps at textbox}[3]{%
+  % Both baselineskip and lineskip?
+  \question{#3}%
+  \if at sdaps@output%
+    \immediate\write\sdapsoutfile{QObject-Text=\arabic{section}.\arabic{subsection}. \unexpanded{#3}} %
+    \label{textbox\thetextboxnum}%
+    \pgfsys at getposition{pgfpageorigin}{\@sdaps at pageorigin}%
+    \pgfsys at getposition{textboxtop\thetextboxnum}{\@sdaps at textboxtoppos}%
+    \pgfpointadd{\@sdaps at textboxtoppos}{%
+      \pgfpointadd{\@sdaps at pageorigin}{\pgfpoint{0}{0}}%
+    }%
+    \pgfgetlastxy{\@sdaps at x}{\@sdaps at y}%
+%
+    \pgfsys at getposition{textboxbottom\thetextboxnum}{\@sdaps at textboxbottompos}%
+    \pgfpointadd{\@sdaps at textboxtoppos}{%
+      \pgfpointadd{\pgfpointscale{-1}{\@sdaps at textboxbottompos}}{%
+        % Assume width is equal to \linewidth
+        \pgfpoint{\linewidth}{0}%
+      }%
+    }%
+    \pgfgetlastxy{\@sdaps at width}{\@sdaps at height}%
+    \immediate\write\sdapsoutfile{Box=Textbox, \getpagerefnumber{textbox\thetextboxnum}, \@sdaps at x, \@sdaps at y, \@sdaps at width, \@sdaps at height}%
+  \fi%
+  \nobreak%
+  \vspace{\parskip}%
+  \nobreak%
+  \begingroup %
+    \setlength\@tempdima{#2}%
+    \setlength\@tempdimb{\lineskip}%
+    \setlength\@tempdimfillwidth{\linewidth}%
+    \advance\@tempdimfillwidth by -2\elementlinewidth
+    \offinterlineskip
+    \def\sides{%
+      \hbox{\raise -\@tempdima \hbox to\linewidth{%
+        \vrule depth 0pt height \@tempdima width \elementlinewidth%
+        {\color{white}\vrule depth 0pt height \@tempdima width \@tempdimfillwidth}%
+        \vrule depth 0pt height \@tempdima width \elementlinewidth%
+      }}%
+    }%
+    \if at sdaps@output%
+      \pgfsys at markposition{textboxtop\thetextboxnum}%
+    \fi%
+    \hrule width \linewidth height \elementlinewidth depth 0pt%
+    \nobreak%
+    \sides%
+    \nobreak%
+    \kern-0.5\@tempdima%
+    \nobreak%
+    #1%
+    \nobreak%
+    \kern-0.5\@tempdima%
+    \nobreak\sides\nobreak%
+    % Undo the skip following \leavevmode
+    \vspace{-\parskip}\nobreak%
+    \vspace{-\@tempdimb}\nobreak%
+    \vspace{-\@tempdima}\nobreak%
+    % Not entirely sure why this is needed ... but with it the lines join
+    % exactly.
+    \vspace{\elementlinewidth}\nobreak%
+    % This needs some explanation: ie. why do we do a leavevmode, and then
+    % raisebox, etc.
+    % The idea is that the baseline for the last vbox much higher than the
+    % lower line of the textbox. By doing this the minimum baseline
+    % distance is not taken into account for the next line after the textbox.
+    % The result is that the spacing after a freeform text field, and the
+    % spacing after a choicegroup with a choiceitemtext is the same.
+    % Seems insane? Maybe it is :-)
+    % And maybe there are better ways of doing this ...
+    \leavevmode\nobreak%
+    \hbox{\raise -\@tempdima \hbox to \linewidth {%
+      % Mark at the bottom left (right underneath the top mark)
+      \if at sdaps@output%
+        \pgfsys at markposition{textboxbottom\thetextboxnum}%
+      \fi%
+      \cleaders\hrule height \elementlinewidth depth 0pt \hfill%
+      % And add the bottom padding
+      \vrule depth \textboxbottompad height 0pt width 0pt%
+      }%
+    }%
+    \addtocounter{textboxnum}{1}%
+  \endgroup%
+}
+
+\newcommand{\sectbox}[1]{%
+ \noindent\protect\colorbox{sectionbgcolor}{%
+   \@tempdima=\hsize
+   \advance\@tempdima by-2\fboxsep
+   \protect\parbox{\@tempdima}{%
+     \smallskip
+     \raggedright % extra commands here
+     \color{sectionfgcolor}\usekomafont{section}{#1} \smallskip
+    }%
+  }%
+}
+
+\let\origsection\section
+\renewcommand{\section}[1]{%
+  \origsection{#1}
+  \immediate\write\sdapsoutfile{}
+  \immediate\write\sdapsoutfile{QObject-Head=\arabic{section}. \unexpanded{#1}}
+}
+
+
+\newenvironmentx{choicegroup}[2][2={X*{\thechoicegroupnumchoices}{c}}]{%
+  \question{#1}%
+  \immediate\write\sdapsoutfile{QObject-Head=\arabic{section}.\arabic{subsection}. \unexpanded{#1}} %
+  \setcounter{choicegroupnumchoices}{0} %
+  \global\def\@sdaps at choicegroup@start{\tabularx{\linewidth}{#2}}%
+  \global\def\@sdaps at choicegroup@boxes{} %
+}{%
+  \ifnum\thechoicegroupnumchoices>-1 %
+    % Throw error \@ehb == you have lost some text
+    \PackageError{sdaps}{Got a choicegroup without any choice lines!}\@ehb %
+  \fi %
+  \endtabularx%
+}
+
+\providecommand*{\choiceline}[1]{%
+  \@sdaps at choicegroup@start %
+  \global\def\@sdaps at choicegroup@start{} %
+  % This is not really elegant, couldn't we just check whether choicegroup at start
+  % is empty?
+  \ifnum\thechoicegroupnumchoices>0
+    % Move to the next line (as it is not part of the @start macro)
+    \\ %
+    \setcounter{choicegroupnumchoices}{-1} %
+  \fi %
+  \immediate\write\sdapsoutfile{QObject-Choice=XAUTO. \unexpanded{#1}}%
+  {\usekomafont{choicegrouplinefont}\strut#1\strut} \@sdaps at choicegroup@boxes \\ %
+}
+
+\providecommand*{\groupaddchoice}[1]{%
+  \ifnum\thechoicegroupnumchoices<0
+    % Throw error \@ehb == you have lost some text
+    % Why is this emitted multiple times? And can one create a multiline message?
+    \PackageError{sdaps}{Tried to add a new choice, but not at the beginning of a choicegroup environment!}\@ehb
+  \else
+    \stepcounter{choicegroupnumchoices}%
+    \g at addto@macro{\@sdaps at choicegroup@start}{& {\usekomafont{choicegroupchoicefont}\strut#1}}%
+    \g at addto@macro{\@sdaps at choicegroup@boxes}{& \immediate\write\sdapsoutfile{Answer-Choice=\unexpanded{#1}} \checkbox}%
+  \fi
+}
+
+
+\providecommand*{\setmarkrange}[1]{%
+  \if at sdaps@markenvactive%
+    \ClassError{sdaps}{Changed markrange inside a markgroup!}{The markrange can not be changed inside an active markgroup.}%
+  \else%
+    \setcounter{markcheckboxcount}{#1}%
+  \fi%
+}
+
+\def\@sdaps at generatemarkboxes{%
+  \@tempcnta=0%
+  \def\@sdaps at markboxes{}%
+  \whiledo{\@tempcnta<\value{markcheckboxcount}}%
+  {%
+    \advance\@tempcnta by 1%
+    \g at addto@macro\@sdaps at markboxes{& {\checkbox}}%
+  }%
+}
+
+\newenvironment{markgroup}[1]{%
+  \question{#1}%
+  \immediate\write\sdapsoutfile{QObject-Head=\arabic{section}.\arabic{subsection}. \unexpanded{#1}}%
+  \@sdaps at markenvactivetrue%
+  \@sdaps at generatemarkboxes%
+  \tabularx{\linewidth}{Xr*{\themarkcheckboxcount}{c}l}
+}{%
+  \endtabularx%
+  \@sdaps at markenvactivefalse%
+}
+
+\providecommand*{\markline}[3]{%
+  \immediate\write\sdapsoutfile{QObject-Mark=XAUTO. \unexpanded{#1}}%
+  \immediate\write\sdapsoutfile{Answer-Mark=\unexpanded{#2}}%
+  {\usekomafont{marklinequestionfont}\strut#1\strut} & {\usekomafont{marklinechoicefont}#2} \@sdaps at markboxes & {\usekomafont{marklinechoicefont}#3}%
+  \immediate\write\sdapsoutfile{Answer-Mark=\unexpanded{#3}}%
+  \\%
+}
+
+\providecommand*{\singlemark}[3]{%
+  \question{#1}%
+  \immediate\write\sdapsoutfile{QObject-Mark=\arabic{section}.\arabic{subsection}. \unexpanded{#1}}%
+  \immediate\write\sdapsoutfile{Answer-Mark=\unexpanded{#2}}%
+  \@sdaps at generatemarkboxes%
+  \begin{tabularx}{\linewidth}{Y*{\themarkcheckboxcount}{c}X}
+    {\usekomafont{singlemarkchoicefont}\strut#2} \@sdaps at markboxes & {\usekomafont{singlemarkchoicefont}#3}\\%
+  \end{tabularx}%
+  \immediate\write\sdapsoutfile{Answer-Mark=\unexpanded{#3}}%
+}
+
+
+\newcounter{choiceitems}
+\newcounter{maxchoiceitems}
+\newenvironment{choicequestion}[2][3]{%
+  \setcounter{choiceitems}{1}%
+  \setcounter{maxchoiceitems}{#1}%
+  \question{#2}%
+  \immediate\write\sdapsoutfile{QObject-Choice=\arabic{section}.\arabic{subsection}. \unexpanded{#2}}%
+  \global\def\@sdaps at choiceitems@dummy{}%
+  \loop{}%
+  \ifnum\thechoiceitems<\themaxchoiceitems{\addtocounter{choiceitems}{1}} \global\edef\@sdaps at choiceitems@dummy{\@sdaps at choiceitems@dummy &}%
+  \repeat%
+  \setcounter{choiceitems}{1}%
+  \tabularx{\linewidth}{*{\themaxchoiceitems}{X}}%
+  % We insert dummy elements into each cell, because otherwise there may be
+  % layouting issues.
+  \@sdaps at choiceitems@dummy%
+   \rule[-1em]{0pt}{3em}% Insert known space (1em below, 2em above the baseline)
+   \cr%
+  \noalign{\vspace{-\extrarowheight}\vspace{-3.0em}}% Subtract known space again.
+}{%
+  \endtabularx%
+}
+
+\providecommand*{\choiceitem}[1]{%
+  \immediate\write\sdapsoutfile{Answer-Choice=\unexpanded{#1}}%
+  \ifthenelse{\thechoiceitems=\themaxchoiceitems}{%
+    \setcounter{choiceitems}{1}%
+    {\usekomafont{choiceitemfont}\checkbox~#1} \\%
+  }{%
+    \addtocounter{choiceitems}{1}%
+    {\usekomafont{choiceitemfont}\checkbox~#1} &%
+  }%
+}
+
+\providecommand*{\choicemulticolitem}[2]{%
+  \multicolumn{#1}{l}{%
+    \addtocounter{choiceitems}{#1}%
+    \immediate\write\sdapsoutfile{Answer-Choice=\unexpanded{#2}}%
+    {\usekomafont{choiceitemfont}\checkbox~#2}%
+  }
+  \ifthenelse{\thechoiceitems>\themaxchoiceitems}{%
+    \setcounter{choiceitems}{1}%
+    \\
+  }{%
+    &
+  }
+}
+
+% We need to declare them outside, they seem to be leaked or something like that
+\newdimen\@tempdimlower%
+\newdimen\@tempdimboxlower%
+\newdimen\@tempdimheight%
+\newdimen\@tempdimruleheight%
+\providecommand{\choiceitemtext}[3]{%
+  \multicolumn{#2}{l}{%
+    \addtocounter{choiceitems}{#2}%
+    \begingroup%
+      {\usekomafont{choiceitemfont}#3}~%
+      %
+      \setlength\@tempdimheight{#1}%
+      %
+      \setlength\@tempdimlower{0pt}%
+      \advance\@tempdimlower by -0.5\@tempdimheight%
+      \advance\@tempdimlower by 0.8ex%
+      %
+      \setlength\@tempdimboxlower\@tempdimlower%
+      \advance\@tempdimboxlower by -\textboxbottompad%
+      %
+      \setlength\@tempdima{1cm}%
+      \@tempdimruleheight=\@tempdimheight%
+      \advance\@tempdimruleheight by +2.0\elementlinewidth%
+      \advance\@tempdimruleheight by \@tempdimlower%
+      %
+      \immediate\write\sdapsoutfile{Answer-Choice=\unexpanded{#3}}%
+      %
+      \if at sdaps@output%
+        \label{textbox\thetextboxnum}%
+        \pgfsys at getposition{pgfpageorigin}{\@sdaps at pageorigin}%
+        \pgfsys at getposition{textboxleft\thetextboxnum}{\@sdaps at textboxleftpos}%
+        \pgfpointadd{\@sdaps at textboxleftpos}{%
+          \pgfpointadd{\@sdaps at pageorigin}{\pgfpoint{0}{\@tempdimruleheight}}%
+        }%
+        \pgfgetlastxy{\@sdaps at x}{\@sdaps at y}%
+        %
+        \pgfsys at getposition{textboxright\thetextboxnum}{\@sdaps at textboxrightpos}%
+        \pgfpointadd{\@sdaps at textboxrightpos}{%
+          \pgfpointadd{\pgfpointscale{-1}{\@sdaps at textboxleftpos}}{%
+            \pgfpoint{0}{#1+2\elementlinewidth}%
+          }%
+        }%
+        \pgfgetlastxy{\@sdaps at width}{\@sdaps at height}%
+        \immediate\write\sdapsoutfile{Box=Textbox, \getpagerefnumber{textbox\thetextboxnum}, \@sdaps at x, \@sdaps at y, \@sdaps at width, \@sdaps at height}%
+      \fi%
+      {
+      }% Yes, this inserts whitespace, because well, we had a bug and we don't
+      %
+      \def\horizontallines{%
+        \hbox{%
+          \raise\@tempdimboxlower\vbox{%
+            \hrule width \@tempdima height \elementlinewidth %
+            {\color{white}\hrule width \@tempdima height \@tempdimheight}%
+            \hrule height \elementlinewidth%
+            \vspace{\textboxbottompad}%
+          }%
+        }%
+      }%
+      \if at sdaps@output%
+        \pgfsys at markposition{textboxleft\thetextboxnum}%
+      \fi%
+      \vrule depth -\@tempdimlower width \elementlinewidth height \@tempdimruleheight %
+      %\kern -\elementlinewidth%
+      \horizontallines%
+      \kern-0.5\@tempdima%
+      \cleaders\horizontallines\hfill%
+      \kern-0.5\@tempdima%
+      \horizontallines%
+      %\kern -\elementlinewidth%
+      \vrule depth -\@tempdimlower width \elementlinewidth height \@tempdimruleheight%
+      \if at sdaps@output%
+        \pgfsys at markposition{textboxright\thetextboxnum}%
+      \fi%
+      \addtocounter{textboxnum}{1}%
+    \endgroup%
+  }%
+  \ifthenelse{\thechoiceitems>\themaxchoiceitems}{%
+    \setcounter{choiceitems}{1}%
+    \\%
+  }{%
+    &%
+  }%
+}
+
+\sectionfont{\sectbox}
+
+\setkomafont{disposition}{\normalfont}
+\addtokomafont{section}{\bfseries\sffamily}
+%\renewcommand*{\sectionheadendvskip}{\vspace*{0p t}}
+
+
+%-------------------------------------------------------------------------------
+% sdaps log
+%-------------------------------------------------------------------------------
+
+\newwrite\sdapsoutfile%
+\immediate\openout\sdapsoutfile=\jobname.sdaps%
+
+
+\endinput
diff --git a/tex/sdapsreport.cls b/tex/sdapsreport.cls
new file mode 100644
index 0000000..19a9ecb
--- /dev/null
+++ b/tex/sdapsreport.cls
@@ -0,0 +1,324 @@
+%% start of file `sdapsreport.cls'.
+%% Copyright 2010-2011 Ferdinand Schwenk (ferdisdot at gmail.com).
+%
+% This work may be distributed and/or modified under the  
+% conditions of the LaTeX Project Public License, either version 1.3c 
+% of this license or (at your option) any later version.  
+% The latest version of this license is in  
+%   http://www.latex-project.org/lppl.txt
+
+
+%-------------------------------------------------------------------------------
+% identification
+%-------------------------------------------------------------------------------
+\NeedsTeXFormat{LaTeX2e}
+\ProvidesClass{sdapsreport}[2011/12/20%
+                            v0.1 Class for SDAPS survey-report]
+
+
+%-------------------------------------------------------------------------------
+% debugging
+%-------------------------------------------------------------------------------
+\newif\if at DEBUG\@DEBUGfalse
+
+
+%-------------------------------------------------------------------------------
+% option processing
+%-------------------------------------------------------------------------------
+% pass unknown options to scrartcl
+\DeclareOption*{\PassOptionsToClass{\CurrentOption}{scrreprt}}
+
+% execute default options
+\ExecuteOptions{12pt,final}
+
+% process given options
+\ProcessOptions\relax
+
+
+%-------------------------------------------------------------------------------
+% load base-class
+%-------------------------------------------------------------------------------
+\LoadClass[twoside,headings=small]{scrreprt}
+
+
+%-------------------------------------------------------------------------------
+% required packages
+%-------------------------------------------------------------------------------
+% geometry package
+%\RequirePackage{geometry}
+%\geometry{hmargin=13mm}
+%\geometry{vmargin=25mm}
+%\geometry{top=21mm}
+%\geometry{bottom=25mm}
+
+% ifthen package
+\RequirePackage{ifthen}
+
+% ifpdf package
+\RequirePackage{ifpdf}
+
+% fontenc package
+\RequirePackage[T1]{fontenc}
+
+% color
+\RequirePackage{color}
+
+% hyperrefs
+\RequirePackage{url}
+\RequirePackage{hyperref}
+\hypersetup{%
+  breaklinks,%
+  baseurl       = http://,%
+  pdfborder     = 0 0 0,%
+  pdfpagemode   = UseNone,%
+  pdfcreator    = \LaTeX{} with `sdapsreport' class,%
+  pdfproducer   = \LaTeX{}
+}
+\AtEndOfClass{%
+  \AtBeginDocument{%
+    \hypersetup{%
+      pdfauthor     = \@author,%
+      pdftitle      = \@title,%
+      pdfsubject    = sdaps report \@title,%
+      pdfkeywords   = sdaps report \@title%
+    }%
+  }%
+}
+
+% graphics
+\RequirePackage{graphicx}
+
+% headers and footers
+\usepackage{scrpage2}
+\clearscrheadings
+  \chead[\@author\\\@title]{\@author\\\@title}
+\pagestyle{scrheadings}
+
+% Section formatting
+\RequirePackage{sectsty}
+
+% table of fixed width
+\RequirePackage{tabularx}
+% display content vertical centered
+\renewcommand\tabularxcolumn[1]{m{#1}}
+\newcolumntype{Y}{>{\raggedleft}X}
+
+% Einheiten
+\RequirePackage{siunitx}
+
+% Listen
+\RequirePackage{mdwlist}
+
+% Translation
+\RequirePackage{translator}
+\usedictionary{translator-sdaps-dictionary}
+
+% Zeichenprogramm
+\RequirePackage{tikz}
+\usetikzlibrary{calc}
+
+
+%-------------------------------------------------------------------------------
+% Declaration
+%-------------------------------------------------------------------------------
+\newcounter{question}
+\newlength{\boxheight}
+
+%-------------------------------------------------------------------------------
+%                class definition
+%-------------------------------------------------------------------------------
+% minimal base settings
+\setlength\lineskip{1\p@}
+\setlength\normallineskip{1\p@}
+\renewcommand\baselinestretch{}
+\setlength{\parindent}{0pt}
+\setlength{\parskip}{0pt}
+\setlength\columnsep{10\p@}
+\setlength\columnseprule{0\p@}
+\pagestyle{scrheadings}
+\pagenumbering{arabic}
+\raggedbottom
+\onecolumn
+
+\newlength{\answerbarwidth}
+\setlength{\answerbarwidth}{0.5\linewidth}
+
+\newlength{\markdescwidth}
+\setlength{\markdescwidth}{0.49\linewidth}
+\newlength{\markdiawidth}
+\setlength{\markdiawidth}{0.5\linewidth}
+\newlength{\markbarheight}
+\setlength{\markbarheight}{3em}
+
+
+\definecolor{sdapschoicebarcolor}{gray}{0.6}
+\definecolor{sdapsmarkmeancolor}{gray}{0.0}
+\definecolor{sdapsmarkstddevcolor}{gray}{0.7}
+\definecolor{sdapsmarkbarcolor}{gray}{0.7}
+\definecolor{sdapsmarkbarframecolor}{gray}{0.0}
+
+\definecolor{sectionbgcolor}{gray}{0.8}
+\definecolor{sectionfgcolor}{gray}{0.0}
+
+
+%-------------------------------------------------------------------------------
+%                style commands definitions
+%-------------------------------------------------------------------------------
+\def\smallskip{\vspace\smallskipamount}
+\def\medskip{\vspace\medskipamount}
+\def\bigskip{\vspace\bigskipamount}
+\newskip\smallskipamount \smallskipamount=3pt  plus 1pt minus 1pt
+\newskip\medskipamount   \medskipamount  =6pt  plus 2pt minus 2pt
+\newskip\bigskipamount   \bigskipamount  =12pt plus 4pt minus 4pt
+
+\setkomafont{descriptionlabel}{\normalfont}
+
+\newcommand{\sectbox}[1]{%
+ \noindent\protect\colorbox{sectionbgcolor}{%
+   \@tempdima=\hsize
+   \advance\@tempdima by-2\fboxsep
+   \protect\parbox{\@tempdima}{%
+     \smallskip
+     \raggedright % extra commands here
+     \color{sectionfgcolor}\usekomafont{section}{#1} \smallskip
+    }%
+  }%
+}
+\sectionfont{\sectbox}
+
+\setkomafont{disposition}{\normalfont}
+\addtokomafont{section}{\bfseries\sffamily}
+
+\global\def\@extrainfos{}
+\newcommand{\addextrainfo}[2]{\global\edef\@extrainfos{\@extrainfos #1: & #2 \cr}}%\global\edef\@extrainfos{\@extrainfos #1 & #2 \\}}
+
+\renewcommand*\maketitle[1][1]{
+  \begin{titlepage}%
+    \setcounter{page}{#1}%
+    \null\vfill
+    \begin{center}%
+      \ifx\@subject\@empty \else
+        {\Large \@subject \par}%
+        \vskip 3em
+      \fi
+      {\titlefont\huge \@title\par}%
+      \vskip 3em
+      {\Large \lineskip 0.75em
+      \begin{tabular}[t]{c}%
+        \@author
+      \end{tabular}\par}%
+      \vskip 1.5em
+      {\Large \@date \par}%
+    \end{center}
+    \vskip \z@ \@plus3fill
+    \begin{tabular}[t]{rl}%
+      \@extrainfos
+    \end{tabular}%
+  \end{titlepage}%
+  \global\let\maketitle\relax
+  \global\let\@author\@empty
+  \global\let\@date\@empty
+  \global\let\@title\@empty
+  \global\let\@subject\@empty
+  \global\let\@extrainfos\@empty
+  \global\let\author\relax
+  \global\let\title\relax
+  \global\let\subject\relax
+  \global\let\extrainfos\relax
+  \global\let\date\relax
+  \global\let\and\relax
+}
+
+%-------------------------------------------------------------------------------
+%                structure commands definitions
+%-------------------------------------------------------------------------------
+\renewcommand{\author}[1]{\def\@author{#1}}
+\renewcommand{\title}[1]{\def\@title{#1}}
+
+\def\question{{\global\let\freeformsep\relax}\subsection*}
+
+\newcommand*{\decimaltopercent}[1]{
+  \pgfset{/pgf/number format/precision=0}
+  \pgfmathparse{100 * #1}
+  \pgfmathroundtozerofill{\pgfmathresult}
+  \SI{\pgfmathresult}{\percent}
+}
+
+\newlength{\einheit}
+\newcommand*{\markdiagram}{%
+  \begin{tikzpicture}[semithick]
+    \pgfkeysgetvalue{/sdaps/mark/range}{\range}
+    \pgfmathsetlength{\einheit}{\linewidth/\range}
+    \foreach \x in {1,...,\range}
+    {
+      \pgfkeysgetvalue{/sdaps/mark/\x/fraction}{\val}
+      \fill[xshift=\x\einheit,yshift=2pt, color=sdapsmarkbarcolor, draw=sdapsmarkbarframecolor, very thin]
+        (-0.5\einheit, 0) rectangle
+        (+0.5\einheit, \val\markbarheight);
+      \node[above] at (\x\einheit, \markbarheight) {\decimaltopercent{\val}};
+    }
+    \pgfmathparse{max(\pgfkeysvalueof{/sdaps/mark/mean}\einheit - %
+      \pgfkeysvalueof{/sdaps/mark/stddev}\einheit, \einheit)}
+    \pgfmathsetlength\@tempdima{\pgfmathresult}
+    \pgfmathparse{min(\pgfkeysvalueof{/sdaps/mark/mean}\einheit + %
+      \pgfkeysvalueof{/sdaps/mark/stddev}\einheit, \range\einheit)}
+    \pgfmathsetlength\@tempdimb{\pgfmathresult}
+    \fill[color=sdapsmarkstddevcolor] %Varianz
+      (\@tempdima, 0ex) rectangle (\@tempdimb, -1ex);
+    \draw[line width=2pt,color=sdapsmarkmeancolor,%
+      xshift=\pgfkeysvalueof{/sdaps/mark/mean}\einheit] %Mittelwert
+      (0, 0ex) -- (0, -1ex-1pt);
+    \draw (+1.0\einheit,0ex) -- (\range\einheit,0ex); %Rahmen.Strich
+%    \draw (+0.5\einheit,2pt) -- (5.5\einheit,2pt); %Rahmen.Strich
+    \draw (+0.5\einheit,2pt) -- ($(\range\einheit,2pt) + (0.5\einheit,0)$); %Rahmen.Strich
+    \foreach \x in {1,...,\range}
+      \draw[xshift=\x\einheit] (0,0) -- (0, -1ex-1pt) node[below]{\footnotesize\x}; %Vert.Striche
+  \end{tikzpicture}
+}
+
+\newenvironment{choicequestion}[1]{%
+  \question{#1}%
+  \tabularx{\linewidth}{Yrl}
+}{%
+  \endtabularx%
+}
+
+\newcommand*{\choiceanswer}[2]{%
+  #1 &
+  \decimaltopercent{#2} &
+  \begin{tikzpicture}
+    \fill[color=sdapschoicebarcolor] (0, 0) rectangle (#2\answerbarwidth, 1em); %Anteil
+    \draw (0, 0) rectangle (\answerbarwidth, 1em); %Rahmen.Strich
+  \end{tikzpicture}\\
+}
+
+\newcommand*{\markanswer}{%
+  \begin{minipage}[c]{\markdescwidth}
+    \pgfkeysvalueof{/sdaps/mark/lower} -- \pgfkeysvalueof{/sdaps/mark/upper}
+    \begin{description*}
+      \item[\translate{answers}:] \pgfkeysvalueof{/sdaps/mark/count}
+      \item[\translate{mean}:] \num{\pgfkeysvalueof{/sdaps/mark/mean}}
+      \item[\translate{standard-deviation}:] \num{\pgfkeysvalueof{/sdaps/mark/stddev}}
+    \end{description*}
+  \end{minipage}
+  \hfill
+  \begin{minipage}[c]{\markdiawidth}
+    \markdiagram
+  \end{minipage}
+  \\
+}
+
+\let\freeformsep\relax
+\newcommand*{\freeform}[2]{%
+  \freeformsep
+  \pgfmathparse{min(#1, \linewidth)}
+  \includegraphics[width=\pgfmathresult pt,keepaspectratio]{#2}\\
+  \def\freeformsep{\rule{\linewidth}{0.5pt}\\}
+}
+\newcommand*{\freeformtext}[1]{%
+  \freeformsep
+  #1\\
+  \def\freeformsep{\rule{\linewidth}{0.5pt}\\}
+}
+
+\endinput
diff --git a/tex/tex_translations.in b/tex/tex_translations.in
new file mode 100644
index 0000000..ea3f960
--- /dev/null
+++ b/tex/tex_translations.in
@@ -0,0 +1,42 @@
+# Translations for sdaps LaTeX classes.
+# Copyright 2010-2013 Ferdinand Schwenk (ferdisdot at gmail.com).
+# Copyright 2011-2013 Benjamin Berg (benjamin at sipsolutions.net).
+#
+# This work may be distributed and/or modified under the
+# conditions of the LaTeX Project Public License, either version 1.3c
+# of this license or (at your option) any later version.
+# The latest version of this license is in
+#   http://www.latex-project.org/lppl.txt
+
+# This file only exist to extract translations using intltool.
+# From this file the latex dictionary files are created at
+# build time.
+
+[translations]
+# The language name of the LaTeX dictionary file for translator
+_tex-language=English
+# Used as an overlay for questionnaires that are not yet done
+_draft=draft
+# Label for the survey-id on the questionnaire (in "classic" mode)
+_surveyid=Survey-ID:
+# Label for the questionnaire-id on the questionnaire (in "classic" mode)
+_questionnaireid=Questionnaire-ID:
+# Default instructions on the questionnaire
+_infotext=This questionnaire is automatically read by a computer program. Please use a pen for filling in your answers.
+
+# Instruction on the questionnaire: Description of a crossed checkbox
+_info-cross=Check
+# Instruction on the questionnaire: Description of a crossed and filled checkbox
+_info-correct=Uncheck to correct
+# Instruction on the questionnaire: Multiple choice question
+_info-select=You can check any number of boxes in selection questions.
+# Instruction on the questionnaire: Mark/range question.
+_info-mark=For questions with a range (1--\arabic{markcheckboxcount}) choose the answer the mark that fits best.
+
+# LaTeX based report: Label for the number of people that answered
+_answers=Answers
+# LaTeX based report: The calculated mean value
+_mean=Mean
+# LaTeX based report: The standard deviation
+_standard-deviation=Standard-Deviation
+

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-edu/pkg-team/sdaps.git



More information about the debian-edu-commits mailing list