Repository: the-paperless-project/paperless
Branch: master
Commit: 9b0063c9731f
Files: 258
Total size: 2.0 MB
Directory structure:
gitextract_yqrokexp/
├── .docker-hub-test
├── .editorconfig
├── .gitattributes
├── .gitignore
├── .travis.yml
├── CODE_OF_CONDUCT.md
├── Dockerfile
├── LICENSE
├── Pipfile
├── README-de.md
├── README-el.md
├── README.md
├── THANKS.md
├── ci/
│ └── deploy-docker
├── data/
│ └── .keep
├── docker-compose.env.example
├── docker-compose.yml.example
├── docs/
│ ├── Dockerfile
│ ├── Makefile
│ ├── _static/
│ │ ├── .keep
│ │ └── custom.css
│ ├── api.rst
│ ├── changelog.rst
│ ├── conf.py
│ ├── consumption.rst
│ ├── contributing.rst
│ ├── customising.rst
│ ├── examples/
│ │ └── lxc/
│ │ ├── lxc-install.sh
│ │ └── paperless.conf
│ ├── extending.rst
│ ├── guesswork.rst
│ ├── index.rst
│ ├── migrating.rst
│ ├── requirements.rst
│ ├── requirements.txt
│ ├── scanners.rst
│ ├── screenshots.rst
│ ├── setup.rst
│ ├── troubleshooting.rst
│ └── utilities.rst
├── management/
│ └── commands/
│ └── create_superuser_with_password.py
├── overrides/
│ └── README.md
├── paperless.conf.example
├── presentation/
│ ├── README.md
│ ├── contrib/
│ │ ├── font-awesome-4.3.0/
│ │ │ ├── css/
│ │ │ │ └── font-awesome.css
│ │ │ ├── fonts/
│ │ │ │ └── FontAwesome.otf
│ │ │ ├── less/
│ │ │ │ ├── animated.less
│ │ │ │ ├── bordered-pulled.less
│ │ │ │ ├── core.less
│ │ │ │ ├── fixed-width.less
│ │ │ │ ├── font-awesome.less
│ │ │ │ ├── icons.less
│ │ │ │ ├── larger.less
│ │ │ │ ├── list.less
│ │ │ │ ├── mixins.less
│ │ │ │ ├── path.less
│ │ │ │ ├── rotated-flipped.less
│ │ │ │ ├── stacked.less
│ │ │ │ └── variables.less
│ │ │ └── scss/
│ │ │ ├── _animated.scss
│ │ │ ├── _bordered-pulled.scss
│ │ │ ├── _core.scss
│ │ │ ├── _fixed-width.scss
│ │ │ ├── _icons.scss
│ │ │ ├── _larger.scss
│ │ │ ├── _list.scss
│ │ │ ├── _mixins.scss
│ │ │ ├── _path.scss
│ │ │ ├── _rotated-flipped.scss
│ │ │ ├── _stacked.scss
│ │ │ ├── _variables.scss
│ │ │ └── font-awesome.scss
│ │ └── google/
│ │ └── css/
│ │ └── lato.css
│ ├── css/
│ │ ├── print/
│ │ │ ├── paper.css
│ │ │ └── pdf.css
│ │ ├── reveal.css
│ │ ├── reveal.scss
│ │ └── theme/
│ │ ├── README.md
│ │ ├── beige.css
│ │ ├── black.css
│ │ ├── blood.css
│ │ ├── league.css
│ │ ├── moon.css
│ │ ├── night.css
│ │ ├── serif.css
│ │ ├── simple.css
│ │ ├── sky.css
│ │ ├── solarized.css
│ │ ├── source/
│ │ │ ├── beige.scss
│ │ │ ├── black.scss
│ │ │ ├── blood.scss
│ │ │ ├── league.scss
│ │ │ ├── moon.scss
│ │ │ ├── night.scss
│ │ │ ├── serif.scss
│ │ │ ├── simple.scss
│ │ │ ├── sky.scss
│ │ │ ├── solarized.scss
│ │ │ └── white.scss
│ │ ├── template/
│ │ │ ├── mixins.scss
│ │ │ ├── settings.scss
│ │ │ └── theme.scss
│ │ └── white.css
│ ├── index.html
│ ├── js/
│ │ └── reveal.js
│ ├── lib/
│ │ ├── css/
│ │ │ └── zenburn.css
│ │ ├── font/
│ │ │ ├── league-gothic/
│ │ │ │ ├── LICENSE
│ │ │ │ └── league-gothic.css
│ │ │ └── source-sans-pro/
│ │ │ ├── LICENSE
│ │ │ └── source-sans-pro.css
│ │ └── js/
│ │ ├── classList.js
│ │ └── html5shiv.js
│ └── plugin/
│ ├── highlight/
│ │ └── highlight.js
│ ├── leap/
│ │ └── leap.js
│ ├── markdown/
│ │ ├── example.html
│ │ ├── example.md
│ │ ├── markdown.js
│ │ └── marked.js
│ ├── math/
│ │ └── math.js
│ ├── multiplex/
│ │ ├── client.js
│ │ ├── index.js
│ │ └── master.js
│ ├── notes/
│ │ ├── notes.html
│ │ └── notes.js
│ ├── notes-server/
│ │ ├── client.js
│ │ ├── index.js
│ │ └── notes.html
│ ├── print-pdf/
│ │ └── print-pdf.js
│ ├── remotes/
│ │ └── remotes.js
│ ├── search/
│ │ └── search.js
│ └── zoom-js/
│ └── zoom.js
├── requirements.txt
├── resources/
│ └── logo/
│ └── print/
│ └── eps/
│ ├── Black logo - no background.eps
│ ├── Color logo - no background.eps
│ ├── Color logo with background.eps
│ └── White logo - no background.eps
├── scripts/
│ ├── docker-entrypoint.sh
│ ├── gunicorn.conf
│ ├── paperless-consumer.service
│ ├── paperless-webserver.service
│ └── post-consumption-example.sh
└── src/
├── documents/
│ ├── __init__.py
│ ├── actions.py
│ ├── admin.py
│ ├── apps.py
│ ├── checks.py
│ ├── consumer.py
│ ├── filters.py
│ ├── forms.py
│ ├── loggers.py
│ ├── mail.py
│ ├── management/
│ │ ├── __init__.py
│ │ └── commands/
│ │ ├── __init__.py
│ │ ├── change_storage_type.py
│ │ ├── document_consumer.py
│ │ ├── document_correspondents.py
│ │ ├── document_exporter.py
│ │ ├── document_importer.py
│ │ ├── document_logs.py
│ │ ├── document_renamer.py
│ │ ├── document_retagger.py
│ │ └── loaddata_stdin.py
│ ├── managers.py
│ ├── migrations/
│ │ ├── 0001_initial.py
│ │ ├── 0002_auto_20151226_1316.py
│ │ ├── 0003_sender.py
│ │ ├── 0004_auto_20160114_1844.py
│ │ ├── 0005_auto_20160123_0313.py
│ │ ├── 0006_auto_20160123_0430.py
│ │ ├── 0007_auto_20160126_2114.py
│ │ ├── 0008_document_file_type.py
│ │ ├── 0009_auto_20160214_0040.py
│ │ ├── 0010_log.py
│ │ ├── 0011_auto_20160303_1929.py
│ │ ├── 0012_auto_20160305_0040.py
│ │ ├── 0013_auto_20160325_2111.py
│ │ ├── 0014_document_checksum.py
│ │ ├── 0015_add_insensitive_to_match.py
│ │ ├── 0016_auto_20170325_1558.py
│ │ ├── 0017_auto_20170512_0507.py
│ │ ├── 0018_auto_20170715_1712.py
│ │ ├── 0019_add_consumer_user.py
│ │ ├── 0020_document_added.py
│ │ ├── 0021_document_storage_type.py
│ │ ├── 0022_auto_20181007_1420.py
│ │ ├── 0023_document_current_filename.py
│ │ └── __init__.py
│ ├── mixins.py
│ ├── models.py
│ ├── parsers.py
│ ├── serialisers.py
│ ├── settings.py
│ ├── signals/
│ │ ├── __init__.py
│ │ └── handlers.py
│ ├── static/
│ │ ├── js/
│ │ │ └── colours.js
│ │ └── paperless.css
│ ├── templates/
│ │ ├── admin/
│ │ │ ├── base_site.html
│ │ │ ├── documents/
│ │ │ │ └── document/
│ │ │ │ ├── change_form.html
│ │ │ │ ├── change_list.html
│ │ │ │ ├── change_list_results.html
│ │ │ │ └── select_object.html
│ │ │ └── index.html
│ │ └── documents/
│ │ └── index.html
│ ├── templatetags/
│ │ ├── __init__.py
│ │ ├── customisation.py
│ │ └── hacks.py
│ ├── tests/
│ │ ├── __init__.py
│ │ ├── factories.py
│ │ ├── samples/
│ │ │ ├── inline_mail.txt
│ │ │ └── mail.txt
│ │ ├── test_checks.py
│ │ ├── test_consumer.py
│ │ ├── test_document_model.py
│ │ ├── test_file_handling.py
│ │ ├── test_importer.py
│ │ ├── test_logger.py
│ │ ├── test_mail.py
│ │ ├── test_matchables.py
│ │ └── test_models.py
│ └── views.py
├── manage.py
├── paperless/
│ ├── __init__.py
│ ├── checks.py
│ ├── db.py
│ ├── middleware.py
│ ├── mixins.py
│ ├── models.py
│ ├── settings.py
│ ├── urls.py
│ ├── version.py
│ ├── views.py
│ └── wsgi.py
├── paperless_tesseract/
│ ├── __init__.py
│ ├── apps.py
│ ├── languages.py
│ ├── parsers.py
│ ├── signals.py
│ └── tests/
│ ├── __init__.py
│ ├── test_date.py
│ ├── test_ocr.py
│ └── test_signals.py
├── paperless_text/
│ ├── __init__.py
│ ├── apps.py
│ ├── parsers.py
│ └── signals.py
├── reminders/
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── filters.py
│ ├── migrations/
│ │ ├── 0001_initial.py
│ │ ├── 0002_auto_20181007_1420.py
│ │ └── __init__.py
│ ├── models.py
│ ├── serialisers.py
│ ├── tests.py
│ └── views.py
├── setup.cfg
└── tox.ini
================================================
FILE CONTENTS
================================================
================================================
FILE: .docker-hub-test
================================================
Docker Hub test 2
================================================
FILE: .editorconfig
================================================
# EditorConfig: http://EditorConfig.org
root = true
[*]
indent_style = tab
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
charset = utf-8
max_line_length = 79
[{*.html,*.css,*.js}]
max_line_length = off
[*.py]
indent_size = 4
indent_style = space
[*.yml]
indent_style = space
# Tests don't get a line width restriction. It's still a good idea to follow
# the 79 character rule, but in the interests of clarity, tests often need to
# violate it.
[**/test_*.py]
max_line_length = off
================================================
FILE: .gitattributes
================================================
THANKS.md merge=union
================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
#lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.pytest_cache
# Translations
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Stored PDFs
media/documents/*.gpg
media/documents/thumbnails/*
media/documents/originals/*
media/overrides.css
media/overrides.js
# Sqlite database
db.sqlite3
db.sqlite3-journal
# PyCharm
.idea
# Other stuff that doesn't belong
.virtualenv
virtualenv
docker-compose.yml
docker-compose.env
# Used for development
scripts/import-for-development
scripts/nuke
# Static files collected by the collectstatic command
./static/
================================================
FILE: .travis.yml
================================================
language: python
before_install:
- sudo apt-get update -qq
- sudo apt-get install -qq libpoppler-cpp-dev unpaper tesseract-ocr imagemagick ghostscript optipng
sudo: false
matrix:
include:
- python: "3.5"
- python: "3.6"
- python: "3.7-dev"
- env:
- BUILD_DOCKER=1
# Variable to add to publish the Docker image:
# * DOCKER_USERNAME
# * DOCKER_PASSWORD, to be encrypted, use `travis encrypt DOCKER_PASSWORD=`
services:
- docker
before_install:
- true
install:
- true
script:
- docker build --tag=the-paperless-project/paperless .
after_success:
- true
install:
- pip install --upgrade pip pipenv sphinx
- pipenv lock -r > requirements.txt
- pip install -r requirements.txt
script:
- cd src/
- pytest --cov
- pycodestyle
- sphinx-build -b html ../docs ../docs/_build -W
after_success:
- coveralls
deploy:
- provider: script
skip_cleanup: true
script: ci/deploy-docker
on:
tags: true
condition: '"${BUILD_DOCKER}" = 1'
- provider: script
skip_cleanup: true
script: ci/deploy-docker
on:
branch: master
condition: '"${BUILD_DOCKER}" = 1'
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* Unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at code@danielquinn.org. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4 to remove puritanical language. The original is available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
================================================
FILE: Dockerfile
================================================
FROM alpine:3.12
LABEL maintainer="The Paperless Project https://github.com/the-paperless-project/paperless" \
contributors="Guy Addadi , Pit Kleyersburg , \
Sven Fischer "
# Copy Pipfiles file, init script and gunicorn.conf
COPY Pipfile* /usr/src/paperless/
COPY scripts/docker-entrypoint.sh /sbin/docker-entrypoint.sh
COPY scripts/gunicorn.conf /usr/src/paperless/
# Set export and consumption directories
ENV PAPERLESS_EXPORT_DIR=/export \
PAPERLESS_CONSUMPTION_DIR=/consume
RUN apk add --no-cache \
bash \
curl \
ghostscript \
gnupg \
imagemagick \
libmagic \
libpq \
optipng \
poppler \
python3 \
shadow \
sudo \
tesseract-ocr \
tzdata \
unpaper && \
apk add --no-cache --virtual .build-dependencies \
g++ \
gcc \
jpeg-dev \
musl-dev \
poppler-dev \
postgresql-dev \
python3-dev \
zlib-dev && \
# Install python dependencies
python3 -m ensurepip && \
rm -r /usr/lib/python*/ensurepip && \
cd /usr/src/paperless && \
pip3 install --upgrade pip pipenv && \
pipenv install --system --deploy && \
# Remove build dependencies
apk del .build-dependencies && \
# Create the consumption directory
mkdir -p $PAPERLESS_CONSUMPTION_DIR && \
# Create user
addgroup -g 1000 paperless && \
adduser -D -u 1000 -G paperless -h /usr/src/paperless paperless && \
chown -Rh paperless:paperless /usr/src/paperless && \
mkdir -p $PAPERLESS_EXPORT_DIR && \
# Avoid setrlimit warnings
# See: https://gitlab.alpinelinux.org/alpine/aports/issues/11122
echo 'Set disable_coredump false' >> /etc/sudo.conf && \
# Setup entrypoint
chmod 755 /sbin/docker-entrypoint.sh
WORKDIR /usr/src/paperless/src
# Mount volumes and set Entrypoint
VOLUME ["/usr/src/paperless/data", "/usr/src/paperless/media", "/consume", "/export"]
ENTRYPOINT ["/sbin/docker-entrypoint.sh"]
CMD ["--help"]
# Copy application
COPY src/ /usr/src/paperless/src/
COPY data/ /usr/src/paperless/data/
COPY media/ /usr/src/paperless/media/
# Collect static files
RUN sudo -HEu paperless /usr/src/paperless/src/manage.py collectstatic --clear --no-input
================================================
FILE: LICENSE
================================================
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}
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 .
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
{project} Copyright (C) {year} {fullname}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
.
================================================
FILE: Pipfile
================================================
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"
[packages]
django = "<2.1,>=2.0"
pillow = "*"
coveralls = "*"
dateparser = "*"
django-cors-headers = "*"
django-crispy-forms = "*"
django-extensions = "*"
django-filter = "*"
djangorestframework = "*"
factory-boy = "*"
filemagic = "*"
fuzzywuzzy = {extras = ["speedup"],version = "==0.15.0"}
gunicorn = "*"
inotify-simple = "*"
langdetect = "*"
pdftotext = "*"
pyocr = "*"
python-dateutil = "*"
python-dotenv = "*"
python-gnupg = "*"
pytz = "*"
sphinx = "*"
tox = "*"
pycodestyle = "*"
pytest = "*"
pytest-cov = "*"
pytest-django = "*"
pytest-sugar = "*"
pytest-env = "*"
pytest-xdist = "*"
psycopg2 = "*"
djangoql = "*"
whitenoise = "*"
brotli = "*"
[dev-packages]
ipython = "*"
================================================
FILE: README-de.md
================================================
[ [en](README.md) | de | [el](README-el.md) ]

[](https://paperless.readthedocs.org/) [](https://gitter.im/danielquinn/paperless) [](https://travis-ci.org/the-paperless-project/paperless) [](https://coveralls.io/github/the-paperless-project/paperless?branch=master) [](https://github.com/the-paperless-project/paperless/blob/master/THANKS.md)
Indexiere und archiviere alle deine eingescannten Papierdokumente
Ich hasse Papier. Abgesehen von Umweltproblemen, ist es der Albtraum einer technisch-interessierten Person:
* Es gibt keine Suchfunktion
* Es braucht physischen Platz
* Sicherungen bedeuten mehr Papier
In den vergangenen Monaten hatte ich mehrmals das Problem, das richtige Dokument nicht zur Hand zu haben. Manchmal warf ich Dokumente weg, die ich noch gebraucht hätte (wer behält schon Wasserrechnungen für zwei Jahre?), andere verlor ich einfach... weil PAPIER. Ich schrieb dies, um mein Leben einfacher zu machen.
## Wie es funktioniert
Paperless steuert nicht deinen Scanner, es hilft nur damit umzugehen, was der Scanner herausspuckt
1. Kaufe einen Dokumentenscanner, der an einen Ort in deinem Netzwerk schreiben kann. Wenn du Inspirationen brauchst, schau in die [Scannerempfehlungen](https://paperless.readthedocs.io/en/latest/scanners.html).
2. Stelle "Scanne zu FTP" oder ähnliches ein. Es sollte möglich sein, eingescannte Bilder ohne etwas tun zu müssen an einen Server hochzuladen. Natürlich kannst du auch die einscannte Datei händisch hochladen, wenn der Scanner automatisches Hochladen nicht unterstützt. Paperless ist es egal, wie die Dokumente in seinen lokalen Konsumordner gelangen.
3. Besitze einen Zielserver, lasse das Paperless-Konsumskript laufen, um die Datei mit OCR zu versehen und sie in einer lokalen Datenbank zu indexieren.
4. Benutze die Weboberfläche, um die Datenbank zu durchforsten und zu finden, was du suchst.
5. Lade die PDF-Datei, die du brauchst/möchtest über die Weboberfläche herunter und mach was auch immer du willst damit. Du kannst es auch drucken und versenden, so als wäre es das Original. In den meisten Fällen wird das niemanden interessieren oder bemerken.
Hier das, was du bekommt:

## Dokumentation
Diese ist komplett verfügbar auf [ReadTheDocs](https://paperless.readthedocs.org/).
## Anforderungen
Dies alles ist eine wirklich ziemlich einfache, glänzende und benutzerfreundliche Hülle rund um einige sehr mächtige Werkzeuge.
* [ImageMagick](http://imagemagick.org/) wandelt Bilder zwischen Farbe und Graustufen um.
* [Tesseract](https://github.com/tesseract-ocr) erledigt die Buchstabenerkennung.
* [Unpaper](https://github.com/unpaper/unpaper) bereinigt und begradigt das eingescannte Bild.
* [GNU Privacy Guard](https://gnupg.org/) wird als Verschlüsselungsbackend genutzt.
* [Python 3](https://python.org/) ist die Sprache des Projekts.
* [Pillow](https://pypi.python.org/pypi/pillowfight/) lädt die Bilddaten als Python-Objekt, um sie mit PyOCR zu verwenden.
* [PyOCR](https://github.com/jflesch/pyocr) ist ein glatter, programmatischer Wrapper um Tesseract.
* [Django](https://www.djangoproject.com/) ist das Framework, auf das dieses Projekt aufbaut.
* [Python-GNUPG](http://pythonhosted.org/python-gnupg/) entschlüsselt die PDFs auf Abruf, um das Herunterladen unverschlüsselter Dateien zu ermöglichen, während die verschlüsselten Dateien auf der Festplatte bleiben.
## Status des Projekts
Dieses Projekt wurde um 2015 gestartet und es gibt viele Leute, die es verwenden. Warum auch immer ist es ziemlich beliebt in Deutschland -- vielleicht kann jemand dort drüben mich über das Warum aufklären.
Ich entwickle keine neuen Funktionen mehr für Paperless, weil es genau das tut, was ich brauche und meine Aufmerksamkeit meinem neuesten Projekt [Aletheia](https://github.com/danielquinn/aletheia) gewidmet ist. Ich verlasse jedoch nicht das Projekt. Ich bin glücklich damit, Pull Requests zu begutachten und Fragen im Issue-Bereich zu beantworten. Wenn du ein Entwickler bist und eine neue Funktion willst, reihe sie in den Issues ein und/oder sende einen PR! Ich bin glücklich damit, neue Sachen hinzuzufügen, habe aber einfach nicht die Zeit, sie selbst zu erarbeiten.
## Verknüpfte Projekte
Paperless gibt es bereits seit einer Weile und Leute haben damit angefangen, Sachen rund um Paperless zu entwickeln. Wenn du einer dieser Menschen bist, kannst du dein Projekt zu dieser Liste hinzufügen:
* [Paperless App](https://github.com/bauerj/paperless_app): Eine Android/iOS-App für Paperless.
* [Paperless Desktop](https://github.com/thomasbrueggemann/paperless-desktop): Eine Desktop-Oberfläche für deine Paperless-Installation. Läuft auf Mac, Linux und Windows.
* [ansible-role-paperless](https://github.com/ovv/ansible-role-paperless): Eine einfache Möglichkeit, Paperless via Ansible laufen zu lassen.
* [paperless-cli](https://github.com/stgarf/paperless-cli): Ein golang Kommandozeilenprogramm, welches mit Paperless interagiert.
## Ähnliche Projekte
Es gibt da draußen auch das Projekt [Mayan EDMS](https://mayan.readthedocs.org/en/latest/), welches überraschenderweise sehr große überschneidende Techniken hat wie Paperless. Mayan EDMS ist *viel* funktionsreicher und kommt ebenso mit einer glatten UI, aber kommt noch mit Python2; basiert jedoch auch auf Django und verwendet ein Konsummodell mit Tesseract und Unpaper. Es kann sein, dass Paperless weniger Ressourcen verbraucht, aber um ehrlich zu sein, hab ich das noch nicht selbst getestet. Eine Sache jedoch ist klar, *Paperless* ist ein **viel** besserer Name.
## Wichtiger Hinweis
Dokumentenscanner werden typischerweise verwendet, um sensible Dokumente zu scannen. Dinge wie die Sozialversicherungsnummer, Steueraufzeichnungen, Rechnungen, etc. Während Paperless die Originaldateien über das Konsumskript verschlüsselt, sind die OCR-Texte *nicht* verschlüsselt und demnach in Klartext gespeichert (es muss durchsuchbar sein, also wenn jemand eine Idee hat, wie man das mit verschlüsselten Daten tun kann: Ich bin ganz Ohr). Das bedeutet, dass Paperless niemals auf einem nicht vertrauten Host laufen sollte. Stattdessen empfehle ich, wenn du es verwenden willst, es lokal auf einem Server in deinem Zuhause laufen zu lassen.
## Spenden
Wie mit aller Freier Software, liegt die Macht weniger in den Finanzen als mehr in den gemeinsamen Bemühungen. Ich schätze wirklich jeden Pull Request und Bugreport, der von Benutzern von Paperless getätigt wird, also bitte macht damit weiter. Wenn du jedoch nicht einer für Programmieren/Design/Dokumentation bist und mich wirklich finanziell unterstützen willst, sage ich nicht nein dazu ;-)
Das Ding ist, mir geht es finanziell OK, also würde ich dich darum bitten, an den [Hochkommissar der Vereinten Nationen für Flüchtlinge](https://donate.unhcr.org/int-en/general) zu spenden. Diese machen wichtige Arbeit und brauchen das Geld viel dringender als ich.
================================================
FILE: README-el.md
================================================
[ [en](README.md) | [de](README-de.md) | el ]

[](https://paperless.readthedocs.org/) [](https://gitter.im/danielquinn/paperless) [](https://travis-ci.org/the-paperless-project/paperless) [](https://coveralls.io/github/the-paperless-project/paperless?branch=master) [](https://github.com/the-paperless-project/paperless/blob/master/THANKS.md)
Ευρετήριο και αρχείο για όλα σας τα σκαναρισμένα έγγραφα
Μισώ το χαρτί. Πέρα από τα περιβαλλοντικά ζητήματα, είναι ο εφιάλτης ενός τεχνικού.
* Δεν υπάρχει η δυνατότητα της αναζήτησης
* Πιάνουν πολύ χώρο
* Τα αντίγραφα ασφαλείας σημάινουν περισσότερο χαρτί
Τους τελευταίους μήνες μου έχει τύχει αρκετές φορές να μην μπορώ να βρω το σωστό έγγραφο. Κάποιες φορές ανακύκλωνα το έγγραφο που χρειαζόμουν (ποιος κρατάει τους λογαριασμούς του νερού για 2 χρόνια;;;) και κάποιες φορές απλά το έχανα ... επειδή έτσι είναι τα χαρτιά. Το έκανα αυτό για να κάνω την ζωή μου πιο εύκολη
## Πως δουλεύει
Η εφαρμογή Paperless δεν ελέγχει το scanner σας, αλλά σας βοηθάει με τα αποτελέσματα του scanner σας.
1. Αγοράστε ένα scanner με πρόσβαση στο δίκτυο σας. Αν χρειάζεστε έμπνευση, δείτε την σελίδα με τα [προτεινόμενα scanner](https://paperless.readthedocs.io/en/latest/scanners.html).
2. Κάντε την ρύθμιση "scan to FTP" ή κάτι παρόμοιο. Θα μπορεί να αποθηκεύει τις σκαναρισμένες εικόνες σε έναν server χωρίς να χρειάζεται να κάνετε κάτι. Φυσικά άμα το scanner σας δεν μπορεί να αποθηκεύσει κάπου τις εικόνες σας αυτόματα μπορείτε να το κάνετε χειροκίνητα. Το Paperless δεν ενδιαφέρεται πως καταλήγουν κάπου τα αρχεία.
3. Να έχετε τον server που τρέχει το OCR script του Paperless να έχει ευρετήριο στην τοπική βάση δεδομένων.
4. Χρησιμοποιήστε το web frontend για να επιλέξετε βάση δεδομένων και να βρείτε αυτό που θέλετε.
5. Κατεβάστε το PDF που θέλετε/χρειάζεστε μέσω του web interface και κάντε ότι θέλετε με αυτό. Μπορείτε ακόμη να το εκτυπώσετε και να το στείλετε, σαν να ήταν το αρχικό. Στις περισσότερες περιπτώσεις κανείς δεν θα το προσέξει ή θα νοιαστεί.
Αυτό είναι που θα πάρετε:

## Documentation
Είναι όλα διαθέσιμα εδώ [ReadTheDocs](https://paperless.readthedocs.org/).
## Απαιτήσεις
Όλα αυτά είναι πολύ απλά, και φιλικά προς τον χρήστη, μια συλλογή με πολύτιμα εργαλεία.
* [ImageMagick](http://imagemagick.org/) μετατρέπει τις εικόνες σε έγχρωμες και ασπρόμαυρες.
* [Tesseract](https://github.com/tesseract-ocr) κάνει την αναγνώρηση των χαρακτήρων.
* [Unpaper](https://github.com/unpaper/unpaper) despeckles and deskews the scanned image.
* [GNU Privacy Guard](https://gnupg.org/) χρησιμοποιείται για κρυπτογράφηση στο backend.
* [Python 3](https://python.org/) είναι η γλώσσα του project.
* [Pillow](https://pypi.python.org/pypi/pillowfight/) Φορτώνει την εικόνα σαν αντικείμενο στην python και μπορεί να χρησιμοποιηθεί με PyOCR
* [PyOCR](https://github.com/jflesch/pyocr) is a slick programmatic wrapper around tesseract.
* [Django](https://www.djangoproject.com/) το framework με το οποίο έγινε το project.
* [Python-GNUPG](http://pythonhosted.org/python-gnupg/) Αποκρυπτογραφεί τα PDF αρχεία στη στιγμή ώστε να κατεβάζετε αποκρυπτογραφημένα αρχεία, αφήνοντας τα κρυπτογραφημένα στον δίσκο.
## Σταθερότητα
Αυτό το project υπάρχει από το 2015 και υπάρχουν αρκετοί άνθρωποι που το χρησιμοποιούν, παρόλα αυτά βρίσκεται σε διαρκή ανάπτυξη (απλά δείτε πότε commit έχουν γίνει στο git history) οπότε μην περιμένετε να είναι 100% σταθερό. Μπορείτε να κάνετε backup την βάση δεδομένων sqlite3, τον φάκελο media και το configuration αρχείο σας ώστε να είστε ασφαλείς.
## Affiliated Projects
Το Paperless υπάρχει εδώ και κάποιο καιρό και άνθρωποι έχουν αρχίσει να φτιάχνουν πράγματα γύρω από αυτό. Αν είσαι ένας από αυτούς τους ανθρώπους, μπορούμε να βάλουμε το project σου σε αυτήν την λίστα:
* [Paperless App](https://github.com/bauerj/paperless_app): Μια εφαρμογή Android / iOS για Paperless.
* [Paperless Desktop](https://github.com/thomasbrueggemann/paperless-desktop): Μια desktop εφαρμογή για εγκατάσταση του Paperless. Τρέχει σε Mac, Linux, και Windows.
* [ansible-role-paperless](https://github.com/ovv/ansible-role-paperless): Ένας εύκολο τρόπος για να τρέχει το Paperless μέσω Ansible.
## Παρόμοια Projects
Υπάρχει ένα άλλο ṕroject που λέγεται [Mayan EDMS](https://mayan.readthedocs.org/en/latest/) το οποίο έχει παρόμοια τεχνικά χαρακτηριστικά με το Paperless σε εντυπωσιακό βαθμό. Επίσης βασισμένο στο Django και χρησιμοποιώντας το consumer model με Tesseract και Unpaper, Mayan EDMS έχει *πολλά* περισσότερα χαρακτηριστικά και έρχεται με ένα επιδέξιο UI, αλλά είναι ακόμα σε Python 2. Μπορεί να είναι ότι το Paperless καταναλώνει λιγότερους πόρους, αλλά για να είμαι ειλικρινής, αυτό είναι μια εικασία την οποία δεν έχω επιβεβαιώσει μόνος μου. Ένα πράγμα είναι σίγουρο, το *Paperless* έχει **πολύ** καλύτερο όνομα.
## Σημαντική Σημείωση
Τα scanner για αρχεία συνήθως χρησιμοποιούνται για ευαίσθητα αρχεία. Πράγματα όπως το ΑΜΚΑ, φορολογικά αρχεία, τιμολόγια κτλπ. Παρόλο που το Paperless κρυπτογραφεί τα αρχικά αρχεία μέσω του consumption script, το κείμενο OCR *δεν είναι* κρυπτογραφημένο και για αυτό αποθηκεύεται (πρέπει να είναι αναζητήσιμο, οπότε αν κάποιος ξέρει να το κάνει αυτό με κρυπτογραφημένα δεδομένα είμαι όλος αυτιά). Αυτό σημάνει ότι το Paperless δεν πρέπει ποτέ να τρέχει σε μη αξιόπιστο πάροχο. Για αυτό συστήνω αν θέλετε να το τρέξετε να το τρέξετε σε έναν τοπικό server σπίτι σας.
## Δωρεές
Όπως με όλα τα δωρεάν λογισμικά, η δύναμη δεν βρίσκεται στα οικονομικά αλλά στην συλλογική προσπάθεια. Αλήθεια εκτιμώ κάθε pull request και bug report που προσφέρεται από τους χρήστες του Paperless, οπότε σας παρακαλώ συνεχίστε. Αν παρόλα αυτά, δεν μπορείτε να γράψετε κώδικα/να κάνέτε design/να γράψετε documentation, και θέλετε να συνεισφέρετε οικονομικά, δεν θα πω όχι ;-)
Το θέμα είναι ότι είμαι οικονομικά εντάξει, οπότε θα σας ζητήσω να δωρίσετε τα χρήματα σας εδώ [United Nations High Commissioner for Refugees](https://donate.unhcr.org/int-en/general). Κάνουν σημαντική δουλειά και χρειάζονται τα χρήματα πολύ περισσότερο από ότι εγώ.
================================================
FILE: README.md
================================================
[ en | [de](README-de.md) | [el](README-el.md) ]

> ## Important news about the future of this project
>
> It's been more than 5 years since I started this project on a whim as an effort to try to get a handle on the massive amount of paper I was dealing with in relation to various visa applications (expat life is complicated!) Since then, the project has *exploded* in popularity, so much so that it overwhelmed me and working on it stopped being "fun" and started becoming a serious source of stress.
>
> In an effort to fix this, I created the Paperless GitHub [organisation](https://github.com/the-paperless-project), and brought on a few people to manage the issue and pull request load. Unfortunately, that model has proven to be unworkable too. With 23 pull requests waiting and 157 issues slowly filling up with confused/annoyed people wanting to get their contributions in, my whole "appoint a few strangers and hope they've got time" idea is showing my lack of foresight and organisational skill.
>
> In the shadow of these difficulties, a fork called [Paperless-ng](https://github.com/jonaswinkler/paperless-ng) written by [Jonas Winkler](https://github.com/jonaswinkler) has cropped up. It's *really* good, and unlike this project, it's actively maintained (at the time of this writing anyway). With 564 forks currently tracked by GitHub, I suspect there are a few more forks worth looking into out there as well.
>
> So, with all of the above in mind, I've decided to archive this project as read-only and suggest that those interested in new updates or submitting patches have a look at Paperless-ng. If you really like "Old Paperless", that's ok too! The project is [GPL licensed](https://github.com/the-paperless-project/paperless/blob/master/LICENSE), so you can fork it and run it on whatever you like so long as you respect the terms of said license.
>
> In time, I may transfer ownership of this organisation to Jonas if he's interested in taking that on, but for the moment, he's happy to run Paperless-ng out of its [current repo](https://github.com/jonaswinkler/paperless-ng). Regardless, if we do decide to make the transfer, I'll post a notification here a few months in advance so that people won't be surprised by new code at this location.
>
> For my part, I'm really happy & proud to have been part of this project, and I'm sorry I've been unable to commit more time to it for everyone. I hope you all understand, and I'm really pleased that this work has been able to continue to live and be useful in a new project. Thank you to everyone who contributed, and for making Free software awesome.
>
> Sincerely,
> [Daniel Quinn](https://github.com/danielquinn)
[](https://paperless.readthedocs.org/)
[](https://gitter.im/danielquinn/paperless)
[](https://travis-ci.org/the-paperless-project/paperless)
[](https://coveralls.io/github/the-paperless-project/paperless?branch=master)
[](https://stackshare.io/the-paperless-project/the-paperless-project)
[](https://github.com/the-paperless-project/paperless/blob/master/THANKS.md)
Index and archive all of your scanned paper documents
I hate paper. Environmental issues aside, it's a tech person's nightmare:
* There's no search feature
* It takes up physical space
* Backups mean more paper
In the past few months I've been bitten more than a few times by the problem of not having the right document around. Sometimes I recycled a document I needed (who keeps water bills for two years?) and other times I just lost it... because paper. I wrote this to make my life easier.
## How it Works
Paperless does not control your scanner, it only helps you deal with what your scanner produces
1. Buy a document scanner that can write to a place on your network. If you need some inspiration, have a look at the [scanner recommendations](https://paperless.readthedocs.io/en/latest/scanners.html) page.
2. Set it up to "scan to FTP" or something similar. It should be able to push scanned images to a server without you having to do anything. Of course if your scanner doesn't know how to automatically upload the file somewhere, you can always do that manually. Paperless doesn't care how the documents get into its local consumption directory.
3. Have the target server run the Paperless consumption script to OCR the file and index it into a local database.
4. Use the web frontend to sift through the database and find what you want.
5. Download the PDF you need/want via the web interface and do whatever you like with it. You can even print it and send it as if it's the original. In most cases, no one will care or notice.
Here's what you get:

## Documentation
It's all available on [ReadTheDocs](https://paperless.readthedocs.io/).
## Requirements
This is all really a quite simple, shiny, user-friendly wrapper around some very powerful tools.
* [ImageMagick](http://imagemagick.org/) converts the images between colour and greyscale.
* [Tesseract](https://github.com/tesseract-ocr) does the character recognition.
* [Unpaper](https://github.com/unpaper/unpaper) despeckles and deskews the scanned image.
* [GNU Privacy Guard](https://gnupg.org/) is used as the encryption backend.
* [Python 3](https://python.org/) is the language of the project.
* [Pillow](https://pypi.python.org/pypi/pillowfight/) loads the image data as a python object to be used with PyOCR.
* [PyOCR](https://github.com/jflesch/pyocr) is a slick programmatic wrapper around tesseract.
* [Django](https://www.djangoproject.com/) is the framework this project is written against.
* [Python-GNUPG](http://pythonhosted.org/python-gnupg/) decrypts the PDFs on-the-fly to allow you to download unencrypted files, leaving the encrypted ones on-disk.
## Project Status
This project has been around since 2015, and there's lots of people using it. For some reason, it's really popular in Germany -- maybe someone over there can clue me in as to why?
I am no longer doing new development on Paperless as it does exactly what I need it to and have since turned my attention to my latest project, [Aletheia](https://github.com/danielquinn/aletheia). However, I'm not abandoning this project. I am happy to field pull requests and answer questions in the issue queue. If you're a developer yourself and want a new feature, float it in the issue queue and/or send me a pull request! I'm happy to add new stuff, but I just don't have the time to do that work myself.
## Affiliated Projects
Paperless has been around a while now, and people are starting to build stuff on top of it. If you're one of those people, we can add your project to this list:
* [Paperless App](https://github.com/bauerj/paperless_app): An Android/iOS app for Paperless.
* [Paperless Desktop](https://github.com/thomasbrueggemann/paperless-desktop): A desktop UI for your Paperless installation. Runs on Mac, Linux, and Windows.
* [ansible-role-paperless](https://github.com/ovv/ansible-role-paperless): An easy way to get Paperless running via Ansible.
* [paperless-cli](https://github.com/stgarf/paperless-cli): A golang command line binary to interact with a Paperless instance.
## Similar Projects
There's another project out there called [Mayan EDMS](https://www.mayan-edms.com/) that has a surprising amount of technical overlap with Paperless. Also based on Django and using a consumer model with Tesseract and Unpaper, Mayan EDMS is *much* more featureful and comes with a slick UI as well, but still in Python 2. It may be that Paperless consumes fewer resources, but to be honest, this is just a guess as I haven't tested this myself. One thing's for certain though, *Paperless* is a **way** better name.
## Important Note
Document scanners are typically used to scan sensitive documents. Things like your social insurance number, tax records, invoices, etc. While Paperless encrypts the original files via the consumption script, the OCR'd text is *not* encrypted and is therefore stored in the clear (it needs to be searchable, so if someone has ideas on how to do that on encrypted data, I'm all ears). This means that Paperless should never be run on an untrusted host. Instead, I recommend that if you do want to use it, run it locally on a server in your own home.
## Donations
As with all Free software, the power is less in the finances and more in the collective efforts. I really appreciate every pull request and bug report offered up by Paperless' users, so please keep that stuff coming. If however, you're not one for coding/design/documentation, and would like to contribute financially, I won't say no ;-)
The thing is, I'm doing ok for money, so I would instead ask you to donate to the [United Nations High Commissioner for Refugees](https://donate.unhcr.org/int-en/general). They're doing important work and they need the money a lot more than I do.
================================================
FILE: THANKS.md
================================================
# Thanks for using Paperless!
Working on this project has been exhausting, but rewarding at the same time.
It's just wonderful that so many people are using this thing, and in so many
crazy ways.
This file is here for everyone to post their own stories about how you use this
code. It helps me to understand who's using it and why, and maybe to give
others an idea of how it might be used. It's based on a Twitter exchange
between [John Glanville](https://twitter.com/hexapodium) and
[Julia Evans](https://github.com/jvns) and later better defined [here](https://github.com/paulmolluzzo/thanks-md).
To contribute, simply issue a pull request that appends to this file something
like this:
```
### Your Name
Some friendly message
```
================================================
FILE: ci/deploy-docker
================================================
#!/bin/bash
if [ "${DOCKER_USERNAME}" == "" -o "${DOCKER_PASSWORD}" == "" ]
then
exit 0
fi
docker login --username=${DOCKER_USERNAME} --password=${DOCKER_PASSWORD}
if [ "${TRAVIS_TAG}" != "" ]
then
docker tag the-paperless-project/paperless the-paperless-project/paperless:${TRAVIS_TAG}
docker push the-paperless-project/paperless:${TRAVIS_TAG}
else
docker push the-paperless-project/paperless
fi
================================================
FILE: data/.keep
================================================
================================================
FILE: docker-compose.env.example
================================================
# Environment variables to set for Paperless
# Commented out variables will be replaced with a default within Paperless.
#
# In addition to what you see here, you can also define any values you find in
# paperless.conf.example here. Values like:
#
# * PAPERLESS_PASSPHRASE
# * PAPERLESS_CONSUMPTION_DIR
# * PAPERLESS_CONSUME_MAIL_HOST
#
# ...are all explained in that file but can be defined here, since the Docker
# installation doesn't make use of paperless.conf.
#
# NOTE: values in paperless.conf should be wrapped in double quotes, but not in this file
# Example:
# paperless.conf: PAPERLESS_FORGIVING_OCR="true"
# docker-compose.env (this file): PAPERLESS_FORGIVING_OCR=true
# Use this variable to set a timezone for the Paperless Docker containers. If not specified, defaults to UTC.
# TZ=America/Los_Angeles
# Additional languages to install for text recognition. Note that this is
# different from PAPERLESS_OCR_LANGUAGE (default=eng), which defines the
# default language used when guessing the language from the OCR output.
# PAPERLESS_OCR_LANGUAGES=deu ita
# Set Paperless to use SSL for the web interface.
# Enabling this will require ssl.key and ssl.cert files in paperless' data directory.
# PAPERLESS_USE_SSL=false
# You can change the default user and group id to a custom one
# USERMAP_UID=1000
# USERMAP_GID=1000
================================================
FILE: docker-compose.yml.example
================================================
version: '2.1'
services:
webserver:
build: ./
# uncomment the following line to start automatically on system boot
# restart: always
ports:
# You can adapt the port you want Paperless to listen on by
# modifying the part before the `:`.
- "8000:8000"
healthcheck:
test: ["CMD", "curl" , "-f", "http://localhost:8000"]
interval: 30s
timeout: 10s
retries: 5
volumes:
- data:/usr/src/paperless/data
- media:/usr/src/paperless/media
# You have to adapt the local path you want the consumption
# directory to mount to by modifying the part before the ':'.
- ./consume:/consume
env_file: docker-compose.env
# The reason the line is here is so that the webserver that doesn't do
# any text recognition and doesn't have to install unnecessary
# languages the user might have set in the env-file by overwriting the
# value with nothing.
environment:
- PAPERLESS_OCR_LANGUAGES=
command: ["gunicorn", "-b", "0.0.0.0:8000"]
consumer:
build: ./
# uncomment the following line to start automatically on system boot
# restart: always
depends_on:
webserver:
condition: service_healthy
volumes:
- data:/usr/src/paperless/data
- media:/usr/src/paperless/media
# This should be set to the same value as the consume directory
# in the webserver service above.
- ./consume:/consume
# Likewise, you can add a local path to mount a directory for
# exporting. This is not strictly needed for paperless to
# function, only if you're exporting your files: uncomment
# it and fill in a local path if you know you're going to
# want to export your documents.
# - /path/to/another/arbitrary/place:/export
env_file: docker-compose.env
command: ["document_consumer"]
volumes:
data:
media:
================================================
FILE: docs/Dockerfile
================================================
FROM python:3.5.1
MAINTAINER Pit Kleyersburg
# Install Sphinx and Pygments
RUN pip install Sphinx Pygments
# Setup directories, copy data
RUN mkdir /build
COPY . /build
WORKDIR /build/docs
# Build documentation
RUN make html
# Start webserver
WORKDIR /build/docs/_build/html
EXPOSE 8000/tcp
CMD ["python3", "-m", "http.server"]
================================================
FILE: docs/Makefile
================================================
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make ' where is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/RIPEAtlasToolsMagellan.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/RIPEAtlasToolsMagellan.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/RIPEAtlasToolsMagellan"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/RIPEAtlasToolsMagellan"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
================================================
FILE: docs/_static/.keep
================================================
================================================
FILE: docs/_static/custom.css
================================================
/* override table width restrictions */
@media screen and (min-width: 767px) {
.wy-table-responsive table td {
/* !important prevents the common CSS stylesheets from
overriding this as on RTD they are loaded after this stylesheet */
white-space: normal !important;
}
.wy-table-responsive {
overflow: visible !important;
}
}
================================================
FILE: docs/api.rst
================================================
.. _api:
The REST API
############
Paperless makes use of the `Django REST Framework`_ standard API interface
because of its inherent awesomeness. Conveniently, the system is also
self-documenting, so to learn more about the access points, schema, what's
accepted and what isn't, you need only visit ``/api`` on your local Paperless
installation.
.. _Django REST Framework: http://django-rest-framework.org/
.. _api-uploading:
Uploading
---------
File uploads in an API are hard and so far as I've been able to tell, there's
no standard way of accepting them, so rather than crowbar file uploads into the
REST API and endure that headache, I've left that process to a simple HTTP
POST, documented on the :ref:`consumption page `.
================================================
FILE: docs/changelog.rst
================================================
Changelog
#########
2.7.0
=====
* `syntonym`_ submitted a pull request to catch IMAP connection errors `#475`_.
* `Stéphane Brunner`_ added ``psycopg2`` to the Pipfile `#489`_. He also fixed
a syntax error in ``docker-compose.yml.example`` `#488`_ and added [DjangoQL](https://github.com/ivelum/djangoql),
which allows a litany of handy search functionality `#492`_.
* `CkuT`_ and `JOKer`_ hacked out a simple, but super-helpful optimisation to
how the thumbnails are served up, improving performance considerably `#481`_.
* `tsia`_ added a few fields to the tags REST API. `#483`_.
* `Brian Cribbs`_ improved the documentation to help people using Paperless
over NFS `#484`_.
* `Brendan M. Sleight`_ updated the documentation to include a note for setting the
``DEBUG`` value. The ``paperless.conf.example`` file was also updated to
mirror the project defaults.
2.6.1
=====
* We now have a logo, complete with a favicon :-)
* Removed some problematic tests.
* Fix the docker-compose example config to include a shared consume volume so
that using the push API will work for users of the Docker install. Thanks to
`Colin Frei`_ for fixing this in `#466`_.
* `khrise`_ submitted a pull request to include the ``added`` property to the
REST API `#471`_.
2.6.0
=====
* Allow an infinite number of logs to be deleted. Thanks to `Ulli`_ for noting
the problem in `#433`_.
* Fix the ``RecentCorrespondentsFilter`` correspondents filter that was added
in 2.4 to play nice with the defaults. Thanks to `tsia`_ and `Sblop`_ who
pointed this out. `#423`_.
* Updated dependencies to include (among other things) a security patch to
requests.
* Fix text in sample data for tests so that the language guesser stops thinking
that everything is in Catalan because we had *Lorem ipsum* in there.
* Tweaked the gunicorn sample command to use filesystem paths instead of Python
paths. `#441`_
* Added pretty colour boxes next to the hex values in the Tags section, thanks
to a pull request from `Joshua Taillon`_ `#442`_.
* Added a ``.editorconfig`` file to better specify coding style.
* `Joshua Taillon`_ also added some logic to tie Paperless' date guessing logic
into how it parses file names on import. `#440`_
2.5.0
=====
* **New dependency**: Paperless now optimises thumbnail generation with
`optipng`_, so you'll need to install that somewhere in your PATH or declare
its location in ``PAPERLESS_OPTIPNG_BINARY``. The Docker image has already
been updated on the Docker Hub, so you just need to pull the latest one from
there if you're a Docker user.
* "Login free" instances of Paperless were breaking whenever you tried to edit
objects in the admin: adding/deleting tags or correspondents, or even fixing
spelling. This was due to the "user hack" we were applying to sessions that
weren't using a login, as that hack user didn't have a valid id. The fix was
to attribute the first user id in the system to this hack user. `#394`_
* A problem in how we handle slug values on Tags and Correspondents required a
few changes to how we handle this field `#393`_:
1. Slugs are no longer editable. They're derived from the name of the tag or
correspondent at save time, so if you wanna change the slug, you have to
change the name, and even then you're restricted to the rules of the
``slugify()`` function. The slug value is still visible in the admin
though.
2. I've added a migration to go over all existing tags & correspondents and
rewrite the ``.slug`` values to ones conforming to the ``slugify()``
rules.
3. The consumption process now uses the same rules as ``.save()`` in
determining a slug and using that to check for an existing
tag/correspondent.
* An annoying bug in the date capture code was causing some bogus dates to be
attached to documents, which in turn busted the UI. Thanks to `Andrew Peng`_
for reporting this. `#414`_.
* A bug in the Dockerfile meant that Tesseract language files weren't being
installed correctly. `euri10`_ was quick to provide a fix: `#406`_, `#413`_.
* Document consumption is now wrapped in a transaction as per an old ticket
`#262`_.
* The ``get_date()`` functionality of the parsers has been consolidated onto
the ``DocumentParser`` class since much of that code was redundant anyway.
2.4.0
=====
* A new set of actions are now available thanks to `jonaswinkler`_'s very first
pull request! You can now do nifty things like tag documents in bulk, or set
correspondents in bulk. `#405`_
* The import/export system is now a little smarter. By default, documents are
tagged as ``unencrypted``, since exports are by their nature unencrypted.
It's now in the import step that we decide the storage type. This allows you
to export from an encrypted system and import into an unencrypted one, or
vice-versa.
* The migration history has been slightly modified to accommodate PostgreSQL
users. Additionally, you can now tell paperless to use PostgreSQL simply by
declaring ``PAPERLESS_DBUSER`` in your environment. This will attempt to
connect to your Postgres database without a password unless you also set
``PAPERLESS_DBPASS``.
* A bug was found in the REST API filter system that was the result of an
update of django-filter some time ago. This has now been patched in `#412`_.
Thanks to `thepill`_ for spotting it!
2.3.0
=====
* Support for consuming plain text & markdown documents was added by
`Joshua Taillon`_! This was a long-requested feature, and it's addition is
likely to be greatly appreciated by the community: `#395`_ Thanks also to
`David Martin`_ for his assistance on the issue.
* `dubit0`_ found & fixed a bug that prevented management commands from running
before we had an operational database: `#396`_
* Joshua also added a simple update to the thumbnail generation process to
improve performance: `#399`_
* As his last bit of effort on this release, Joshua also added some code to
allow you to view the documents inline rather than download them as an
attachment. `#400`_
* Finally, `ahyear`_ found a slip in the Docker documentation and patched it.
`#401`_
2.2.1
=====
* `Kyle Lucy`_ reported a bug quickly after the release of 2.2.0 where we broke
the ``DISABLE_LOGIN`` feature: `#392`_.
2.2.0
=====
* Thanks to `dadosch`_, `Wolfgang Mader`_, and `Tim Brooks`_ this is the first
version of Paperless that supports Django 2.0! As a result of their hard
work, you can now also run Paperless on Python 3.7 as well: `#386`_ &
`#390`_.
* `Stéphane Brunner`_ added a few lines of code that made tagging interface a
lot easier on those of us with lots of different tags: `#391`_.
* `Kilian Koeltzsch`_ noticed a bug in how we capture & automatically create
tags, so that's fixed now too: `#384`_.
* `erikarvstedt`_ tweaked the behaviour of the test suite to be better behaved
for packaging environments: `#383`_.
* `Lukasz Soluch`_ added CORS support to make building a new Javascript-based
front-end cleaner & easier: `#387`_.
2.1.0
=====
* `Enno Lohmeier`_ added three simple features that make Paperless a lot more
user (and developer) friendly:
1. There's a new search box on the front page: `#374`_.
2. The correspondents & tags pages now have a column showing the number of
relevant documents: `#375`_.
3. The Dockerfile has been tweaked to build faster for those of us who are
doing active development on Paperless using the Docker environment:
`#376`_.
* You now also have the ability to customise the interface to your heart's
content by creating a file called ``overrides.css`` and/or ``overrides.js``
in the root of your media directory. Thanks to `Mark McFate`_ for this
idea: `#371`_
2.0.0
=====
This is a big release as we've changed a core-functionality of Paperless: we no
longer encrypt files with GPG by default.
The reasons for this are many, but it boils down to that the encryption wasn't
really all that useful, as files on-disk were still accessible so long as you
had the key, and the key was most typically stored in the config file. In
other words, your files are only as safe as the ``paperless`` user is. In
addition to that, *the contents of the documents were never encrypted*, so
important numbers etc. were always accessible simply by querying the database.
Still, it was better than nothing, but the consensus from users appears to be
that it was more an annoyance than anything else, so this feature is now turned
off unless you explicitly set a passphrase in your config file.
Migrating from 1.x
------------------
Encryption isn't gone, it's just off for new users. So long as you have
``PAPERLESS_PASSPHRASE`` set in your config or your environment, Paperless
should continue to operate as it always has. If however, you want to drop
encryption too, you only need to do two things:
1. Run ``./manage.py migrate && ./manage.py change_storage_type gpg unencrypted``.
This will go through your entire database and Decrypt All The Things.
2. Remove ``PAPERLESS_PASSPHRASE`` from your ``paperless.conf`` file, or simply
stop declaring it in your environment.
Special thanks to `erikarvstedt`_, `matthewmoto`_, and `mcronce`_ who did the
bulk of the work on this big change.
1.4.0
=====
* `Quentin Dawans`_ has refactored the document consumer to allow for some
command-line options. Notably, you can now direct it to consume from a
particular ``--directory``, limit the ``--loop-time``, set the time between
mail server checks with ``--mail-delta`` or just run it as a one-off with
``--one-shot``. See `#305`_ & `#313`_ for more information.
* Refactor the use of travis/tox/pytest/coverage into two files:
``.travis.yml`` and ``setup.cfg``.
* Start generating requirements.txt from a Pipfile. I'll probably switch over
to just using pipenv in the future.
* All for a alternative FreeBSD-friendly location for ``paperless.conf``.
Thanks to `Martin Arendtsen`_ who provided this (`#322`_).
* Document consumption events are now logged in the Django admin events log.
Thanks to `CkuT`_ for doing the legwork on this one and to `Quentin Dawans`_
& `David Martin`_ for helping to coordinate & work out how the feature would
be developed.
* `erikarvstedt`_ contributed a pull request (`#328`_) to add ``--noreload``
to the default server start process. This helps reduce the load imposed
by the running webservice.
* Through some discussion on `#253`_ and `#323`_, we've removed a few of the
hardcoded URL values to make it easier for people to host Paperless on a
subdirectory. Thanks to `Quentin Dawans`_ and `Kyle Lucy`_ for helping to
work this out.
* The clickable area for documents on the listing page has been increased to a
more predictable space thanks to a glorious hack from `erikarvstedt`_ in
`#344`_.
* `Strubbl`_ noticed an annoying bug in the bash script wrapping the Docker
entrypoint and fixed it with some very creating Bash skills: `#352`_.
* You can now use the search field to find documents by tag thanks to
`thinkjk`_'s *first ever issue*: `#354`_.
* Inotify is now being used to detect additions to the consume directory thanks
to some excellent work from `erikarvstedt`_ on `#351`_
1.3.0
=====
* You can now run Paperless without a login, though you'll still have to create
at least one user. This is thanks to a pull-request from `matthewmoto`_:
`#295`_. Note that logins are still required by default, and that you need
to disable them by setting ``PAPERLESS_DISABLE_LOGIN="true"`` in your
environment or in ``/etc/paperless.conf``.
* Fix for `#303`_ where sketchily-formatted documents could cause the consumer
to break and insert half-records into the database breaking all sorts of
things. We now capture the return codes of both ``convert`` and ``unpaper``
and fail-out nicely.
* Fix for additional date types thanks to input from `Isaac`_ and code from
`BastianPoe`_ (`#301`_).
* Fix for running migrations in the Docker container (`#299`_). Thanks to
`Georgi Todorov`_ for the fix (`#300`_) and to `Pit`_ for the review.
* Fix for Docker cases where the issuing user is not UID 1000. This was a
collaborative fix between `Jeffrey Portman`_ and `Pit`_ in `#311`_ and
`#312`_ to fix `#306`_.
* Patch the historical migrations to support MySQL's um, *interesting* way of
handing indexes (`#308`_). Thanks to `Simon Taddiken`_ for reporting the
problem and helping me find where to fix it.
1.2.0
=====
* New Docker image, now based on Alpine, thanks to the efforts of `addadi`_
and `Pit`_. This new image is dramatically smaller than the Debian-based
one, and it also has `a new home on Docker Hub`_. A proper thank-you to
`Pit`_ for hosting the image on his Docker account all this time, but after
some discussion, we decided the image needed a more *official-looking* home.
* `BastianPoe`_ has added the long-awaited feature to automatically skip the
OCR step when the PDF already contains text. This can be overridden by
setting ``PAPERLESS_OCR_ALWAYS=YES`` either in your ``paperless.conf`` or
in the environment. Note that this also means that Paperless now requires
``libpoppler-cpp-dev`` to be installed. **Important**: You'll need to run
``pip install -r requirements.txt`` after the usual ``git pull`` to
properly update.
* `BastianPoe`_ has also contributed a monumental amount of work (`#291`_) to
solving `#158`_: setting the document creation date based on finding a date
in the document text.
1.1.0
=====
* Fix for `#283`_, a redirect bug which broke interactions with
paperless-desktop. Thanks to `chris-aeviator`_ for reporting it.
* Addition of an optional new financial year filter, courtesy of
`David Martin`_ `#256`_
* Fixed a typo in how thumbnails were named in exports `#285`_, courtesy of
`Dan Panzarella`_
1.0.0
=====
* Upgrade to Django 1.11. **You'll need to run
``pip install -r requirements.txt`` after the usual ``git pull`` to
properly update**.
* Replace the templatetag-based hack we had for document listing in favour of
a slightly less ugly solution in the form of another template tag with less
copypasta.
* Support for multi-word-matches for auto-tagging thanks to an excellent
patch from `ishirav`_ `#277`_.
* Fixed a CSS bug reported by `Stefan Hagen`_ that caused an overlapping of
the text and checkboxes under some resolutions `#272`_.
* Patched the Docker config to force the serving of static files. Credit for
this one goes to `dev-rke`_ via `#248`_.
* Fix file permissions during Docker start up thanks to `Pit`_ on `#268`_.
* Date fields in the admin are now expressed as HTML5 date fields thanks to
`Lukas Winkler`_'s issue `#278`_
0.8.0
=====
* Paperless can now run in a subdirectory on a host (``/paperless``), rather
than always running in the root (``/``) thanks to `maphy-psd`_'s work on
`#255`_.
0.7.0
=====
* **Potentially breaking change**: As per `#235`_, Paperless will no longer
automatically delete documents attached to correspondents when those
correspondents are themselves deleted. This was Django's default
behaviour, but didn't make much sense in Paperless' case. Thanks to
`Thomas Brueggemann`_ and `David Martin`_ for their input on this one.
* Fix for `#232`_ wherein Paperless wasn't recognising ``.tif`` files
properly. Thanks to `ayounggun`_ for reporting this one and to
`Kusti Skytén`_ for posting the correct solution in the Github issue.
0.6.0
=====
* Abandon the shared-secret trick we were using for the POST API in favour
of BasicAuth or Django session.
* Fix the POST API so it actually works. `#236`_
* **Breaking change**: We've dropped the use of ``PAPERLESS_SHARED_SECRET``
as it was being used both for the API (now replaced with a normal auth)
and form email polling. Now that we're only using it for email, this
variable has been renamed to ``PAPERLESS_EMAIL_SECRET``. The old value
will still work for a while, but you should change your config if you've
been using the email polling feature. Thanks to `Joshua Gilman`_ for all
the help with this feature.
0.5.0
=====
* Support for fuzzy matching in the auto-tagger & auto-correspondent systems
thanks to `Jake Gysland`_'s patch `#220`_.
* Modified the Dockerfile to prepare an export directory (`#212`_). Thanks
to combined efforts from `Pit`_ and `Strubbl`_ in working out the kinks on
this one.
* Updated the import/export scripts to include support for thumbnails. Big
thanks to `CkuT`_ for finding this shortcoming and doing the work to get
it fixed in `#224`_.
* All of the following changes are thanks to `David Martin`_:
* Bumped the dependency on pyocr to 0.4.7 so new users can make use of
Tesseract 4 if they so prefer (`#226`_).
* Fixed a number of issues with the automated mail handler (`#227`_, `#228`_)
* Amended the documentation for better handling of systemd service files (`#229`_)
* Amended the Django Admin configuration to have nice headers (`#230`_)
0.4.1
=====
* Fix for `#206`_ wherein the pluggable parser didn't recognise files with
all-caps suffixes like ``.PDF``
0.4.0
=====
* Introducing reminders. See `#199`_ for more information, but the short
explanation is that you can now attach simple notes & times to documents
which are made available via the API. Currently, the default API
(basically just the Django admin) doesn't really make use of this, but
`Thomas Brueggemann`_ over at `Paperless Desktop`_ has said that he would
like to make use of this feature in his project.
0.3.6
=====
* Fix for `#200`_ (!!) where the API wasn't configured to allow updating the
correspondent or the tags for a document.
* The ``content`` field is now optional, to allow for the edge case of a
purely graphical document.
* You can no longer add documents via the admin. This never worked in the
first place, so all I've done here is remove the link to the broken form.
* The consumer code has been heavily refactored to support a pluggable
interface. Install a paperless consumer via pip and tell paperless about
it with an environment variable, and you're good to go. Proper
documentation is on its way.
0.3.5
=====
* A serious facelift for the documents listing page wherein we drop the
tabular layout in favour of a tiled interface.
* Users can now configure the number of items per page.
* Fix for `#171`_: Allow users to specify their own ``SECRET_KEY`` value.
* Moved the dotenv loading to the top of settings.py
* Fix for `#112`_: Added checks for binaries required for document
consumption.
0.3.4
=====
* Removal of django-suit due to a licensing conflict I bumped into in 0.3.3.
Note that you *can* use Django Suit with Paperless, but only in a
non-profit situation as their free license prohibits for-profit use. As a
result, I can't bundle Suit with Paperless without conflicting with the
GPL. Further development will be done against the stock Django admin.
* I shrunk the thumbnails a little 'cause they were too big for me, even on
my high-DPI monitor.
* BasicAuth support for document and thumbnail downloads, as well as the Push
API thanks to @thomasbrueggemann. See `#179`_.
0.3.3
=====
* Thumbnails in the UI and a Django-suit -based face-lift courtesy of @ekw!
* Timezone, items per page, and default language are now all configurable,
also thanks to @ekw.
0.3.2
=====
* Fix for `#172`_: defaulting ALLOWED_HOSTS to ``["*"]`` and allowing the
user to set her own value via ``PAPERLESS_ALLOWED_HOSTS`` should the need
arise.
0.3.1
=====
* Added a default value for ``CONVERT_BINARY``
0.3.0
=====
* Updated to using django-filter 1.x
* Added some system checks so new users aren't confused by misconfigurations.
* Consumer loop time is now configurable for systems with slow writes. Just
set ``PAPERLESS_CONSUMER_LOOP_TIME`` to a number of seconds. The default
is 10.
* As per `#44`_, we've removed support for ``PAPERLESS_CONVERT``,
``PAPERLESS_CONSUME``, and ``PAPERLESS_SECRET``. Please use
``PAPERLESS_CONVERT_BINARY``, ``PAPERLESS_CONSUMPTION_DIR``, and
``PAPERLESS_SHARED_SECRET`` respectively instead.
0.2.0
=====
* `#150`_: The media root is now a variable you can set in
``paperless.conf``.
* `#148`_: The database location (sqlite) is now a variable you can set in
``paperless.conf``.
* `#146`_: Fixed a bug that allowed unauthorised access to the ``/fetch``
URL.
* `#131`_: Document files are now automatically removed from disk when
they're deleted in Paperless.
* `#121`_: Fixed a bug where Paperless wasn't setting document creation time
based on the file naming scheme.
* `#81`_: Added a hook to run an arbitrary script after every document is
consumed.
* `#98`_: Added optional environment variables for ImageMagick so that it
doesn't explode when handling Very Large Documents or when it's just
running on a low-memory system. Thanks to `Florian Harr`_ for his help on
this one.
* `#89`_ Ported the auto-tagging code to correspondents as well. Thanks to
`Justin Snyman`_ for the pointers in the issue queue.
* Added support for guessing the date from the file name along with the
correspondent, title, and tags. Thanks to `Tikitu de Jager`_ for his pull
request that I took forever to merge and to `Pit`_ for his efforts on the
regex front.
* `#94`_: Restored support for changing the created date in the UI. Thanks
to `Martin Honermeyer`_ and `Tim White`_ for working with me on this.
0.1.1
=====
* Potentially **Breaking Change**: All references to "sender" in the code
have been renamed to "correspondent" to better reflect the nature of the
property (one could quite reasonably scan a document before sending it to
someone.)
* `#67`_: Rewrote the document exporter and added a new importer that allows
for full metadata retention without depending on the file name and
modification time. A big thanks to `Tikitu de Jager`_, `Pit`_,
`Florian Jung`_, and `Christopher Luu`_ for their code snippets and
contributing conversation that lead to this change.
* `#20`_: Added *unpaper* support to help in cleaning up the scanned image
before it's OCR'd. Thanks to `Pit`_ for this one.
* `#71`_ Added (encrypted) thumbnails in anticipation of a proper UI.
* `#68`_: Added support for using a proper config file at
``/etc/paperless.conf`` and modified the systemd unit files to use it.
* Refactored the Vagrant installation process to use environment variables
rather than asking the user to modify ``settings.py``.
* `#44`_: Harmonise environment variable names with constant names.
* `#60`_: Setup logging to actually use the Python native logging framework.
* `#53`_: Fixed an annoying bug that caused ``.jpeg`` and ``.JPG`` images
to be imported but made unavailable.
0.1.0
=====
* Docker support! Big thanks to `Wayne Werner`_, `Brian Conn`_, and
`Tikitu de Jager`_ for this one, and especially to `Pit`_
who spearheadded this effort.
* A simple REST API is in place, but it should be considered unstable.
* Cleaned up the consumer to use temporary directories instead of a single
scratch space. (Thanks `Pit`_)
* Improved the efficiency of the consumer by parsing pages more intelligently
and introducing a threaded OCR process (thanks again `Pit`_).
* `#45`_: Cleaned up the logic for tag matching. Reported by `darkmatter`_.
* `#47`_: Auto-rotate landscape documents. Reported by `Paul`_ and fixed by
`Pit`_.
* `#48`_: Matching algorithms should do so on a word boundary (`darkmatter`_)
* `#54`_: Documented the re-tagger (`zedster`_)
* `#57`_: Make sure file is preserved on import failure (`darkmatter`_)
* Added tox with pep8 checking
0.0.6
=====
* Added support for parallel OCR (significant work from `Pit`_)
* Sped up the language detection (significant work from `Pit`_)
* Added simple logging
0.0.5
=====
* Added support for image files as documents (png, jpg, gif, tiff)
* Added a crude means of HTTP POST for document imports
* Added IMAP mail support
* Added a re-tagging utility
* Documentation for the above as well as data migration
0.0.4
=====
* Added automated tagging basted on keyword matching
* Cleaned up the document listing page
* Removed ``User`` and ``Group`` from the admin
* Added ``pytz`` to the list of requirements
0.0.3
=====
* Added basic tagging
0.0.2
=====
* Added language detection
* Added datestamps to ``document_exporter``.
* Changed ``settings.TESSERACT_LANGUAGE`` to ``settings.OCR_LANGUAGE``.
0.0.1
=====
* Initial release
.. _Brian Conn: https://github.com/TheConnMan
.. _Christopher Luu: https://github.com/nuudles
.. _Florian Jung: https://github.com/the01
.. _Tikitu de Jager: https://github.com/tikitu
.. _Paul: https://github.com/polo2ro
.. _Pit: https://github.com/pitkley
.. _Wayne Werner: https://github.com/waynew
.. _darkmatter: https://github.com/darkmatter
.. _zedster: https://github.com/zedster
.. _Martin Honermeyer: https://github.com/djmaze
.. _Tim White: https://github.com/timwhite
.. _Florian Harr: https://github.com/evils
.. _Justin Snyman: https://github.com/stringlytyped
.. _Thomas Brueggemann: https://github.com/thomasbrueggemann
.. _Jake Gysland: https://github.com/jgysland
.. _Strubbl: https://github.com/strubbl
.. _CkuT: https://github.com/CkuT
.. _David Martin: https://github.com/ddddavidmartin
.. _Paperless Desktop: https://github.com/thomasbrueggemann/paperless-desktop
.. _Joshua Gilman: https://github.com/jmgilman
.. _ayounggun: https://github.com/ayounggun
.. _Kusti Skytén: https://github.com/kskyten
.. _maphy-psd: https://github.com/maphy-psd
.. _ishirav: https://github.com/ishirav
.. _Stefan Hagen: https://github.com/xkpd3
.. _dev-rke: https://github.com/dev-rke
.. _Lukas Winkler: https://github.com/Findus23
.. _chris-aeviator: https://github.com/chris-aeviator
.. _Dan Panzarella: https://github.com/pzl
.. _addadi: https://github.com/addadi
.. _BastianPoe: https://github.com/BastianPoe
.. _matthewmoto: https://github.com/matthewmoto
.. _Isaac: https://github.com/isaacsando
.. _Georgi Todorov: https://github.com/TeraHz
.. _Jeffrey Portman: https://github.com/ChromoX
.. _Simon Taddiken: https://github.com/skuzzle
.. _Quentin Dawans: https://github.com/ovv
.. _Martin Arendtsen: https://github.com/Arendtsen
.. _erikarvstedt: https://github.com/erikarvstedt
.. _Kyle Lucy: https://github.com/kmlucy
.. _thinkjk: https://github.com/thinkjk
.. _mcronce: https://github.com/mcronce
.. _Enno Lohmeier: https://github.com/elohmeier
.. _Mark McFate: https://github.com/SummittDweller
.. _dadosch: https://github.com/dadosch
.. _Wolfgang Mader: https://github.com/wmader
.. _Tim Brooks: https://github.com/brookst
.. _Stéphane Brunner: https://github.com/sbrunner
.. _Kilian Koeltzsch: https://github.com/kiliankoe
.. _Lukasz Soluch: https://github.com/LukaszSolo
.. _Joshua Taillon: https://github.com/jat255
.. _dubit0: https://github.com/dubit0
.. _ahyear: https://github.com/ahyear
.. _jonaswinkler: https://github.com/jonaswinkler
.. _thepill: https://github.com/thepill
.. _Andrew Peng: https://github.com/pengc99
.. _euri10: https://github.com/euri10
.. _Ulli: https://github.com/Ulli2k
.. _tsia: https://github.com/tsia
.. _Sblop: https://github.com/Sblop
.. _Colin Frei: https://github.com/colinfrei
.. _khrise: https://github.com/khrise
.. _syntonym: https://github.com/syntonym
.. _JOKer: https://github.com/MasterofJOKers
.. _Brian Cribbs: https://github.com/cribbstechnolog
.. _Brendan M. Sleight: https://github.com/bmsleight
.. _#20: https://github.com/the-paperless-project/paperless/issues/20
.. _#44: https://github.com/the-paperless-project/paperless/issues/44
.. _#45: https://github.com/the-paperless-project/paperless/issues/45
.. _#47: https://github.com/the-paperless-project/paperless/issues/47
.. _#48: https://github.com/the-paperless-project/paperless/issues/48
.. _#53: https://github.com/the-paperless-project/paperless/issues/53
.. _#54: https://github.com/the-paperless-project/paperless/issues/54
.. _#57: https://github.com/the-paperless-project/paperless/issues/57
.. _#60: https://github.com/the-paperless-project/paperless/issues/60
.. _#67: https://github.com/the-paperless-project/paperless/issues/67
.. _#68: https://github.com/the-paperless-project/paperless/issues/68
.. _#71: https://github.com/the-paperless-project/paperless/issues/71
.. _#81: https://github.com/the-paperless-project/paperless/issues/81
.. _#89: https://github.com/the-paperless-project/paperless/issues/89
.. _#94: https://github.com/the-paperless-project/paperless/issues/94
.. _#98: https://github.com/the-paperless-project/paperless/issues/98
.. _#112: https://github.com/the-paperless-project/paperless/issues/112
.. _#121: https://github.com/the-paperless-project/paperless/issues/121
.. _#131: https://github.com/the-paperless-project/paperless/issues/131
.. _#146: https://github.com/the-paperless-project/paperless/issues/146
.. _#148: https://github.com/the-paperless-project/paperless/pull/148
.. _#150: https://github.com/the-paperless-project/paperless/pull/150
.. _#158: https://github.com/the-paperless-project/paperless/issues/158
.. _#171: https://github.com/the-paperless-project/paperless/issues/171
.. _#172: https://github.com/the-paperless-project/paperless/issues/172
.. _#179: https://github.com/the-paperless-project/paperless/pull/179
.. _#199: https://github.com/the-paperless-project/paperless/issues/199
.. _#200: https://github.com/the-paperless-project/paperless/issues/200
.. _#206: https://github.com/the-paperless-project/paperless/issues/206
.. _#212: https://github.com/the-paperless-project/paperless/pull/212
.. _#220: https://github.com/the-paperless-project/paperless/pull/220
.. _#224: https://github.com/the-paperless-project/paperless/pull/224
.. _#226: https://github.com/the-paperless-project/paperless/pull/226
.. _#227: https://github.com/the-paperless-project/paperless/pull/227
.. _#228: https://github.com/the-paperless-project/paperless/pull/228
.. _#229: https://github.com/the-paperless-project/paperless/pull/229
.. _#230: https://github.com/the-paperless-project/paperless/pull/230
.. _#232: https://github.com/the-paperless-project/paperless/issues/232
.. _#235: https://github.com/the-paperless-project/paperless/issues/235
.. _#236: https://github.com/the-paperless-project/paperless/issues/236
.. _#255: https://github.com/the-paperless-project/paperless/pull/255
.. _#268: https://github.com/the-paperless-project/paperless/pull/268
.. _#277: https://github.com/the-paperless-project/paperless/pull/277
.. _#272: https://github.com/the-paperless-project/paperless/issues/272
.. _#248: https://github.com/the-paperless-project/paperless/issues/248
.. _#278: https://github.com/the-paperless-project/paperless/issues/248
.. _#283: https://github.com/the-paperless-project/paperless/issues/283
.. _#256: https://github.com/the-paperless-project/paperless/pull/256
.. _#285: https://github.com/the-paperless-project/paperless/pull/285
.. _#291: https://github.com/the-paperless-project/paperless/pull/291
.. _#295: https://github.com/the-paperless-project/paperless/pull/295
.. _#299: https://github.com/the-paperless-project/paperless/issues/299
.. _#300: https://github.com/the-paperless-project/paperless/pull/300
.. _#301: https://github.com/the-paperless-project/paperless/issues/301
.. _#303: https://github.com/the-paperless-project/paperless/issues/303
.. _#305: https://github.com/the-paperless-project/paperless/issues/305
.. _#306: https://github.com/the-paperless-project/paperless/issues/306
.. _#308: https://github.com/the-paperless-project/paperless/issues/308
.. _#311: https://github.com/the-paperless-project/paperless/pull/311
.. _#312: https://github.com/the-paperless-project/paperless/pull/312
.. _#313: https://github.com/the-paperless-project/paperless/pull/313
.. _#322: https://github.com/the-paperless-project/paperless/pull/322
.. _#328: https://github.com/the-paperless-project/paperless/pull/328
.. _#253: https://github.com/the-paperless-project/paperless/issues/253
.. _#262: https://github.com/the-paperless-project/paperless/issues/262
.. _#323: https://github.com/the-paperless-project/paperless/issues/323
.. _#344: https://github.com/the-paperless-project/paperless/pull/344
.. _#351: https://github.com/the-paperless-project/paperless/pull/351
.. _#352: https://github.com/the-paperless-project/paperless/pull/352
.. _#354: https://github.com/the-paperless-project/paperless/issues/354
.. _#371: https://github.com/the-paperless-project/paperless/issues/371
.. _#374: https://github.com/the-paperless-project/paperless/pull/374
.. _#375: https://github.com/the-paperless-project/paperless/pull/375
.. _#376: https://github.com/the-paperless-project/paperless/pull/376
.. _#383: https://github.com/the-paperless-project/paperless/pull/383
.. _#384: https://github.com/the-paperless-project/paperless/issues/384
.. _#386: https://github.com/the-paperless-project/paperless/issues/386
.. _#387: https://github.com/the-paperless-project/paperless/pull/387
.. _#391: https://github.com/the-paperless-project/paperless/pull/391
.. _#390: https://github.com/the-paperless-project/paperless/pull/390
.. _#392: https://github.com/the-paperless-project/paperless/issues/392
.. _#393: https://github.com/the-paperless-project/paperless/issues/393
.. _#395: https://github.com/the-paperless-project/paperless/pull/395
.. _#394: https://github.com/the-paperless-project/paperless/issues/394
.. _#396: https://github.com/the-paperless-project/paperless/pull/396
.. _#399: https://github.com/the-paperless-project/paperless/pull/399
.. _#400: https://github.com/the-paperless-project/paperless/pull/400
.. _#401: https://github.com/the-paperless-project/paperless/pull/401
.. _#405: https://github.com/the-paperless-project/paperless/pull/405
.. _#406: https://github.com/the-paperless-project/paperless/issues/406
.. _#412: https://github.com/the-paperless-project/paperless/issues/412
.. _#413: https://github.com/the-paperless-project/paperless/pull/413
.. _#414: https://github.com/the-paperless-project/paperless/issues/414
.. _#423: https://github.com/the-paperless-project/paperless/issues/423
.. _#433: https://github.com/the-paperless-project/paperless/issues/433
.. _#440: https://github.com/the-paperless-project/paperless/pull/440
.. _#441: https://github.com/the-paperless-project/paperless/pull/441
.. _#442: https://github.com/the-paperless-project/paperless/pull/442
.. _#466: https://github.com/the-paperless-project/paperless/pull/466
.. _#471: https://github.com/the-paperless-project/paperless/pull/471
.. _#475: https://github.com/the-paperless-project/paperless/pull/475
.. _#481: https://github.com/the-paperless-project/paperless/pull/481
.. _#483: https://github.com/the-paperless-project/paperless/pull/483
.. _#484: https://github.com/the-paperless-project/paperless/pull/484
.. _#488: https://github.com/the-paperless-project/paperless/pull/488
.. _#489: https://github.com/the-paperless-project/paperless/pull/489
.. _#492: https://github.com/the-paperless-project/paperless/pull/492
.. _pipenv: https://docs.pipenv.org/
.. _a new home on Docker Hub: https://hub.docker.com/r/danielquinn/paperless/
.. _optipng: http://optipng.sourceforge.net/
================================================
FILE: docs/conf.py
================================================
# -*- coding: utf-8 -*-
#
# Paperless documentation build configuration file, created by
# sphinx-quickstart on Mon Oct 26 18:36:52 2015.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys
import os
__version__ = None
exec(open("../src/paperless/version.py").read())
# Believe it or not, this is the officially sanctioned way to add custom CSS.
def setup(app):
app.add_stylesheet("custom.css")
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
'sphinx.ext.todo',
'sphinx.ext.imgmath',
'sphinx.ext.viewcode',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Paperless'
copyright = u'2015, Daniel Quinn'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
#
# If the build process ever explodes here, it's because you've set the version
# number in paperless.version to a tuple with 3 numbers in it.
#
# The short X.Y version.
version = ".".join([str(_) for _ in __version__[:2]])
# The full version, including alpha/beta/rc tags.
release = ".".join([str(_) for _ in __version__[:3]])
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# " v documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'paperless'
#
# Attempt to use the ReadTheDocs theme. If it's not installed, fallback to
# the default.
#
try:
import sphinx_rtd_theme
html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
except ImportError:
pass
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
('index', 'paperless.tex', u'Paperless Documentation',
u'Daniel Quinn', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'paperless', u'Paperless Documentation',
[u'Daniel Quinn'], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'Paperless', u'Paperless Documentation',
u'Daniel Quinn', 'paperless', 'Scan, index, and archive all of your paper documents.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False
# -- Options for Epub output ----------------------------------------------
# Bibliographic Dublin Core info.
epub_title = u'Paperless'
epub_author = u'Daniel Quinn'
epub_publisher = u'Daniel Quinn'
epub_copyright = u'2015, Daniel Quinn'
# The basename for the epub file. It defaults to the project name.
#epub_basename = u'Paperless'
# The HTML theme for the epub output. Since the default themes are not optimized
# for small screen space, using the same theme for HTML and epub output is
# usually not wise. This defaults to 'epub', a theme designed to save visual
# space.
#epub_theme = 'epub'
# The language of the text. It defaults to the language option
# or en if the language is not set.
#epub_language = ''
# The scheme of the identifier. Typical schemes are ISBN or URL.
#epub_scheme = ''
# The unique identifier of the text. This can be a ISBN number
# or the project homepage.
#epub_identifier = ''
# A unique identification for the text.
#epub_uid = ''
# A tuple containing the cover image and cover page html template filenames.
#epub_cover = ()
# A sequence of (type, uri, title) tuples for the guide element of content.opf.
#epub_guide = ()
# HTML files that should be inserted before the pages created by sphinx.
# The format is a list of tuples containing the path and title.
#epub_pre_files = []
# HTML files shat should be inserted after the pages created by sphinx.
# The format is a list of tuples containing the path and title.
#epub_post_files = []
# A list of files that should not be packed into the epub file.
epub_exclude_files = ['search.html']
# The depth of the table of contents in toc.ncx.
#epub_tocdepth = 3
# Allow duplicate toc entries.
#epub_tocdup = True
# Choose between 'default' and 'includehidden'.
#epub_tocscope = 'default'
# Fix unsupported image types using the PIL.
#epub_fix_images = False
# Scale large images.
#epub_max_image_width = 0
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#epub_show_urls = 'inline'
# If false, no index is generated.
#epub_use_index = True
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'http://docs.python.org/': None}
================================================
FILE: docs/consumption.rst
================================================
.. _consumption:
Consumption
###########
Once you've got Paperless setup, you need to start feeding documents into it.
Currently, there are three options: the consumption directory, IMAP (email), and
HTTP POST.
.. _consumption-directory:
The Consumption Directory
=========================
The primary method of getting documents into your database is by putting them in
the consumption directory. The ``document_consumer`` script runs in an infinite
loop looking for new additions to this directory and when it finds them, it goes
about the process of parsing them with the OCR, indexing what it finds, and
encrypting the PDF (if ``PAPERLESS_PASSPHRASE`` is set), storing it in the
media directory.
Getting stuff into this directory is up to you. If you're running Paperless
on your local computer, you might just want to drag and drop files there, but if
you're running this on a server and want your scanner to automatically push
files to this directory, you'll need to setup some sort of service to accept the
files from the scanner. Typically, you're looking at an FTP server like
`Proftpd`_ or `Samba`_.
.. _Proftpd: http://www.proftpd.org/
.. _Samba: http://www.samba.org/
So where is this consumption directory? It's wherever you define it. Look for
the ``CONSUMPTION_DIR`` value in ``settings.py``. Set that to somewhere
appropriate for your use and put some documents in there. When you're ready,
follow the :ref:`consumer ` instructions to get it running.
.. _consumption-directory-hook:
Hooking into the Consumption Process
------------------------------------
Sometimes you may want to do something arbitrary whenever a document is
consumed. Rather than try to predict what you may want to do, Paperless lets
you execute scripts of your own choosing just before or after a document is
consumed using a couple simple hooks.
Just write a script, put it somewhere that Paperless can read & execute, and
then put the path to that script in ``paperless.conf`` with the variable name
of either ``PAPERLESS_PRE_CONSUME_SCRIPT`` or
``PAPERLESS_POST_CONSUME_SCRIPT``. The script will be executed before or
or after the document is consumed respectively.
.. important::
These scripts are executed in a **blocking** process, which means that if
a script takes a long time to run, it can significantly slow down your
document consumption flow. If you want things to run asynchronously,
you'll have to fork the process in your script and exit.
.. _consumption-directory-hook-variables:
What Can These Scripts Do?
..........................
It's your script, so you're only limited by your imagination and the laws of
physics. However, the following values are passed to the scripts in order:
.. _consumption-director-hook-variables-pre:
Pre-consumption script
::::::::::::::::::::::
* Document file name
A simple but common example for this would be creating a simple script like
this:
``/usr/local/bin/ocr-pdf``
.. code:: bash
#!/usr/bin/env bash
pdf2pdfocr.py -i ${1}
``/etc/paperless.conf``
.. code:: bash
...
PAPERLESS_PRE_CONSUME_SCRIPT="/usr/local/bin/ocr-pdf"
...
This will pass the path to the document about to be consumed to ``/usr/local/bin/ocr-pdf``,
which will in turn call `pdf2pdfocr.py`_ on your document, which will then
overwrite the file with an OCR'd version of the file and exit. At which point,
the consumption process will begin with the newly modified file.
.. _pdf2pdfocr.py: https://github.com/LeoFCardoso/pdf2pdfocr
.. _consumption-director-hook-variables-post:
Post-consumption script
:::::::::::::::::::::::
* Document id
* Generated file name
* Source path
* Thumbnail path
* Download URL
* Thumbnail URL
* Correspondent
* Tags
The script can be in any language you like, but for a simple shell script
example, you can take a look at ``post-consumption-example.sh`` in the
``scripts`` directory in this project.
.. _consumption-imap:
IMAP (Email)
============
Another handy way to get documents into your database is to email them to
yourself. The typical use-case would be to be out for lunch and want to send a
copy of the receipt back to your system at home. Paperless can be taught to
pull emails down from an arbitrary account and dump them into the consumption
directory where the process :ref:`above ` will follow the
usual pattern on consuming the document.
Some things you need to know about this feature:
* It's disabled by default. By setting the values below it will be enabled.
* It's been tested in a limited environment, so it may not work for you (please
submit a pull request if you can!)
* It's designed to **delete mail from the server once consumed**. So don't go
pointing this to your personal email account and wonder where all your stuff
went.
* Currently, only one photo (attachment) per email will work.
So, with all that in mind, here's what you do to get it running:
1. Setup a new email account somewhere, or if you're feeling daring, create a
folder in an existing email box and note the path to that folder.
2. In ``/etc/paperless.conf`` set all of the appropriate values in
``PATHS AND FOLDERS`` and ``SECURITY``.
If you decided to use a subfolder of an existing account, then make sure you
set ``PAPERLESS_CONSUME_MAIL_INBOX`` accordingly here. You also have to set
the ``PAPERLESS_EMAIL_SECRET`` to something you can remember 'cause you'll
have to include that in every email you send.
3. Restart the :ref:`consumer `. The consumer will check
the configured email account at startup and from then on every 10 minutes
for something new and pulls down whatever it finds.
4. Send yourself an email! Note that the subject is treated as the file name,
so if you set the subject to ``Correspondent - Title - tag,tag,tag``, you'll
get what you expect. Also, you must include the aforementioned secret
string in every email so the fetcher knows that it's safe to import.
Note that Paperless only allows the email title to consist of safe characters
to be imported. These consist of alpha-numeric characters and ``-_ ,.'``.
5. After a few minutes, the consumer will poll your mailbox, pull down the
message, and place the attachment in the consumption directory with the
appropriate name. A few minutes later, the consumer will import it like any
other file.
.. _consumption-http:
HTTP POST
=========
You can also submit a document via HTTP POST, so long as you do so after
authenticating. To push your document to Paperless, send an HTTP POST to the
server with the following name/value pairs:
* ``correspondent``: The name of the document's correspondent. Note that there
are restrictions on what characters you can use here. Specifically,
alphanumeric characters, `-`, `,`, `.`, and `'` are ok, everything else is
out. You also can't use the sequence ` - ` (space, dash, space).
* ``title``: The title of the document. The rules for characters is the same
here as the correspondent.
* ``document``: The file you're uploading
Specify ``enctype="multipart/form-data"``, and then POST your file with::
Content-Disposition: form-data; name="document"; filename="whatever.pdf"
An example of this in HTML is a typical form:
.. code:: html
But a potentially more useful way to do this would be in Python. Here we use
the requests library to handle basic authentication and to send the POST data
to the URL.
.. code:: python
import os
from hashlib import sha256
import requests
from requests.auth import HTTPBasicAuth
# You authenticate via BasicAuth or with a session id.
# We use BasicAuth here
username = "my-username"
password = "my-super-secret-password"
# Where you have Paperless installed and listening
url = "http://localhost:8000/push"
# Document metadata
correspondent = "Test Correspondent"
title = "Test Title"
# The local file you want to push
path = "/path/to/some/directory/my-document.pdf"
with open(path, "rb") as f:
response = requests.post(
url=url,
data={"title": title, "correspondent": correspondent},
files={"document": (os.path.basename(path), f, "application/pdf")},
auth=HTTPBasicAuth(username, password),
allow_redirects=False
)
if response.status_code == 202:
# Everything worked out ok
print("Upload successful")
else:
# If you don't get a 202, it's probably because your credentials
# are wrong or something. This will give you a rough idea of what
# happened.
print("We got HTTP status code: {}".format(response.status_code))
for k, v in response.headers.items():
print("{}: {}".format(k, v))
================================================
FILE: docs/contributing.rst
================================================
.. _contributing:
Contributing to Paperless
#########################
Maybe you've been using Paperless for a while and want to add a feature or two,
or maybe you've come across a bug that you have some ideas how to solve. The
beauty of Free software is that you can see what's wrong and help to get it
fixed for everyone!
How to Get Your Changes Rolled Into Paperless
=============================================
If you've found a bug, but don't know how to fix it, you can always post an
issue on `GitHub`_ in the hopes that someone will have the time to fix it for
you. If however you're the one with the time, pull requests are always
welcome, you just have to make sure that your code conforms to a few standards:
Pep8
----
It's the standard for all Python development, so it's `very well documented`_.
The short version is:
* Lines should wrap at 79 characters
* Use ``snake_case`` for variables, ``CamelCase`` for classes, and ``ALL_CAPS``
for constants.
* Space out your operators: ``stuff + 7`` instead of ``stuff+7``
* Two empty lines between classes, and functions, but 1 empty line between
class methods.
There's more to it than that, but if you follow those, you'll probably be
alright. When you submit your pull request, there's a pep8 checker that'll
look at your code to see if anything is off. If it finds anything, it'll
complain at you until you fix it.
Additional Style Guides
-----------------------
Where pep8 is ambiguous, I've tried to be a little more specific. These rules
aren't hard-and-fast, but if you can conform to them, I'll appreciate it and
spend less time trying to conform your PR before merging:
Function calls
..............
If you're calling a function and that necessitates more than one line of code,
please format it like this:
.. code:: python
my_function(
argument1,
kwarg1="x",
kwarg2="y"
another_really_long_kwarg="some big value"
a_kwarg_calling_another_long_function=another_function(
another_arg,
another_kwarg="kwarg!"
)
)
This is all in the interest of code uniformity rather than anything else. If
we stick to a style, everything is understandable in the same way.
Quoting Strings
...............
pep8 is a little too open-minded on this for my liking. Python strings should
be quoted with double quotes (``"``) except in cases where the resulting string
would require too much escaping of a double quote, in which case, a single
quoted, or triple-quoted string will do:
.. code:: python
my_string = "This is my string"
problematic_string = 'This is a "string" with "quotes" in it'
In HTML templates, please use double-quotes for tag attributes, and single
quotes for arguments passed to Django tempalte tags:
.. code:: html
This is to keep linters happy they look at an HTML file and see an attribute
closing the ``"`` before it should have been.
--
That's all there is in terms of guidelines, so I hope it's not too daunting.
Indentation & Spacing
.....................
When it comes to indentation:
* For Python, the rule is: follow pep8 and use 4 spaces.
* For Javascript, CSS, and HTML, please use 1 tab.
Additionally, Django templates making use of block elements like ``{% if %}``,
``{% for %}``, and ``{% block %}`` etc. should be indented:
Good:
.. code:: html
{% block stuff %}
================================================
FILE: presentation/js/reveal.js
================================================
/*!
* reveal.js
* http://lab.hakim.se/reveal-js
* MIT licensed
*
* Copyright (C) 2015 Hakim El Hattab, http://hakim.se
*/
(function( root, factory ) {
if( typeof define === 'function' && define.amd ) {
// AMD. Register as an anonymous module.
define( function() {
root.Reveal = factory();
return root.Reveal;
} );
} else if( typeof exports === 'object' ) {
// Node. Does not work with strict CommonJS.
module.exports = factory();
} else {
// Browser globals.
root.Reveal = factory();
}
}( this, function() {
'use strict';
var Reveal;
var SLIDES_SELECTOR = '.slides section',
HORIZONTAL_SLIDES_SELECTOR = '.slides>section',
VERTICAL_SLIDES_SELECTOR = '.slides>section.present>section',
HOME_SLIDE_SELECTOR = '.slides>section:first-of-type',
// Configurations defaults, can be overridden at initialization time
config = {
// The "normal" size of the presentation, aspect ratio will be preserved
// when the presentation is scaled to fit different resolutions
width: 960,
height: 700,
// Factor of the display size that should remain empty around the content
margin: 0.1,
// Bounds for smallest/largest possible scale to apply to content
minScale: 0.2,
maxScale: 1.5,
// Display controls in the bottom right corner
controls: true,
// Display a presentation progress bar
progress: true,
// Display the page number of the current slide
slideNumber: false,
// Push each slide change to the browser history
history: false,
// Enable keyboard shortcuts for navigation
keyboard: true,
// Optional function that blocks keyboard events when retuning false
keyboardCondition: null,
// Enable the slide overview mode
overview: true,
// Vertical centering of slides
center: true,
// Enables touch navigation on devices with touch input
touch: true,
// Loop the presentation
loop: false,
// Change the presentation direction to be RTL
rtl: false,
// Turns fragments on and off globally
fragments: true,
// Flags if the presentation is running in an embedded mode,
// i.e. contained within a limited portion of the screen
embedded: false,
// Flags if we should show a help overlay when the questionmark
// key is pressed
help: true,
// Flags if it should be possible to pause the presentation (blackout)
pause: true,
// Number of milliseconds between automatically proceeding to the
// next slide, disabled when set to 0, this value can be overwritten
// by using a data-autoslide attribute on your slides
autoSlide: 0,
// Stop auto-sliding after user input
autoSlideStoppable: true,
// Enable slide navigation via mouse wheel
mouseWheel: false,
// Apply a 3D roll to links on hover
rollingLinks: false,
// Hides the address bar on mobile devices
hideAddressBar: true,
// Opens links in an iframe preview overlay
previewLinks: false,
// Exposes the reveal.js API through window.postMessage
postMessage: true,
// Dispatches all reveal.js events to the parent window through postMessage
postMessageEvents: false,
// Focuses body when page changes visiblity to ensure keyboard shortcuts work
focusBodyOnPageVisibilityChange: true,
// Transition style
transition: 'slide', // none/fade/slide/convex/concave/zoom
// Transition speed
transitionSpeed: 'default', // default/fast/slow
// Transition style for full page slide backgrounds
backgroundTransition: 'fade', // none/fade/slide/convex/concave/zoom
// Parallax background image
parallaxBackgroundImage: '', // CSS syntax, e.g. "a.jpg"
// Parallax background size
parallaxBackgroundSize: '', // CSS syntax, e.g. "3000px 2000px"
// Number of slides away from the current that are visible
viewDistance: 3,
// Script dependencies to load
dependencies: []
},
// Flags if reveal.js is loaded (has dispatched the 'ready' event)
loaded = false,
// The horizontal and vertical index of the currently active slide
indexh,
indexv,
// The previous and current slide HTML elements
previousSlide,
currentSlide,
previousBackground,
// Slides may hold a data-state attribute which we pick up and apply
// as a class to the body. This list contains the combined state of
// all current slides.
state = [],
// The current scale of the presentation (see width/height config)
scale = 1,
// Cached references to DOM elements
dom = {},
// Features supported by the browser, see #checkCapabilities()
features = {},
// Client is a mobile device, see #checkCapabilities()
isMobileDevice,
// Throttles mouse wheel navigation
lastMouseWheelStep = 0,
// Delays updates to the URL due to a Chrome thumbnailer bug
writeURLTimeout = 0,
// Flags if the interaction event listeners are bound
eventsAreBound = false,
// The current auto-slide duration
autoSlide = 0,
// Auto slide properties
autoSlidePlayer,
autoSlideTimeout = 0,
autoSlideStartTime = -1,
autoSlidePaused = false,
// Holds information about the currently ongoing touch input
touch = {
startX: 0,
startY: 0,
startSpan: 0,
startCount: 0,
captured: false,
threshold: 40
},
// Holds information about the keyboard shortcuts
keyboardShortcuts = {
'N , SPACE': 'Next slide',
'P': 'Previous slide',
'← , H': 'Navigate left',
'→ , L': 'Navigate right',
'↑ , K': 'Navigate up',
'↓ , J': 'Navigate down',
'Home': 'First slide',
'End': 'Last slide',
'B , .': 'Pause',
'F': 'Fullscreen',
'ESC, O': 'Slide overview'
};
/**
* Starts up the presentation if the client is capable.
*/
function initialize( options ) {
checkCapabilities();
if( !features.transforms2d && !features.transforms3d ) {
document.body.setAttribute( 'class', 'no-transforms' );
// Since JS won't be running any further, we need to load all
// images that were intended to lazy load now
var images = document.getElementsByTagName( 'img' );
for( var i = 0, len = images.length; i < len; i++ ) {
var image = images[i];
if( image.getAttribute( 'data-src' ) ) {
image.setAttribute( 'src', image.getAttribute( 'data-src' ) );
image.removeAttribute( 'data-src' );
}
}
// If the browser doesn't support core features we won't be
// using JavaScript to control the presentation
return;
}
// Cache references to key DOM elements
dom.wrapper = document.querySelector( '.reveal' );
dom.slides = document.querySelector( '.reveal .slides' );
// Force a layout when the whole page, incl fonts, has loaded
window.addEventListener( 'load', layout, false );
var query = Reveal.getQueryHash();
// Do not accept new dependencies via query config to avoid
// the potential of malicious script injection
if( typeof query['dependencies'] !== 'undefined' ) delete query['dependencies'];
// Copy options over to our config object
extend( config, options );
extend( config, query );
// Hide the address bar in mobile browsers
hideAddressBar();
// Loads the dependencies and continues to #start() once done
load();
}
/**
* Inspect the client to see what it's capable of, this
* should only happens once per runtime.
*/
function checkCapabilities() {
features.transforms3d = 'WebkitPerspective' in document.body.style ||
'MozPerspective' in document.body.style ||
'msPerspective' in document.body.style ||
'OPerspective' in document.body.style ||
'perspective' in document.body.style;
features.transforms2d = 'WebkitTransform' in document.body.style ||
'MozTransform' in document.body.style ||
'msTransform' in document.body.style ||
'OTransform' in document.body.style ||
'transform' in document.body.style;
features.requestAnimationFrameMethod = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
features.requestAnimationFrame = typeof features.requestAnimationFrameMethod === 'function';
features.canvas = !!document.createElement( 'canvas' ).getContext;
features.touch = !!( 'ontouchstart' in window );
isMobileDevice = navigator.userAgent.match( /(iphone|ipod|ipad|android)/gi );
}
/**
* Loads the dependencies of reveal.js. Dependencies are
* defined via the configuration option 'dependencies'
* and will be loaded prior to starting/binding reveal.js.
* Some dependencies may have an 'async' flag, if so they
* will load after reveal.js has been started up.
*/
function load() {
var scripts = [],
scriptsAsync = [],
scriptsToPreload = 0;
// Called once synchronous scripts finish loading
function proceed() {
if( scriptsAsync.length ) {
// Load asynchronous scripts
head.js.apply( null, scriptsAsync );
}
start();
}
function loadScript( s ) {
head.ready( s.src.match( /([\w\d_\-]*)\.?js$|[^\\\/]*$/i )[0], function() {
// Extension may contain callback functions
if( typeof s.callback === 'function' ) {
s.callback.apply( this );
}
if( --scriptsToPreload === 0 ) {
proceed();
}
});
}
for( var i = 0, len = config.dependencies.length; i < len; i++ ) {
var s = config.dependencies[i];
// Load if there's no condition or the condition is truthy
if( !s.condition || s.condition() ) {
if( s.async ) {
scriptsAsync.push( s.src );
}
else {
scripts.push( s.src );
}
loadScript( s );
}
}
if( scripts.length ) {
scriptsToPreload = scripts.length;
// Load synchronous scripts
head.js.apply( null, scripts );
}
else {
proceed();
}
}
/**
* Starts up reveal.js by binding input events and navigating
* to the current URL deeplink if there is one.
*/
function start() {
// Make sure we've got all the DOM elements we need
setupDOM();
// Listen to messages posted to this window
setupPostMessage();
// Resets all vertical slides so that only the first is visible
resetVerticalSlides();
// Updates the presentation to match the current configuration values
configure();
// Read the initial hash
readURL();
// Update all backgrounds
updateBackground( true );
// Notify listeners that the presentation is ready but use a 1ms
// timeout to ensure it's not fired synchronously after #initialize()
setTimeout( function() {
// Enable transitions now that we're loaded
dom.slides.classList.remove( 'no-transition' );
loaded = true;
dispatchEvent( 'ready', {
'indexh': indexh,
'indexv': indexv,
'currentSlide': currentSlide
} );
}, 1 );
// Special setup and config is required when printing to PDF
if( isPrintingPDF() ) {
removeEventListeners();
// The document needs to have loaded for the PDF layout
// measurements to be accurate
if( document.readyState === 'complete' ) {
setupPDF();
}
else {
window.addEventListener( 'load', setupPDF );
}
}
}
/**
* Finds and stores references to DOM elements which are
* required by the presentation. If a required element is
* not found, it is created.
*/
function setupDOM() {
// Prevent transitions while we're loading
dom.slides.classList.add( 'no-transition' );
// Background element
dom.background = createSingletonNode( dom.wrapper, 'div', 'backgrounds', null );
// Progress bar
dom.progress = createSingletonNode( dom.wrapper, 'div', 'progress', '' );
dom.progressbar = dom.progress.querySelector( 'span' );
// Arrow controls
createSingletonNode( dom.wrapper, 'aside', 'controls',
'' +
'' +
'' +
'' );
// Slide number
dom.slideNumber = createSingletonNode( dom.wrapper, 'div', 'slide-number', '' );
// Overlay graphic which is displayed during the paused mode
createSingletonNode( dom.wrapper, 'div', 'pause-overlay', null );
// Cache references to elements
dom.controls = document.querySelector( '.reveal .controls' );
dom.theme = document.querySelector( '#theme' );
dom.wrapper.setAttribute( 'role', 'application' );
// There can be multiple instances of controls throughout the page
dom.controlsLeft = toArray( document.querySelectorAll( '.navigate-left' ) );
dom.controlsRight = toArray( document.querySelectorAll( '.navigate-right' ) );
dom.controlsUp = toArray( document.querySelectorAll( '.navigate-up' ) );
dom.controlsDown = toArray( document.querySelectorAll( '.navigate-down' ) );
dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) );
dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) );
dom.statusDiv = createStatusDiv();
}
/**
* Creates a hidden div with role aria-live to announce the
* current slide content. Hide the div off-screen to make it
* available only to Assistive Technologies.
*/
function createStatusDiv() {
var statusDiv = document.getElementById( 'aria-status-div' );
if( !statusDiv ) {
statusDiv = document.createElement( 'div' );
statusDiv.style.position = 'absolute';
statusDiv.style.height = '1px';
statusDiv.style.width = '1px';
statusDiv.style.overflow ='hidden';
statusDiv.style.clip = 'rect( 1px, 1px, 1px, 1px )';
statusDiv.setAttribute( 'id', 'aria-status-div' );
statusDiv.setAttribute( 'aria-live', 'polite' );
statusDiv.setAttribute( 'aria-atomic','true' );
dom.wrapper.appendChild( statusDiv );
}
return statusDiv;
}
/**
* Configures the presentation for printing to a static
* PDF.
*/
function setupPDF() {
var slideSize = getComputedSlideSize( window.innerWidth, window.innerHeight );
// Dimensions of the PDF pages
var pageWidth = Math.floor( slideSize.width * ( 1 + config.margin ) ),
pageHeight = Math.floor( slideSize.height * ( 1 + config.margin ) );
// Dimensions of slides within the pages
var slideWidth = slideSize.width,
slideHeight = slideSize.height;
// Let the browser know what page size we want to print
injectStyleSheet( '@page{size:'+ pageWidth +'px '+ pageHeight +'px; margin: 0;}' );
// Limit the size of certain elements to the dimensions of the slide
injectStyleSheet( '.reveal section>img, .reveal section>video, .reveal section>iframe{max-width: '+ slideWidth +'px; max-height:'+ slideHeight +'px}' );
document.body.classList.add( 'print-pdf' );
document.body.style.width = pageWidth + 'px';
document.body.style.height = pageHeight + 'px';
// Slide and slide background layout
toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
// Vertical stacks are not centred since their section
// children will be
if( slide.classList.contains( 'stack' ) === false ) {
// Center the slide inside of the page, giving the slide some margin
var left = ( pageWidth - slideWidth ) / 2,
top = ( pageHeight - slideHeight ) / 2;
var contentHeight = getAbsoluteHeight( slide );
var numberOfPages = Math.max( Math.ceil( contentHeight / pageHeight ), 1 );
// Center slides vertically
if( numberOfPages === 1 && config.center || slide.classList.contains( 'center' ) ) {
top = Math.max( ( pageHeight - contentHeight ) / 2, 0 );
}
// Position the slide inside of the page
slide.style.left = left + 'px';
slide.style.top = top + 'px';
slide.style.width = slideWidth + 'px';
// TODO Backgrounds need to be multiplied when the slide
// stretches over multiple pages
var background = slide.querySelector( '.slide-background' );
if( background ) {
background.style.width = pageWidth + 'px';
background.style.height = ( pageHeight * numberOfPages ) + 'px';
background.style.top = -top + 'px';
background.style.left = -left + 'px';
}
}
} );
// Show all fragments
toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' .fragment' ) ).forEach( function( fragment ) {
fragment.classList.add( 'visible' );
} );
}
/**
* Creates an HTML element and returns a reference to it.
* If the element already exists the existing instance will
* be returned.
*/
function createSingletonNode( container, tagname, classname, innerHTML ) {
// Find all nodes matching the description
var nodes = container.querySelectorAll( '.' + classname );
// Check all matches to find one which is a direct child of
// the specified container
for( var i = 0; i < nodes.length; i++ ) {
var testNode = nodes[i];
if( testNode.parentNode === container ) {
return testNode;
}
}
// If no node was found, create it now
var node = document.createElement( tagname );
node.classList.add( classname );
if( typeof innerHTML === 'string' ) {
node.innerHTML = innerHTML;
}
container.appendChild( node );
return node;
}
/**
* Creates the slide background elements and appends them
* to the background container. One element is created per
* slide no matter if the given slide has visible background.
*/
function createBackgrounds() {
var printMode = isPrintingPDF();
// Clear prior backgrounds
dom.background.innerHTML = '';
dom.background.classList.add( 'no-transition' );
// Iterate over all horizontal slides
toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) {
var backgroundStack;
if( printMode ) {
backgroundStack = createBackground( slideh, slideh );
}
else {
backgroundStack = createBackground( slideh, dom.background );
}
// Iterate over all vertical slides
toArray( slideh.querySelectorAll( 'section' ) ).forEach( function( slidev ) {
if( printMode ) {
createBackground( slidev, slidev );
}
else {
createBackground( slidev, backgroundStack );
}
backgroundStack.classList.add( 'stack' );
} );
} );
// Add parallax background if specified
if( config.parallaxBackgroundImage ) {
dom.background.style.backgroundImage = 'url("' + config.parallaxBackgroundImage + '")';
dom.background.style.backgroundSize = config.parallaxBackgroundSize;
// Make sure the below properties are set on the element - these properties are
// needed for proper transitions to be set on the element via CSS. To remove
// annoying background slide-in effect when the presentation starts, apply
// these properties after short time delay
setTimeout( function() {
dom.wrapper.classList.add( 'has-parallax-background' );
}, 1 );
}
else {
dom.background.style.backgroundImage = '';
dom.wrapper.classList.remove( 'has-parallax-background' );
}
}
/**
* Creates a background for the given slide.
*
* @param {HTMLElement} slide
* @param {HTMLElement} container The element that the background
* should be appended to
*/
function createBackground( slide, container ) {
var data = {
background: slide.getAttribute( 'data-background' ),
backgroundSize: slide.getAttribute( 'data-background-size' ),
backgroundImage: slide.getAttribute( 'data-background-image' ),
backgroundVideo: slide.getAttribute( 'data-background-video' ),
backgroundIframe: slide.getAttribute( 'data-background-iframe' ),
backgroundColor: slide.getAttribute( 'data-background-color' ),
backgroundRepeat: slide.getAttribute( 'data-background-repeat' ),
backgroundPosition: slide.getAttribute( 'data-background-position' ),
backgroundTransition: slide.getAttribute( 'data-background-transition' )
};
var element = document.createElement( 'div' );
// Carry over custom classes from the slide to the background
element.className = 'slide-background ' + slide.className.replace( /present|past|future/, '' );
if( data.background ) {
// Auto-wrap image urls in url(...)
if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)$/gi.test( data.background ) ) {
slide.setAttribute( 'data-background-image', data.background );
}
else {
element.style.background = data.background;
}
}
// Create a hash for this combination of background settings.
// This is used to determine when two slide backgrounds are
// the same.
if( data.background || data.backgroundColor || data.backgroundImage || data.backgroundVideo || data.backgroundIframe ) {
element.setAttribute( 'data-background-hash', data.background +
data.backgroundSize +
data.backgroundImage +
data.backgroundVideo +
data.backgroundIframe +
data.backgroundColor +
data.backgroundRepeat +
data.backgroundPosition +
data.backgroundTransition );
}
// Additional and optional background properties
if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize;
if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;
if( data.backgroundRepeat ) element.style.backgroundRepeat = data.backgroundRepeat;
if( data.backgroundPosition ) element.style.backgroundPosition = data.backgroundPosition;
if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition );
container.appendChild( element );
// If backgrounds are being recreated, clear old classes
slide.classList.remove( 'has-dark-background' );
slide.classList.remove( 'has-light-background' );
// If this slide has a background color, add a class that
// signals if it is light or dark. If the slide has no background
// color, no class will be set
var computedBackgroundColor = window.getComputedStyle( element ).backgroundColor;
if( computedBackgroundColor ) {
var rgb = colorToRgb( computedBackgroundColor );
// Ignore fully transparent backgrounds. Some browsers return
// rgba(0,0,0,0) when reading the computed background color of
// an element with no background
if( rgb && rgb.a !== 0 ) {
if( colorBrightness( computedBackgroundColor ) < 128 ) {
slide.classList.add( 'has-dark-background' );
}
else {
slide.classList.add( 'has-light-background' );
}
}
}
return element;
}
/**
* Registers a listener to postMessage events, this makes it
* possible to call all reveal.js API methods from another
* window. For example:
*
* revealWindow.postMessage( JSON.stringify({
* method: 'slide',
* args: [ 2 ]
* }), '*' );
*/
function setupPostMessage() {
if( config.postMessage ) {
window.addEventListener( 'message', function ( event ) {
var data = event.data;
// Make sure we're dealing with JSON
if( data.charAt( 0 ) === '{' && data.charAt( data.length - 1 ) === '}' ) {
data = JSON.parse( data );
// Check if the requested method can be found
if( data.method && typeof Reveal[data.method] === 'function' ) {
Reveal[data.method].apply( Reveal, data.args );
}
}
}, false );
}
}
/**
* Applies the configuration settings from the config
* object. May be called multiple times.
*/
function configure( options ) {
var numberOfSlides = dom.wrapper.querySelectorAll( SLIDES_SELECTOR ).length;
dom.wrapper.classList.remove( config.transition );
// New config options may be passed when this method
// is invoked through the API after initialization
if( typeof options === 'object' ) extend( config, options );
// Force linear transition based on browser capabilities
if( features.transforms3d === false ) config.transition = 'linear';
dom.wrapper.classList.add( config.transition );
dom.wrapper.setAttribute( 'data-transition-speed', config.transitionSpeed );
dom.wrapper.setAttribute( 'data-background-transition', config.backgroundTransition );
dom.controls.style.display = config.controls ? 'block' : 'none';
dom.progress.style.display = config.progress ? 'block' : 'none';
if( config.rtl ) {
dom.wrapper.classList.add( 'rtl' );
}
else {
dom.wrapper.classList.remove( 'rtl' );
}
if( config.center ) {
dom.wrapper.classList.add( 'center' );
}
else {
dom.wrapper.classList.remove( 'center' );
}
// Exit the paused mode if it was configured off
if( config.pause === false ) {
resume();
}
if( config.mouseWheel ) {
document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
document.addEventListener( 'mousewheel', onDocumentMouseScroll, false );
}
else {
document.removeEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
document.removeEventListener( 'mousewheel', onDocumentMouseScroll, false );
}
// Rolling 3D links
if( config.rollingLinks ) {
enableRollingLinks();
}
else {
disableRollingLinks();
}
// Iframe link previews
if( config.previewLinks ) {
enablePreviewLinks();
}
else {
disablePreviewLinks();
enablePreviewLinks( '[data-preview-link]' );
}
// Remove existing auto-slide controls
if( autoSlidePlayer ) {
autoSlidePlayer.destroy();
autoSlidePlayer = null;
}
// Generate auto-slide controls if needed
if( numberOfSlides > 1 && config.autoSlide && config.autoSlideStoppable && features.canvas && features.requestAnimationFrame ) {
autoSlidePlayer = new Playback( dom.wrapper, function() {
return Math.min( Math.max( ( Date.now() - autoSlideStartTime ) / autoSlide, 0 ), 1 );
} );
autoSlidePlayer.on( 'click', onAutoSlidePlayerClick );
autoSlidePaused = false;
}
// When fragments are turned off they should be visible
if( config.fragments === false ) {
toArray( dom.slides.querySelectorAll( '.fragment' ) ).forEach( function( element ) {
element.classList.add( 'visible' );
element.classList.remove( 'current-fragment' );
} );
}
sync();
}
/**
* Binds all event listeners.
*/
function addEventListeners() {
eventsAreBound = true;
window.addEventListener( 'hashchange', onWindowHashChange, false );
window.addEventListener( 'resize', onWindowResize, false );
if( config.touch ) {
dom.wrapper.addEventListener( 'touchstart', onTouchStart, false );
dom.wrapper.addEventListener( 'touchmove', onTouchMove, false );
dom.wrapper.addEventListener( 'touchend', onTouchEnd, false );
// Support pointer-style touch interaction as well
if( window.navigator.pointerEnabled ) {
// IE 11 uses un-prefixed version of pointer events
dom.wrapper.addEventListener( 'pointerdown', onPointerDown, false );
dom.wrapper.addEventListener( 'pointermove', onPointerMove, false );
dom.wrapper.addEventListener( 'pointerup', onPointerUp, false );
}
else if( window.navigator.msPointerEnabled ) {
// IE 10 uses prefixed version of pointer events
dom.wrapper.addEventListener( 'MSPointerDown', onPointerDown, false );
dom.wrapper.addEventListener( 'MSPointerMove', onPointerMove, false );
dom.wrapper.addEventListener( 'MSPointerUp', onPointerUp, false );
}
}
if( config.keyboard ) {
document.addEventListener( 'keydown', onDocumentKeyDown, false );
document.addEventListener( 'keypress', onDocumentKeyPress, false );
}
if( config.progress && dom.progress ) {
dom.progress.addEventListener( 'click', onProgressClicked, false );
}
if( config.focusBodyOnPageVisibilityChange ) {
var visibilityChange;
if( 'hidden' in document ) {
visibilityChange = 'visibilitychange';
}
else if( 'msHidden' in document ) {
visibilityChange = 'msvisibilitychange';
}
else if( 'webkitHidden' in document ) {
visibilityChange = 'webkitvisibilitychange';
}
if( visibilityChange ) {
document.addEventListener( visibilityChange, onPageVisibilityChange, false );
}
}
// Listen to both touch and click events, in case the device
// supports both
var pointerEvents = [ 'touchstart', 'click' ];
// Only support touch for Android, fixes double navigations in
// stock browser
if( navigator.userAgent.match( /android/gi ) ) {
pointerEvents = [ 'touchstart' ];
}
pointerEvents.forEach( function( eventName ) {
dom.controlsLeft.forEach( function( el ) { el.addEventListener( eventName, onNavigateLeftClicked, false ); } );
dom.controlsRight.forEach( function( el ) { el.addEventListener( eventName, onNavigateRightClicked, false ); } );
dom.controlsUp.forEach( function( el ) { el.addEventListener( eventName, onNavigateUpClicked, false ); } );
dom.controlsDown.forEach( function( el ) { el.addEventListener( eventName, onNavigateDownClicked, false ); } );
dom.controlsPrev.forEach( function( el ) { el.addEventListener( eventName, onNavigatePrevClicked, false ); } );
dom.controlsNext.forEach( function( el ) { el.addEventListener( eventName, onNavigateNextClicked, false ); } );
} );
}
/**
* Unbinds all event listeners.
*/
function removeEventListeners() {
eventsAreBound = false;
document.removeEventListener( 'keydown', onDocumentKeyDown, false );
document.removeEventListener( 'keypress', onDocumentKeyPress, false );
window.removeEventListener( 'hashchange', onWindowHashChange, false );
window.removeEventListener( 'resize', onWindowResize, false );
dom.wrapper.removeEventListener( 'touchstart', onTouchStart, false );
dom.wrapper.removeEventListener( 'touchmove', onTouchMove, false );
dom.wrapper.removeEventListener( 'touchend', onTouchEnd, false );
// IE11
if( window.navigator.pointerEnabled ) {
dom.wrapper.removeEventListener( 'pointerdown', onPointerDown, false );
dom.wrapper.removeEventListener( 'pointermove', onPointerMove, false );
dom.wrapper.removeEventListener( 'pointerup', onPointerUp, false );
}
// IE10
else if( window.navigator.msPointerEnabled ) {
dom.wrapper.removeEventListener( 'MSPointerDown', onPointerDown, false );
dom.wrapper.removeEventListener( 'MSPointerMove', onPointerMove, false );
dom.wrapper.removeEventListener( 'MSPointerUp', onPointerUp, false );
}
if ( config.progress && dom.progress ) {
dom.progress.removeEventListener( 'click', onProgressClicked, false );
}
[ 'touchstart', 'click' ].forEach( function( eventName ) {
dom.controlsLeft.forEach( function( el ) { el.removeEventListener( eventName, onNavigateLeftClicked, false ); } );
dom.controlsRight.forEach( function( el ) { el.removeEventListener( eventName, onNavigateRightClicked, false ); } );
dom.controlsUp.forEach( function( el ) { el.removeEventListener( eventName, onNavigateUpClicked, false ); } );
dom.controlsDown.forEach( function( el ) { el.removeEventListener( eventName, onNavigateDownClicked, false ); } );
dom.controlsPrev.forEach( function( el ) { el.removeEventListener( eventName, onNavigatePrevClicked, false ); } );
dom.controlsNext.forEach( function( el ) { el.removeEventListener( eventName, onNavigateNextClicked, false ); } );
} );
}
/**
* Extend object a with the properties of object b.
* If there's a conflict, object b takes precedence.
*/
function extend( a, b ) {
for( var i in b ) {
a[ i ] = b[ i ];
}
}
/**
* Converts the target object to an array.
*/
function toArray( o ) {
return Array.prototype.slice.call( o );
}
/**
* Utility for deserializing a value.
*/
function deserialize( value ) {
if( typeof value === 'string' ) {
if( value === 'null' ) return null;
else if( value === 'true' ) return true;
else if( value === 'false' ) return false;
else if( value.match( /^\d+$/ ) ) return parseFloat( value );
}
return value;
}
/**
* Measures the distance in pixels between point a
* and point b.
*
* @param {Object} a point with x/y properties
* @param {Object} b point with x/y properties
*/
function distanceBetween( a, b ) {
var dx = a.x - b.x,
dy = a.y - b.y;
return Math.sqrt( dx*dx + dy*dy );
}
/**
* Applies a CSS transform to the target element.
*/
function transformElement( element, transform ) {
element.style.WebkitTransform = transform;
element.style.MozTransform = transform;
element.style.msTransform = transform;
element.style.OTransform = transform;
element.style.transform = transform;
}
/**
* Injects the given CSS styles into the DOM.
*/
function injectStyleSheet( value ) {
var tag = document.createElement( 'style' );
tag.type = 'text/css';
if( tag.styleSheet ) {
tag.styleSheet.cssText = value;
}
else {
tag.appendChild( document.createTextNode( value ) );
}
document.getElementsByTagName( 'head' )[0].appendChild( tag );
}
/**
* Measures the distance in pixels between point a and point b.
*
* @param {String} color The string representation of a color,
* the following formats are supported:
* - #000
* - #000000
* - rgb(0,0,0)
*/
function colorToRgb( color ) {
var hex3 = color.match( /^#([0-9a-f]{3})$/i );
if( hex3 && hex3[1] ) {
hex3 = hex3[1];
return {
r: parseInt( hex3.charAt( 0 ), 16 ) * 0x11,
g: parseInt( hex3.charAt( 1 ), 16 ) * 0x11,
b: parseInt( hex3.charAt( 2 ), 16 ) * 0x11
};
}
var hex6 = color.match( /^#([0-9a-f]{6})$/i );
if( hex6 && hex6[1] ) {
hex6 = hex6[1];
return {
r: parseInt( hex6.substr( 0, 2 ), 16 ),
g: parseInt( hex6.substr( 2, 2 ), 16 ),
b: parseInt( hex6.substr( 4, 2 ), 16 )
};
}
var rgb = color.match( /^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i );
if( rgb ) {
return {
r: parseInt( rgb[1], 10 ),
g: parseInt( rgb[2], 10 ),
b: parseInt( rgb[3], 10 )
};
}
var rgba = color.match( /^rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\,\s*([\d]+|[\d]*.[\d]+)\s*\)$/i );
if( rgba ) {
return {
r: parseInt( rgba[1], 10 ),
g: parseInt( rgba[2], 10 ),
b: parseInt( rgba[3], 10 ),
a: parseFloat( rgba[4] )
};
}
return null;
}
/**
* Calculates brightness on a scale of 0-255.
*
* @param color See colorStringToRgb for supported formats.
*/
function colorBrightness( color ) {
if( typeof color === 'string' ) color = colorToRgb( color );
if( color ) {
return ( color.r * 299 + color.g * 587 + color.b * 114 ) / 1000;
}
return null;
}
/**
* Retrieves the height of the given element by looking
* at the position and height of its immediate children.
*/
function getAbsoluteHeight( element ) {
var height = 0;
if( element ) {
var absoluteChildren = 0;
toArray( element.childNodes ).forEach( function( child ) {
if( typeof child.offsetTop === 'number' && child.style ) {
// Count # of abs children
if( window.getComputedStyle( child ).position === 'absolute' ) {
absoluteChildren += 1;
}
height = Math.max( height, child.offsetTop + child.offsetHeight );
}
} );
// If there are no absolute children, use offsetHeight
if( absoluteChildren === 0 ) {
height = element.offsetHeight;
}
}
return height;
}
/**
* Returns the remaining height within the parent of the
* target element.
*
* remaining height = [ configured parent height ] - [ current parent height ]
*/
function getRemainingHeight( element, height ) {
height = height || 0;
if( element ) {
var newHeight, oldHeight = element.style.height;
// Change the .stretch element height to 0 in order find the height of all
// the other elements
element.style.height = '0px';
newHeight = height - element.parentNode.offsetHeight;
// Restore the old height, just in case
element.style.height = oldHeight + 'px';
return newHeight;
}
return height;
}
/**
* Checks if this instance is being used to print a PDF.
*/
function isPrintingPDF() {
return ( /print-pdf/gi ).test( window.location.search );
}
/**
* Hides the address bar if we're on a mobile device.
*/
function hideAddressBar() {
if( config.hideAddressBar && isMobileDevice ) {
// Events that should trigger the address bar to hide
window.addEventListener( 'load', removeAddressBar, false );
window.addEventListener( 'orientationchange', removeAddressBar, false );
}
}
/**
* Causes the address bar to hide on mobile devices,
* more vertical space ftw.
*/
function removeAddressBar() {
setTimeout( function() {
window.scrollTo( 0, 1 );
}, 10 );
}
/**
* Dispatches an event of the specified type from the
* reveal DOM element.
*/
function dispatchEvent( type, args ) {
var event = document.createEvent( 'HTMLEvents', 1, 2 );
event.initEvent( type, true, true );
extend( event, args );
dom.wrapper.dispatchEvent( event );
// If we're in an iframe, post each reveal.js event to the
// parent window. Used by the notes plugin
if( config.postMessageEvents && window.parent !== window.self ) {
window.parent.postMessage( JSON.stringify({ namespace: 'reveal', eventName: type, state: getState() }), '*' );
}
}
/**
* Wrap all links in 3D goodness.
*/
function enableRollingLinks() {
if( features.transforms3d && !( 'msPerspective' in document.body.style ) ) {
var anchors = dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' a' );
for( var i = 0, len = anchors.length; i < len; i++ ) {
var anchor = anchors[i];
if( anchor.textContent && !anchor.querySelector( '*' ) && ( !anchor.className || !anchor.classList.contains( anchor, 'roll' ) ) ) {
var span = document.createElement('span');
span.setAttribute('data-title', anchor.text);
span.innerHTML = anchor.innerHTML;
anchor.classList.add( 'roll' );
anchor.innerHTML = '';
anchor.appendChild(span);
}
}
}
}
/**
* Unwrap all 3D links.
*/
function disableRollingLinks() {
var anchors = dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' a.roll' );
for( var i = 0, len = anchors.length; i < len; i++ ) {
var anchor = anchors[i];
var span = anchor.querySelector( 'span' );
if( span ) {
anchor.classList.remove( 'roll' );
anchor.innerHTML = span.innerHTML;
}
}
}
/**
* Bind preview frame links.
*/
function enablePreviewLinks( selector ) {
var anchors = toArray( document.querySelectorAll( selector ? selector : 'a' ) );
anchors.forEach( function( element ) {
if( /^(http|www)/gi.test( element.getAttribute( 'href' ) ) ) {
element.addEventListener( 'click', onPreviewLinkClicked, false );
}
} );
}
/**
* Unbind preview frame links.
*/
function disablePreviewLinks() {
var anchors = toArray( document.querySelectorAll( 'a' ) );
anchors.forEach( function( element ) {
if( /^(http|www)/gi.test( element.getAttribute( 'href' ) ) ) {
element.removeEventListener( 'click', onPreviewLinkClicked, false );
}
} );
}
/**
* Opens a preview window for the target URL.
*/
function showPreview( url ) {
closeOverlay();
dom.overlay = document.createElement( 'div' );
dom.overlay.classList.add( 'overlay' );
dom.overlay.classList.add( 'overlay-preview' );
dom.wrapper.appendChild( dom.overlay );
dom.overlay.innerHTML = [
'',
'',
'',
'',
'',
'
';
for( var key in keyboardShortcuts ) {
html += '
' + key + '
' + keyboardShortcuts[ key ] + '
';
}
html += '
';
dom.overlay.innerHTML = [
'',
'',
'',
'
',
'
'+ html +'
',
'
'
].join('');
dom.overlay.querySelector( '.close' ).addEventListener( 'click', function( event ) {
closeOverlay();
event.preventDefault();
}, false );
setTimeout( function() {
dom.overlay.classList.add( 'visible' );
}, 1 );
}
}
/**
* Closes any currently open overlay.
*/
function closeOverlay() {
if( dom.overlay ) {
dom.overlay.parentNode.removeChild( dom.overlay );
dom.overlay = null;
}
}
/**
* Applies JavaScript-controlled layout rules to the
* presentation.
*/
function layout() {
if( dom.wrapper && !isPrintingPDF() ) {
var size = getComputedSlideSize();
var slidePadding = 20; // TODO Dig this out of DOM
// Layout the contents of the slides
layoutSlideContents( config.width, config.height, slidePadding );
dom.slides.style.width = size.width + 'px';
dom.slides.style.height = size.height + 'px';
// Determine scale of content to fit within available space
scale = Math.min( size.presentationWidth / size.width, size.presentationHeight / size.height );
// Respect max/min scale settings
scale = Math.max( scale, config.minScale );
scale = Math.min( scale, config.maxScale );
// Don't apply any scaling styles if scale is 1
if( scale === 1 ) {
dom.slides.style.zoom = '';
dom.slides.style.left = '';
dom.slides.style.top = '';
dom.slides.style.bottom = '';
dom.slides.style.right = '';
transformElement( dom.slides, '' );
}
else {
// Prefer zooming in desktop Chrome so that content remains crisp
if( !isMobileDevice && /chrome/i.test( navigator.userAgent ) && typeof dom.slides.style.zoom !== 'undefined' ) {
dom.slides.style.zoom = scale;
}
// Apply scale transform as a fallback
else {
dom.slides.style.left = '50%';
dom.slides.style.top = '50%';
dom.slides.style.bottom = 'auto';
dom.slides.style.right = 'auto';
transformElement( dom.slides, 'translate(-50%, -50%) scale('+ scale +')' );
}
}
// Select all slides, vertical and horizontal
var slides = toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) );
for( var i = 0, len = slides.length; i < len; i++ ) {
var slide = slides[ i ];
// Don't bother updating invisible slides
if( slide.style.display === 'none' ) {
continue;
}
if( config.center || slide.classList.contains( 'center' ) ) {
// Vertical stacks are not centred since their section
// children will be
if( slide.classList.contains( 'stack' ) ) {
slide.style.top = 0;
}
else {
slide.style.top = Math.max( ( ( size.height - getAbsoluteHeight( slide ) ) / 2 ) - slidePadding, 0 ) + 'px';
}
}
else {
slide.style.top = '';
}
}
updateProgress();
updateParallax();
}
}
/**
* Applies layout logic to the contents of all slides in
* the presentation.
*/
function layoutSlideContents( width, height, padding ) {
// Handle sizing of elements with the 'stretch' class
toArray( dom.slides.querySelectorAll( 'section > .stretch' ) ).forEach( function( element ) {
// Determine how much vertical space we can use
var remainingHeight = getRemainingHeight( element, height );
// Consider the aspect ratio of media elements
if( /(img|video)/gi.test( element.nodeName ) ) {
var nw = element.naturalWidth || element.videoWidth,
nh = element.naturalHeight || element.videoHeight;
var es = Math.min( width / nw, remainingHeight / nh );
element.style.width = ( nw * es ) + 'px';
element.style.height = ( nh * es ) + 'px';
}
else {
element.style.width = width + 'px';
element.style.height = remainingHeight + 'px';
}
} );
}
/**
* Calculates the computed pixel size of our slides. These
* values are based on the width and height configuration
* options.
*/
function getComputedSlideSize( presentationWidth, presentationHeight ) {
var size = {
// Slide size
width: config.width,
height: config.height,
// Presentation size
presentationWidth: presentationWidth || dom.wrapper.offsetWidth,
presentationHeight: presentationHeight || dom.wrapper.offsetHeight
};
// Reduce available space by margin
size.presentationWidth -= ( size.presentationHeight * config.margin );
size.presentationHeight -= ( size.presentationHeight * config.margin );
// Slide width may be a percentage of available width
if( typeof size.width === 'string' && /%$/.test( size.width ) ) {
size.width = parseInt( size.width, 10 ) / 100 * size.presentationWidth;
}
// Slide height may be a percentage of available height
if( typeof size.height === 'string' && /%$/.test( size.height ) ) {
size.height = parseInt( size.height, 10 ) / 100 * size.presentationHeight;
}
return size;
}
/**
* Stores the vertical index of a stack so that the same
* vertical slide can be selected when navigating to and
* from the stack.
*
* @param {HTMLElement} stack The vertical stack element
* @param {int} v Index to memorize
*/
function setPreviousVerticalIndex( stack, v ) {
if( typeof stack === 'object' && typeof stack.setAttribute === 'function' ) {
stack.setAttribute( 'data-previous-indexv', v || 0 );
}
}
/**
* Retrieves the vertical index which was stored using
* #setPreviousVerticalIndex() or 0 if no previous index
* exists.
*
* @param {HTMLElement} stack The vertical stack element
*/
function getPreviousVerticalIndex( stack ) {
if( typeof stack === 'object' && typeof stack.setAttribute === 'function' && stack.classList.contains( 'stack' ) ) {
// Prefer manually defined start-indexv
var attributeName = stack.hasAttribute( 'data-start-indexv' ) ? 'data-start-indexv' : 'data-previous-indexv';
return parseInt( stack.getAttribute( attributeName ) || 0, 10 );
}
return 0;
}
/**
* Displays the overview of slides (quick nav) by
* scaling down and arranging all slide elements.
*
* Experimental feature, might be dropped if perf
* can't be improved.
*/
function activateOverview() {
// Only proceed if enabled in config
if( config.overview ) {
// Don't auto-slide while in overview mode
cancelAutoSlide();
var wasActive = dom.wrapper.classList.contains( 'overview' );
// Vary the depth of the overview based on screen size
var depth = window.innerWidth < 400 ? 1000 : 2500;
dom.wrapper.classList.add( 'overview' );
dom.wrapper.classList.remove( 'overview-deactivating' );
var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
for( var i = 0, len1 = horizontalSlides.length; i < len1; i++ ) {
var hslide = horizontalSlides[i],
hoffset = config.rtl ? -105 : 105;
hslide.setAttribute( 'data-index-h', i );
// Apply CSS transform
transformElement( hslide, 'translateZ(-'+ depth +'px) translate(' + ( ( i - indexh ) * hoffset ) + '%, 0%)' );
if( hslide.classList.contains( 'stack' ) ) {
var verticalSlides = hslide.querySelectorAll( 'section' );
for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) {
var verticalIndex = i === indexh ? indexv : getPreviousVerticalIndex( hslide );
var vslide = verticalSlides[j];
vslide.setAttribute( 'data-index-h', i );
vslide.setAttribute( 'data-index-v', j );
// Apply CSS transform
transformElement( vslide, 'translate(0%, ' + ( ( j - verticalIndex ) * 105 ) + '%)' );
// Navigate to this slide on click
vslide.addEventListener( 'click', onOverviewSlideClicked, true );
}
}
else {
// Navigate to this slide on click
hslide.addEventListener( 'click', onOverviewSlideClicked, true );
}
}
updateSlidesVisibility();
layout();
if( !wasActive ) {
// Notify observers of the overview showing
dispatchEvent( 'overviewshown', {
'indexh': indexh,
'indexv': indexv,
'currentSlide': currentSlide
} );
}
}
}
/**
* Exits the slide overview and enters the currently
* active slide.
*/
function deactivateOverview() {
// Only proceed if enabled in config
if( config.overview ) {
dom.wrapper.classList.remove( 'overview' );
// Temporarily add a class so that transitions can do different things
// depending on whether they are exiting/entering overview, or just
// moving from slide to slide
dom.wrapper.classList.add( 'overview-deactivating' );
setTimeout( function () {
dom.wrapper.classList.remove( 'overview-deactivating' );
}, 1 );
// Select all slides
toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
// Resets all transforms to use the external styles
transformElement( slide, '' );
slide.removeEventListener( 'click', onOverviewSlideClicked, true );
} );
slide( indexh, indexv );
cueAutoSlide();
// Notify observers of the overview hiding
dispatchEvent( 'overviewhidden', {
'indexh': indexh,
'indexv': indexv,
'currentSlide': currentSlide
} );
}
}
/**
* Toggles the slide overview mode on and off.
*
* @param {Boolean} override Optional flag which overrides the
* toggle logic and forcibly sets the desired state. True means
* overview is open, false means it's closed.
*/
function toggleOverview( override ) {
if( typeof override === 'boolean' ) {
override ? activateOverview() : deactivateOverview();
}
else {
isOverview() ? deactivateOverview() : activateOverview();
}
}
/**
* Checks if the overview is currently active.
*
* @return {Boolean} true if the overview is active,
* false otherwise
*/
function isOverview() {
return dom.wrapper.classList.contains( 'overview' );
}
/**
* Checks if the current or specified slide is vertical
* (nested within another slide).
*
* @param {HTMLElement} slide [optional] The slide to check
* orientation of
*/
function isVerticalSlide( slide ) {
// Prefer slide argument, otherwise use current slide
slide = slide ? slide : currentSlide;
return slide && slide.parentNode && !!slide.parentNode.nodeName.match( /section/i );
}
/**
* Handling the fullscreen functionality via the fullscreen API
*
* @see http://fullscreen.spec.whatwg.org/
* @see https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode
*/
function enterFullscreen() {
var element = document.body;
// Check which implementation is available
var requestMethod = element.requestFullScreen ||
element.webkitRequestFullscreen ||
element.webkitRequestFullScreen ||
element.mozRequestFullScreen ||
element.msRequestFullscreen;
if( requestMethod ) {
requestMethod.apply( element );
}
}
/**
* Enters the paused mode which fades everything on screen to
* black.
*/
function pause() {
if( config.pause ) {
var wasPaused = dom.wrapper.classList.contains( 'paused' );
cancelAutoSlide();
dom.wrapper.classList.add( 'paused' );
if( wasPaused === false ) {
dispatchEvent( 'paused' );
}
}
}
/**
* Exits from the paused mode.
*/
function resume() {
var wasPaused = dom.wrapper.classList.contains( 'paused' );
dom.wrapper.classList.remove( 'paused' );
cueAutoSlide();
if( wasPaused ) {
dispatchEvent( 'resumed' );
}
}
/**
* Toggles the paused mode on and off.
*/
function togglePause( override ) {
if( typeof override === 'boolean' ) {
override ? pause() : resume();
}
else {
isPaused() ? resume() : pause();
}
}
/**
* Checks if we are currently in the paused mode.
*/
function isPaused() {
return dom.wrapper.classList.contains( 'paused' );
}
/**
* Toggles the auto slide mode on and off.
*
* @param {Boolean} override Optional flag which sets the desired state.
* True means autoplay starts, false means it stops.
*/
function toggleAutoSlide( override ) {
if( typeof override === 'boolean' ) {
override ? resumeAutoSlide() : pauseAutoSlide();
}
else {
autoSlidePaused ? resumeAutoSlide() : pauseAutoSlide();
}
}
/**
* Checks if the auto slide mode is currently on.
*/
function isAutoSliding() {
return !!( autoSlide && !autoSlidePaused );
}
/**
* Steps from the current point in the presentation to the
* slide which matches the specified horizontal and vertical
* indices.
*
* @param {int} h Horizontal index of the target slide
* @param {int} v Vertical index of the target slide
* @param {int} f Optional index of a fragment within the
* target slide to activate
* @param {int} o Optional origin for use in multimaster environments
*/
function slide( h, v, f, o ) {
// Remember where we were at before
previousSlide = currentSlide;
// Query all horizontal slides in the deck
var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
// If no vertical index is specified and the upcoming slide is a
// stack, resume at its previous vertical index
if( v === undefined ) {
v = getPreviousVerticalIndex( horizontalSlides[ h ] );
}
// If we were on a vertical stack, remember what vertical index
// it was on so we can resume at the same position when returning
if( previousSlide && previousSlide.parentNode && previousSlide.parentNode.classList.contains( 'stack' ) ) {
setPreviousVerticalIndex( previousSlide.parentNode, indexv );
}
// Remember the state before this slide
var stateBefore = state.concat();
// Reset the state array
state.length = 0;
var indexhBefore = indexh || 0,
indexvBefore = indexv || 0;
// Activate and transition to the new slide
indexh = updateSlides( HORIZONTAL_SLIDES_SELECTOR, h === undefined ? indexh : h );
indexv = updateSlides( VERTICAL_SLIDES_SELECTOR, v === undefined ? indexv : v );
// Update the visibility of slides now that the indices have changed
updateSlidesVisibility();
layout();
// Apply the new state
stateLoop: for( var i = 0, len = state.length; i < len; i++ ) {
// Check if this state existed on the previous slide. If it
// did, we will avoid adding it repeatedly
for( var j = 0; j < stateBefore.length; j++ ) {
if( stateBefore[j] === state[i] ) {
stateBefore.splice( j, 1 );
continue stateLoop;
}
}
document.documentElement.classList.add( state[i] );
// Dispatch custom event matching the state's name
dispatchEvent( state[i] );
}
// Clean up the remains of the previous state
while( stateBefore.length ) {
document.documentElement.classList.remove( stateBefore.pop() );
}
// If the overview is active, re-activate it to update positions
if( isOverview() ) {
activateOverview();
}
// Find the current horizontal slide and any possible vertical slides
// within it
var currentHorizontalSlide = horizontalSlides[ indexh ],
currentVerticalSlides = currentHorizontalSlide.querySelectorAll( 'section' );
// Store references to the previous and current slides
currentSlide = currentVerticalSlides[ indexv ] || currentHorizontalSlide;
// Show fragment, if specified
if( typeof f !== 'undefined' ) {
navigateFragment( f );
}
// Dispatch an event if the slide changed
var slideChanged = ( indexh !== indexhBefore || indexv !== indexvBefore );
if( slideChanged ) {
dispatchEvent( 'slidechanged', {
'indexh': indexh,
'indexv': indexv,
'previousSlide': previousSlide,
'currentSlide': currentSlide,
'origin': o
} );
}
else {
// Ensure that the previous slide is never the same as the current
previousSlide = null;
}
// Solves an edge case where the previous slide maintains the
// 'present' class when navigating between adjacent vertical
// stacks
if( previousSlide ) {
previousSlide.classList.remove( 'present' );
previousSlide.setAttribute( 'aria-hidden', 'true' );
// Reset all slides upon navigate to home
// Issue: #285
if ( dom.wrapper.querySelector( HOME_SLIDE_SELECTOR ).classList.contains( 'present' ) ) {
// Launch async task
setTimeout( function () {
var slides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.stack') ), i;
for( i in slides ) {
if( slides[i] ) {
// Reset stack
setPreviousVerticalIndex( slides[i], 0 );
}
}
}, 0 );
}
}
// Handle embedded content
if( slideChanged || !previousSlide ) {
stopEmbeddedContent( previousSlide );
startEmbeddedContent( currentSlide );
}
// Announce the current slide contents, for screen readers
dom.statusDiv.textContent = currentSlide.textContent;
updateControls();
updateProgress();
updateBackground();
updateParallax();
updateSlideNumber();
// Update the URL hash
writeURL();
cueAutoSlide();
}
/**
* Syncs the presentation with the current DOM. Useful
* when new slides or control elements are added or when
* the configuration has changed.
*/
function sync() {
// Subscribe to input
removeEventListeners();
addEventListeners();
// Force a layout to make sure the current config is accounted for
layout();
// Reflect the current autoSlide value
autoSlide = config.autoSlide;
// Start auto-sliding if it's enabled
cueAutoSlide();
// Re-create the slide backgrounds
createBackgrounds();
// Write the current hash to the URL
writeURL();
sortAllFragments();
updateControls();
updateProgress();
updateBackground( true );
updateSlideNumber();
updateSlidesVisibility();
formatEmbeddedContent();
}
/**
* Resets all vertical slides so that only the first
* is visible.
*/
function resetVerticalSlides() {
var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
horizontalSlides.forEach( function( horizontalSlide ) {
var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
verticalSlides.forEach( function( verticalSlide, y ) {
if( y > 0 ) {
verticalSlide.classList.remove( 'present' );
verticalSlide.classList.remove( 'past' );
verticalSlide.classList.add( 'future' );
verticalSlide.setAttribute( 'aria-hidden', 'true' );
}
} );
} );
}
/**
* Sorts and formats all of fragments in the
* presentation.
*/
function sortAllFragments() {
var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
horizontalSlides.forEach( function( horizontalSlide ) {
var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
verticalSlides.forEach( function( verticalSlide, y ) {
sortFragments( verticalSlide.querySelectorAll( '.fragment' ) );
} );
if( verticalSlides.length === 0 ) sortFragments( horizontalSlide.querySelectorAll( '.fragment' ) );
} );
}
/**
* Updates one dimension of slides by showing the slide
* with the specified index.
*
* @param {String} selector A CSS selector that will fetch
* the group of slides we are working with
* @param {Number} index The index of the slide that should be
* shown
*
* @return {Number} The index of the slide that is now shown,
* might differ from the passed in index if it was out of
* bounds.
*/
function updateSlides( selector, index ) {
// Select all slides and convert the NodeList result to
// an array
var slides = toArray( dom.wrapper.querySelectorAll( selector ) ),
slidesLength = slides.length;
var printMode = isPrintingPDF();
if( slidesLength ) {
// Should the index loop?
if( config.loop ) {
index %= slidesLength;
if( index < 0 ) {
index = slidesLength + index;
}
}
// Enforce max and minimum index bounds
index = Math.max( Math.min( index, slidesLength - 1 ), 0 );
for( var i = 0; i < slidesLength; i++ ) {
var element = slides[i];
var reverse = config.rtl && !isVerticalSlide( element );
element.classList.remove( 'past' );
element.classList.remove( 'present' );
element.classList.remove( 'future' );
// http://www.w3.org/html/wg/drafts/html/master/editing.html#the-hidden-attribute
element.setAttribute( 'hidden', '' );
element.setAttribute( 'aria-hidden', 'true' );
// If this element contains vertical slides
if( element.querySelector( 'section' ) ) {
element.classList.add( 'stack' );
}
// If we're printing static slides, all slides are "present"
if( printMode ) {
element.classList.add( 'present' );
continue;
}
if( i < index ) {
// Any element previous to index is given the 'past' class
element.classList.add( reverse ? 'future' : 'past' );
if( config.fragments ) {
var pastFragments = toArray( element.querySelectorAll( '.fragment' ) );
// Show all fragments on prior slides
while( pastFragments.length ) {
var pastFragment = pastFragments.pop();
pastFragment.classList.add( 'visible' );
pastFragment.classList.remove( 'current-fragment' );
}
}
}
else if( i > index ) {
// Any element subsequent to index is given the 'future' class
element.classList.add( reverse ? 'past' : 'future' );
if( config.fragments ) {
var futureFragments = toArray( element.querySelectorAll( '.fragment.visible' ) );
// No fragments in future slides should be visible ahead of time
while( futureFragments.length ) {
var futureFragment = futureFragments.pop();
futureFragment.classList.remove( 'visible' );
futureFragment.classList.remove( 'current-fragment' );
}
}
}
}
// Mark the current slide as present
slides[index].classList.add( 'present' );
slides[index].removeAttribute( 'hidden' );
slides[index].removeAttribute( 'aria-hidden' );
// If this slide has a state associated with it, add it
// onto the current state of the deck
var slideState = slides[index].getAttribute( 'data-state' );
if( slideState ) {
state = state.concat( slideState.split( ' ' ) );
}
}
else {
// Since there are no slides we can't be anywhere beyond the
// zeroth index
index = 0;
}
return index;
}
/**
* Optimization method; hide all slides that are far away
* from the present slide.
*/
function updateSlidesVisibility() {
// Select all slides and convert the NodeList result to
// an array
var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ),
horizontalSlidesLength = horizontalSlides.length,
distanceX,
distanceY;
if( horizontalSlidesLength && typeof indexh !== 'undefined' ) {
// The number of steps away from the present slide that will
// be visible
var viewDistance = isOverview() ? 10 : config.viewDistance;
// Limit view distance on weaker devices
if( isMobileDevice ) {
viewDistance = isOverview() ? 6 : 2;
}
// Limit view distance on weaker devices
if( isPrintingPDF() ) {
viewDistance = Number.MAX_VALUE;
}
for( var x = 0; x < horizontalSlidesLength; x++ ) {
var horizontalSlide = horizontalSlides[x];
var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) ),
verticalSlidesLength = verticalSlides.length;
// Loops so that it measures 1 between the first and last slides
distanceX = Math.abs( ( ( indexh || 0 ) - x ) % ( horizontalSlidesLength - viewDistance ) ) || 0;
// Show the horizontal slide if it's within the view distance
if( distanceX < viewDistance ) {
showSlide( horizontalSlide );
}
else {
hideSlide( horizontalSlide );
}
if( verticalSlidesLength ) {
var oy = getPreviousVerticalIndex( horizontalSlide );
for( var y = 0; y < verticalSlidesLength; y++ ) {
var verticalSlide = verticalSlides[y];
distanceY = x === ( indexh || 0 ) ? Math.abs( ( indexv || 0 ) - y ) : Math.abs( y - oy );
if( distanceX + distanceY < viewDistance ) {
showSlide( verticalSlide );
}
else {
hideSlide( verticalSlide );
}
}
}
}
}
}
/**
* Updates the progress bar to reflect the current slide.
*/
function updateProgress() {
// Update progress if enabled
if( config.progress && dom.progressbar ) {
dom.progressbar.style.width = getProgress() * dom.wrapper.offsetWidth + 'px';
}
}
/**
* Updates the slide number div to reflect the current slide.
*/
function updateSlideNumber() {
// Update slide number if enabled
if( config.slideNumber && dom.slideNumber) {
// Display the number of the page using 'indexh - indexv' format
var indexString = indexh;
if( indexv > 0 ) {
indexString += ' - ' + indexv;
}
dom.slideNumber.innerHTML = indexString;
}
}
/**
* Updates the state of all control/navigation arrows.
*/
function updateControls() {
var routes = availableRoutes();
var fragments = availableFragments();
// Remove the 'enabled' class from all directions
dom.controlsLeft.concat( dom.controlsRight )
.concat( dom.controlsUp )
.concat( dom.controlsDown )
.concat( dom.controlsPrev )
.concat( dom.controlsNext ).forEach( function( node ) {
node.classList.remove( 'enabled' );
node.classList.remove( 'fragmented' );
} );
// Add the 'enabled' class to the available routes
if( routes.left ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'enabled' ); } );
if( routes.right ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'enabled' ); } );
if( routes.up ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'enabled' ); } );
if( routes.down ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'enabled' ); } );
// Prev/next buttons
if( routes.left || routes.up ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'enabled' ); } );
if( routes.right || routes.down ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'enabled' ); } );
// Highlight fragment directions
if( currentSlide ) {
// Always apply fragment decorator to prev/next buttons
if( fragments.prev ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
if( fragments.next ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
// Apply fragment decorators to directional buttons based on
// what slide axis they are in
if( isVerticalSlide( currentSlide ) ) {
if( fragments.prev ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
if( fragments.next ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
}
else {
if( fragments.prev ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
if( fragments.next ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
}
}
}
/**
* Updates the background elements to reflect the current
* slide.
*
* @param {Boolean} includeAll If true, the backgrounds of
* all vertical slides (not just the present) will be updated.
*/
function updateBackground( includeAll ) {
var currentBackground = null;
// Reverse past/future classes when in RTL mode
var horizontalPast = config.rtl ? 'future' : 'past',
horizontalFuture = config.rtl ? 'past' : 'future';
// Update the classes of all backgrounds to match the
// states of their slides (past/present/future)
toArray( dom.background.childNodes ).forEach( function( backgroundh, h ) {
backgroundh.classList.remove( 'past' );
backgroundh.classList.remove( 'present' );
backgroundh.classList.remove( 'future' );
if( h < indexh ) {
backgroundh.classList.add( horizontalPast );
}
else if ( h > indexh ) {
backgroundh.classList.add( horizontalFuture );
}
else {
backgroundh.classList.add( 'present' );
// Store a reference to the current background element
currentBackground = backgroundh;
}
if( includeAll || h === indexh ) {
toArray( backgroundh.querySelectorAll( '.slide-background' ) ).forEach( function( backgroundv, v ) {
backgroundv.classList.remove( 'past' );
backgroundv.classList.remove( 'present' );
backgroundv.classList.remove( 'future' );
if( v < indexv ) {
backgroundv.classList.add( 'past' );
}
else if ( v > indexv ) {
backgroundv.classList.add( 'future' );
}
else {
backgroundv.classList.add( 'present' );
// Only if this is the present horizontal and vertical slide
if( h === indexh ) currentBackground = backgroundv;
}
} );
}
} );
// Stop any currently playing video background
if( previousBackground ) {
var previousVideo = previousBackground.querySelector( 'video' );
if( previousVideo ) previousVideo.pause();
}
if( currentBackground ) {
// Start video playback
var currentVideo = currentBackground.querySelector( 'video' );
if( currentVideo ) {
currentVideo.currentTime = 0;
currentVideo.play();
}
// Don't transition between identical backgrounds. This
// prevents unwanted flicker.
var previousBackgroundHash = previousBackground ? previousBackground.getAttribute( 'data-background-hash' ) : null;
var currentBackgroundHash = currentBackground.getAttribute( 'data-background-hash' );
if( currentBackgroundHash && currentBackgroundHash === previousBackgroundHash && currentBackground !== previousBackground ) {
dom.background.classList.add( 'no-transition' );
}
previousBackground = currentBackground;
}
// If there's a background brightness flag for this slide,
// bubble it to the .reveal container
if( currentSlide ) {
[ 'has-light-background', 'has-dark-background' ].forEach( function( classToBubble ) {
if( currentSlide.classList.contains( classToBubble ) ) {
dom.wrapper.classList.add( classToBubble );
}
else {
dom.wrapper.classList.remove( classToBubble );
}
} );
}
// Allow the first background to apply without transition
setTimeout( function() {
dom.background.classList.remove( 'no-transition' );
}, 1 );
}
/**
* Updates the position of the parallax background based
* on the current slide index.
*/
function updateParallax() {
if( config.parallaxBackgroundImage ) {
var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
verticalSlides = dom.wrapper.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
var backgroundSize = dom.background.style.backgroundSize.split( ' ' ),
backgroundWidth, backgroundHeight;
if( backgroundSize.length === 1 ) {
backgroundWidth = backgroundHeight = parseInt( backgroundSize[0], 10 );
}
else {
backgroundWidth = parseInt( backgroundSize[0], 10 );
backgroundHeight = parseInt( backgroundSize[1], 10 );
}
var slideWidth = dom.background.offsetWidth;
var horizontalSlideCount = horizontalSlides.length;
var horizontalOffset = -( backgroundWidth - slideWidth ) / ( horizontalSlideCount-1 ) * indexh;
var slideHeight = dom.background.offsetHeight;
var verticalSlideCount = verticalSlides.length;
var verticalOffset = verticalSlideCount > 1 ? -( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 ) * indexv : 0;
dom.background.style.backgroundPosition = horizontalOffset + 'px ' + verticalOffset + 'px';
}
}
/**
* Called when the given slide is within the configured view
* distance. Shows the slide element and loads any content
* that is set to load lazily (data-src).
*/
function showSlide( slide ) {
// Show the slide element
slide.style.display = 'block';
// Media elements with data-src attributes
toArray( slide.querySelectorAll( 'img[data-src], video[data-src], audio[data-src], iframe[data-src]' ) ).forEach( function( element ) {
element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
element.removeAttribute( 'data-src' );
} );
// Media elements with children
toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( media ) {
var sources = 0;
toArray( media.querySelectorAll( 'source[data-src]' ) ).forEach( function( source ) {
source.setAttribute( 'src', source.getAttribute( 'data-src' ) );
source.removeAttribute( 'data-src' );
sources += 1;
} );
// If we rewrote sources for this video/audio element, we need
// to manually tell it to load from its new origin
if( sources > 0 ) {
media.load();
}
} );
// Show the corresponding background element
var indices = getIndices( slide );
var background = getSlideBackground( indices.h, indices.v );
if( background ) {
background.style.display = 'block';
// If the background contains media, load it
if( background.hasAttribute( 'data-loaded' ) === false ) {
background.setAttribute( 'data-loaded', 'true' );
var backgroundImage = slide.getAttribute( 'data-background-image' ),
backgroundVideo = slide.getAttribute( 'data-background-video' ),
backgroundIframe = slide.getAttribute( 'data-background-iframe' );
// Images
if( backgroundImage ) {
background.style.backgroundImage = 'url('+ backgroundImage +')';
}
// Videos
else if ( backgroundVideo && !isSpeakerNotes() ) {
var video = document.createElement( 'video' );
// Support comma separated lists of video sources
backgroundVideo.split( ',' ).forEach( function( source ) {
video.innerHTML += '';
} );
background.appendChild( video );
}
// Iframes
else if ( backgroundIframe ) {
var iframe = document.createElement( 'iframe' );
iframe.setAttribute( 'src', backgroundIframe );
iframe.style.width = '100%';
iframe.style.height = '100%';
iframe.style.maxHeight = '100%';
iframe.style.maxWidth = '100%';
background.appendChild( iframe );
}
}
}
}
/**
* Called when the given slide is moved outside of the
* configured view distance.
*/
function hideSlide( slide ) {
// Hide the slide element
slide.style.display = 'none';
// Hide the corresponding background element
var indices = getIndices( slide );
var background = getSlideBackground( indices.h, indices.v );
if( background ) {
background.style.display = 'none';
}
}
/**
* Determine what available routes there are for navigation.
*
* @return {Object} containing four booleans: left/right/up/down
*/
function availableRoutes() {
var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
verticalSlides = dom.wrapper.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
var routes = {
left: indexh > 0 || config.loop,
right: indexh < horizontalSlides.length - 1 || config.loop,
up: indexv > 0,
down: indexv < verticalSlides.length - 1
};
// reverse horizontal controls for rtl
if( config.rtl ) {
var left = routes.left;
routes.left = routes.right;
routes.right = left;
}
return routes;
}
/**
* Returns an object describing the available fragment
* directions.
*
* @return {Object} two boolean properties: prev/next
*/
function availableFragments() {
if( currentSlide && config.fragments ) {
var fragments = currentSlide.querySelectorAll( '.fragment' );
var hiddenFragments = currentSlide.querySelectorAll( '.fragment:not(.visible)' );
return {
prev: fragments.length - hiddenFragments.length > 0,
next: !!hiddenFragments.length
};
}
else {
return { prev: false, next: false };
}
}
/**
* Enforces origin-specific format rules for embedded media.
*/
function formatEmbeddedContent() {
// YouTube frames must include "?enablejsapi=1"
toArray( dom.slides.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
var src = el.getAttribute( 'src' );
if( !/enablejsapi\=1/gi.test( src ) ) {
el.setAttribute( 'src', src + ( !/\?/.test( src ) ? '?' : '&' ) + 'enablejsapi=1' );
}
});
// Vimeo frames must include "?api=1"
toArray( dom.slides.querySelectorAll( 'iframe[src*="player.vimeo.com/"]' ) ).forEach( function( el ) {
var src = el.getAttribute( 'src' );
if( !/api\=1/gi.test( src ) ) {
el.setAttribute( 'src', src + ( !/\?/.test( src ) ? '?' : '&' ) + 'api=1' );
}
});
}
/**
* Start playback of any embedded content inside of
* the targeted slide.
*/
function startEmbeddedContent( slide ) {
if( slide && !isSpeakerNotes() ) {
// HTML5 media elements
toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
if( el.hasAttribute( 'data-autoplay' ) ) {
el.play();
}
} );
// iframe embeds
toArray( slide.querySelectorAll( 'iframe' ) ).forEach( function( el ) {
el.contentWindow.postMessage( 'slide:start', '*' );
});
// YouTube embeds
toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
if( el.hasAttribute( 'data-autoplay' ) ) {
el.contentWindow.postMessage( '{"event":"command","func":"playVideo","args":""}', '*' );
}
});
// Vimeo embeds
toArray( slide.querySelectorAll( 'iframe[src*="player.vimeo.com/"]' ) ).forEach( function( el ) {
if( el.hasAttribute( 'data-autoplay' ) ) {
el.contentWindow.postMessage( '{"method":"play"}', '*' );
}
});
}
}
/**
* Stop playback of any embedded content inside of
* the targeted slide.
*/
function stopEmbeddedContent( slide ) {
if( slide && slide.parentNode ) {
// HTML5 media elements
toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
if( !el.hasAttribute( 'data-ignore' ) ) {
el.pause();
}
} );
// iframe embeds
toArray( slide.querySelectorAll( 'iframe' ) ).forEach( function( el ) {
el.contentWindow.postMessage( 'slide:stop', '*' );
});
// YouTube embeds
toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) {
el.contentWindow.postMessage( '{"event":"command","func":"pauseVideo","args":""}', '*' );
}
});
// Vimeo embeds
toArray( slide.querySelectorAll( 'iframe[src*="player.vimeo.com/"]' ) ).forEach( function( el ) {
if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) {
el.contentWindow.postMessage( '{"method":"pause"}', '*' );
}
});
}
}
/**
* Returns a value ranging from 0-1 that represents
* how far into the presentation we have navigated.
*/
function getProgress() {
var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
// The number of past and total slides
var totalCount = getTotalSlides();
var pastCount = 0;
// Step through all slides and count the past ones
mainLoop: for( var i = 0; i < horizontalSlides.length; i++ ) {
var horizontalSlide = horizontalSlides[i];
var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
for( var j = 0; j < verticalSlides.length; j++ ) {
// Stop as soon as we arrive at the present
if( verticalSlides[j].classList.contains( 'present' ) ) {
break mainLoop;
}
pastCount++;
}
// Stop as soon as we arrive at the present
if( horizontalSlide.classList.contains( 'present' ) ) {
break;
}
// Don't count the wrapping section for vertical slides
if( horizontalSlide.classList.contains( 'stack' ) === false ) {
pastCount++;
}
}
if( currentSlide ) {
var allFragments = currentSlide.querySelectorAll( '.fragment' );
// If there are fragments in the current slide those should be
// accounted for in the progress.
if( allFragments.length > 0 ) {
var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
// This value represents how big a portion of the slide progress
// that is made up by its fragments (0-1)
var fragmentWeight = 0.9;
// Add fragment progress to the past slide count
pastCount += ( visibleFragments.length / allFragments.length ) * fragmentWeight;
}
}
return pastCount / ( totalCount - 1 );
}
/**
* Checks if this presentation is running inside of the
* speaker notes window.
*/
function isSpeakerNotes() {
return !!window.location.search.match( /receiver/gi );
}
/**
* Reads the current URL (hash) and navigates accordingly.
*/
function readURL() {
var hash = window.location.hash;
// Attempt to parse the hash as either an index or name
var bits = hash.slice( 2 ).split( '/' ),
name = hash.replace( /#|\//gi, '' );
// If the first bit is invalid and there is a name we can
// assume that this is a named link
if( isNaN( parseInt( bits[0], 10 ) ) && name.length ) {
var element;
// Ensure the named link is a valid HTML ID attribute
if( /^[a-zA-Z][\w:.-]*$/.test( name ) ) {
// Find the slide with the specified ID
element = document.querySelector( '#' + name );
}
if( element ) {
// Find the position of the named slide and navigate to it
var indices = Reveal.getIndices( element );
slide( indices.h, indices.v );
}
// If the slide doesn't exist, navigate to the current slide
else {
slide( indexh || 0, indexv || 0 );
}
}
else {
// Read the index components of the hash
var h = parseInt( bits[0], 10 ) || 0,
v = parseInt( bits[1], 10 ) || 0;
if( h !== indexh || v !== indexv ) {
slide( h, v );
}
}
}
/**
* Updates the page URL (hash) to reflect the current
* state.
*
* @param {Number} delay The time in ms to wait before
* writing the hash
*/
function writeURL( delay ) {
if( config.history ) {
// Make sure there's never more than one timeout running
clearTimeout( writeURLTimeout );
// If a delay is specified, timeout this call
if( typeof delay === 'number' ) {
writeURLTimeout = setTimeout( writeURL, delay );
}
else if( currentSlide ) {
var url = '/';
// Attempt to create a named link based on the slide's ID
var id = currentSlide.getAttribute( 'id' );
if( id ) {
id = id.toLowerCase();
id = id.replace( /[^a-zA-Z0-9\-\_\:\.]/g, '' );
}
// If the current slide has an ID, use that as a named link
if( typeof id === 'string' && id.length ) {
url = '/' + id;
}
// Otherwise use the /h/v index
else {
if( indexh > 0 || indexv > 0 ) url += indexh;
if( indexv > 0 ) url += '/' + indexv;
}
window.location.hash = url;
}
}
}
/**
* Retrieves the h/v location of the current, or specified,
* slide.
*
* @param {HTMLElement} slide If specified, the returned
* index will be for this slide rather than the currently
* active one
*
* @return {Object} { h: , v: , f: }
*/
function getIndices( slide ) {
// By default, return the current indices
var h = indexh,
v = indexv,
f;
// If a slide is specified, return the indices of that slide
if( slide ) {
var isVertical = isVerticalSlide( slide );
var slideh = isVertical ? slide.parentNode : slide;
// Select all horizontal slides
var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
// Now that we know which the horizontal slide is, get its index
h = Math.max( horizontalSlides.indexOf( slideh ), 0 );
// Assume we're not vertical
v = undefined;
// If this is a vertical slide, grab the vertical index
if( isVertical ) {
v = Math.max( toArray( slide.parentNode.querySelectorAll( 'section' ) ).indexOf( slide ), 0 );
}
}
if( !slide && currentSlide ) {
var hasFragments = currentSlide.querySelectorAll( '.fragment' ).length > 0;
if( hasFragments ) {
var currentFragment = currentSlide.querySelector( '.current-fragment' );
if( currentFragment && currentFragment.hasAttribute( 'data-fragment-index' ) ) {
f = parseInt( currentFragment.getAttribute( 'data-fragment-index' ), 10 );
}
else {
f = currentSlide.querySelectorAll( '.fragment.visible' ).length - 1;
}
}
}
return { h: h, v: v, f: f };
}
/**
* Retrieves the total number of slides in this presentation.
*/
function getTotalSlides() {
return dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ).length;
}
/**
* Returns the slide element matching the specified index.
*/
function getSlide( x, y ) {
var horizontalSlide = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR )[ x ];
var verticalSlides = horizontalSlide && horizontalSlide.querySelectorAll( 'section' );
if( verticalSlides && verticalSlides.length && typeof y === 'number' ) {
return verticalSlides ? verticalSlides[ y ] : undefined;
}
return horizontalSlide;
}
/**
* Returns the background element for the given slide.
* All slides, even the ones with no background properties
* defined, have a background element so as long as the
* index is valid an element will be returned.
*/
function getSlideBackground( x, y ) {
// When printing to PDF the slide backgrounds are nested
// inside of the slides
if( isPrintingPDF() ) {
var slide = getSlide( x, y );
if( slide ) {
var background = slide.querySelector( '.slide-background' );
if( background && background.parentNode === slide ) {
return background;
}
}
return undefined;
}
var horizontalBackground = dom.wrapper.querySelectorAll( '.backgrounds>.slide-background' )[ x ];
var verticalBackgrounds = horizontalBackground && horizontalBackground.querySelectorAll( '.slide-background' );
if( verticalBackgrounds && verticalBackgrounds.length && typeof y === 'number' ) {
return verticalBackgrounds ? verticalBackgrounds[ y ] : undefined;
}
return horizontalBackground;
}
/**
* Retrieves the current state of the presentation as
* an object. This state can then be restored at any
* time.
*/
function getState() {
var indices = getIndices();
return {
indexh: indices.h,
indexv: indices.v,
indexf: indices.f,
paused: isPaused(),
overview: isOverview()
};
}
/**
* Restores the presentation to the given state.
*
* @param {Object} state As generated by getState()
*/
function setState( state ) {
if( typeof state === 'object' ) {
slide( deserialize( state.indexh ), deserialize( state.indexv ), deserialize( state.indexf ) );
var pausedFlag = deserialize( state.paused ),
overviewFlag = deserialize( state.overview );
if( typeof pausedFlag === 'boolean' && pausedFlag !== isPaused() ) {
togglePause( pausedFlag );
}
if( typeof overviewFlag === 'boolean' && overviewFlag !== isOverview() ) {
toggleOverview( overviewFlag );
}
}
}
/**
* Return a sorted fragments list, ordered by an increasing
* "data-fragment-index" attribute.
*
* Fragments will be revealed in the order that they are returned by
* this function, so you can use the index attributes to control the
* order of fragment appearance.
*
* To maintain a sensible default fragment order, fragments are presumed
* to be passed in document order. This function adds a "fragment-index"
* attribute to each node if such an attribute is not already present,
* and sets that attribute to an integer value which is the position of
* the fragment within the fragments list.
*/
function sortFragments( fragments ) {
fragments = toArray( fragments );
var ordered = [],
unordered = [],
sorted = [];
// Group ordered and unordered elements
fragments.forEach( function( fragment, i ) {
if( fragment.hasAttribute( 'data-fragment-index' ) ) {
var index = parseInt( fragment.getAttribute( 'data-fragment-index' ), 10 );
if( !ordered[index] ) {
ordered[index] = [];
}
ordered[index].push( fragment );
}
else {
unordered.push( [ fragment ] );
}
} );
// Append fragments without explicit indices in their
// DOM order
ordered = ordered.concat( unordered );
// Manually count the index up per group to ensure there
// are no gaps
var index = 0;
// Push all fragments in their sorted order to an array,
// this flattens the groups
ordered.forEach( function( group ) {
group.forEach( function( fragment ) {
sorted.push( fragment );
fragment.setAttribute( 'data-fragment-index', index );
} );
index ++;
} );
return sorted;
}
/**
* Navigate to the specified slide fragment.
*
* @param {Number} index The index of the fragment that
* should be shown, -1 means all are invisible
* @param {Number} offset Integer offset to apply to the
* fragment index
*
* @return {Boolean} true if a change was made in any
* fragments visibility as part of this call
*/
function navigateFragment( index, offset ) {
if( currentSlide && config.fragments ) {
var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment' ) );
if( fragments.length ) {
// If no index is specified, find the current
if( typeof index !== 'number' ) {
var lastVisibleFragment = sortFragments( currentSlide.querySelectorAll( '.fragment.visible' ) ).pop();
if( lastVisibleFragment ) {
index = parseInt( lastVisibleFragment.getAttribute( 'data-fragment-index' ) || 0, 10 );
}
else {
index = -1;
}
}
// If an offset is specified, apply it to the index
if( typeof offset === 'number' ) {
index += offset;
}
var fragmentsShown = [],
fragmentsHidden = [];
toArray( fragments ).forEach( function( element, i ) {
if( element.hasAttribute( 'data-fragment-index' ) ) {
i = parseInt( element.getAttribute( 'data-fragment-index' ), 10 );
}
// Visible fragments
if( i <= index ) {
if( !element.classList.contains( 'visible' ) ) fragmentsShown.push( element );
element.classList.add( 'visible' );
element.classList.remove( 'current-fragment' );
// Announce the fragments one by one to the Screen Reader
dom.statusDiv.textContent = element.textContent;
if( i === index ) {
element.classList.add( 'current-fragment' );
}
}
// Hidden fragments
else {
if( element.classList.contains( 'visible' ) ) fragmentsHidden.push( element );
element.classList.remove( 'visible' );
element.classList.remove( 'current-fragment' );
}
} );
if( fragmentsHidden.length ) {
dispatchEvent( 'fragmenthidden', { fragment: fragmentsHidden[0], fragments: fragmentsHidden } );
}
if( fragmentsShown.length ) {
dispatchEvent( 'fragmentshown', { fragment: fragmentsShown[0], fragments: fragmentsShown } );
}
updateControls();
updateProgress();
return !!( fragmentsShown.length || fragmentsHidden.length );
}
}
return false;
}
/**
* Navigate to the next slide fragment.
*
* @return {Boolean} true if there was a next fragment,
* false otherwise
*/
function nextFragment() {
return navigateFragment( null, 1 );
}
/**
* Navigate to the previous slide fragment.
*
* @return {Boolean} true if there was a previous fragment,
* false otherwise
*/
function previousFragment() {
return navigateFragment( null, -1 );
}
/**
* Cues a new automated slide if enabled in the config.
*/
function cueAutoSlide() {
cancelAutoSlide();
if( currentSlide ) {
var currentFragment = currentSlide.querySelector( '.current-fragment' );
var fragmentAutoSlide = currentFragment ? currentFragment.getAttribute( 'data-autoslide' ) : null;
var parentAutoSlide = currentSlide.parentNode ? currentSlide.parentNode.getAttribute( 'data-autoslide' ) : null;
var slideAutoSlide = currentSlide.getAttribute( 'data-autoslide' );
// Pick value in the following priority order:
// 1. Current fragment's data-autoslide
// 2. Current slide's data-autoslide
// 3. Parent slide's data-autoslide
// 4. Global autoSlide setting
if( fragmentAutoSlide ) {
autoSlide = parseInt( fragmentAutoSlide, 10 );
}
else if( slideAutoSlide ) {
autoSlide = parseInt( slideAutoSlide, 10 );
}
else if( parentAutoSlide ) {
autoSlide = parseInt( parentAutoSlide, 10 );
}
else {
autoSlide = config.autoSlide;
}
// If there are media elements with data-autoplay,
// automatically set the autoSlide duration to the
// length of that media
toArray( currentSlide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
if( el.hasAttribute( 'data-autoplay' ) ) {
if( autoSlide && el.duration * 1000 > autoSlide ) {
autoSlide = ( el.duration * 1000 ) + 1000;
}
}
} );
// Cue the next auto-slide if:
// - There is an autoSlide value
// - Auto-sliding isn't paused by the user
// - The presentation isn't paused
// - The overview isn't active
// - The presentation isn't over
if( autoSlide && !autoSlidePaused && !isPaused() && !isOverview() && ( !Reveal.isLastSlide() || availableFragments().next || config.loop === true ) ) {
autoSlideTimeout = setTimeout( navigateNext, autoSlide );
autoSlideStartTime = Date.now();
}
if( autoSlidePlayer ) {
autoSlidePlayer.setPlaying( autoSlideTimeout !== -1 );
}
}
}
/**
* Cancels any ongoing request to auto-slide.
*/
function cancelAutoSlide() {
clearTimeout( autoSlideTimeout );
autoSlideTimeout = -1;
}
function pauseAutoSlide() {
if( autoSlide && !autoSlidePaused ) {
autoSlidePaused = true;
dispatchEvent( 'autoslidepaused' );
clearTimeout( autoSlideTimeout );
if( autoSlidePlayer ) {
autoSlidePlayer.setPlaying( false );
}
}
}
function resumeAutoSlide() {
if( autoSlide && autoSlidePaused ) {
autoSlidePaused = false;
dispatchEvent( 'autoslideresumed' );
cueAutoSlide();
}
}
function navigateLeft() {
// Reverse for RTL
if( config.rtl ) {
if( ( isOverview() || nextFragment() === false ) && availableRoutes().left ) {
slide( indexh + 1 );
}
}
// Normal navigation
else if( ( isOverview() || previousFragment() === false ) && availableRoutes().left ) {
slide( indexh - 1 );
}
}
function navigateRight() {
// Reverse for RTL
if( config.rtl ) {
if( ( isOverview() || previousFragment() === false ) && availableRoutes().right ) {
slide( indexh - 1 );
}
}
// Normal navigation
else if( ( isOverview() || nextFragment() === false ) && availableRoutes().right ) {
slide( indexh + 1 );
}
}
function navigateUp() {
// Prioritize hiding fragments
if( ( isOverview() || previousFragment() === false ) && availableRoutes().up ) {
slide( indexh, indexv - 1 );
}
}
function navigateDown() {
// Prioritize revealing fragments
if( ( isOverview() || nextFragment() === false ) && availableRoutes().down ) {
slide( indexh, indexv + 1 );
}
}
/**
* Navigates backwards, prioritized in the following order:
* 1) Previous fragment
* 2) Previous vertical slide
* 3) Previous horizontal slide
*/
function navigatePrev() {
// Prioritize revealing fragments
if( previousFragment() === false ) {
if( availableRoutes().up ) {
navigateUp();
}
else {
// Fetch the previous horizontal slide, if there is one
var previousSlide;
if( config.rtl ) {
previousSlide = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.future' ) ).pop();
}
else {
previousSlide = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.past' ) ).pop();
}
if( previousSlide ) {
var v = ( previousSlide.querySelectorAll( 'section' ).length - 1 ) || undefined;
var h = indexh - 1;
slide( h, v );
}
}
}
}
/**
* The reverse of #navigatePrev().
*/
function navigateNext() {
// Prioritize revealing fragments
if( nextFragment() === false ) {
if( availableRoutes().down ) {
navigateDown();
}
else if( config.rtl ) {
navigateLeft();
}
else {
navigateRight();
}
}
// If auto-sliding is enabled we need to cue up
// another timeout
cueAutoSlide();
}
// --------------------------------------------------------------------//
// ----------------------------- EVENTS -------------------------------//
// --------------------------------------------------------------------//
/**
* Called by all event handlers that are based on user
* input.
*/
function onUserInput( event ) {
if( config.autoSlideStoppable ) {
pauseAutoSlide();
}
}
/**
* Handler for the document level 'keypress' event.
*/
function onDocumentKeyPress( event ) {
// Check if the pressed key is question mark
if( event.shiftKey && event.charCode === 63 ) {
if( dom.overlay ) {
closeOverlay();
}
else {
showHelp( true );
}
}
}
/**
* Handler for the document level 'keydown' event.
*/
function onDocumentKeyDown( event ) {
// If there's a condition specified and it returns false,
// ignore this event
if( typeof config.keyboardCondition === 'function' && config.keyboardCondition() === false ) {
return true;
}
// Remember if auto-sliding was paused so we can toggle it
var autoSlideWasPaused = autoSlidePaused;
onUserInput( event );
// Check if there's a focused element that could be using
// the keyboard
var activeElementIsCE = document.activeElement && document.activeElement.contentEditable !== 'inherit';
var activeElementIsInput = document.activeElement && document.activeElement.tagName && /input|textarea/i.test( document.activeElement.tagName );
// Disregard the event if there's a focused element or a
// keyboard modifier key is present
if( activeElementIsCE || activeElementIsInput || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
// While paused only allow "unpausing" keyboard events (b and .)
if( isPaused() && [66,190,191].indexOf( event.keyCode ) === -1 ) {
return false;
}
var triggered = false;
// 1. User defined key bindings
if( typeof config.keyboard === 'object' ) {
for( var key in config.keyboard ) {
// Check if this binding matches the pressed key
if( parseInt( key, 10 ) === event.keyCode ) {
var value = config.keyboard[ key ];
// Callback function
if( typeof value === 'function' ) {
value.apply( null, [ event ] );
}
// String shortcuts to reveal.js API
else if( typeof value === 'string' && typeof Reveal[ value ] === 'function' ) {
Reveal[ value ].call();
}
triggered = true;
}
}
}
// 2. System defined key bindings
if( triggered === false ) {
// Assume true and try to prove false
triggered = true;
switch( event.keyCode ) {
// p, page up
case 80: case 33: navigatePrev(); break;
// n, page down
case 78: case 34: navigateNext(); break;
// h, left
case 72: case 37: navigateLeft(); break;
// l, right
case 76: case 39: navigateRight(); break;
// k, up
case 75: case 38: navigateUp(); break;
// j, down
case 74: case 40: navigateDown(); break;
// home
case 36: slide( 0 ); break;
// end
case 35: slide( Number.MAX_VALUE ); break;
// space
case 32: isOverview() ? deactivateOverview() : event.shiftKey ? navigatePrev() : navigateNext(); break;
// return
case 13: isOverview() ? deactivateOverview() : triggered = false; break;
// two-spot, semicolon, b, period, Logitech presenter tools "black screen" button
case 58: case 59: case 66: case 190: case 191: togglePause(); break;
// f
case 70: enterFullscreen(); break;
// a
case 65: if ( config.autoSlideStoppable ) toggleAutoSlide( autoSlideWasPaused ); break;
default:
triggered = false;
}
}
// If the input resulted in a triggered action we should prevent
// the browsers default behavior
if( triggered ) {
event.preventDefault && event.preventDefault();
}
// ESC or O key
else if ( ( event.keyCode === 27 || event.keyCode === 79 ) && features.transforms3d ) {
if( dom.overlay ) {
closeOverlay();
}
else {
toggleOverview();
}
event.preventDefault && event.preventDefault();
}
// If auto-sliding is enabled we need to cue up
// another timeout
cueAutoSlide();
}
/**
* Handler for the 'touchstart' event, enables support for
* swipe and pinch gestures.
*/
function onTouchStart( event ) {
touch.startX = event.touches[0].clientX;
touch.startY = event.touches[0].clientY;
touch.startCount = event.touches.length;
// If there's two touches we need to memorize the distance
// between those two points to detect pinching
if( event.touches.length === 2 && config.overview ) {
touch.startSpan = distanceBetween( {
x: event.touches[1].clientX,
y: event.touches[1].clientY
}, {
x: touch.startX,
y: touch.startY
} );
}
}
/**
* Handler for the 'touchmove' event.
*/
function onTouchMove( event ) {
// Each touch should only trigger one action
if( !touch.captured ) {
onUserInput( event );
var currentX = event.touches[0].clientX;
var currentY = event.touches[0].clientY;
// If the touch started with two points and still has
// two active touches; test for the pinch gesture
if( event.touches.length === 2 && touch.startCount === 2 && config.overview ) {
// The current distance in pixels between the two touch points
var currentSpan = distanceBetween( {
x: event.touches[1].clientX,
y: event.touches[1].clientY
}, {
x: touch.startX,
y: touch.startY
} );
// If the span is larger than the desire amount we've got
// ourselves a pinch
if( Math.abs( touch.startSpan - currentSpan ) > touch.threshold ) {
touch.captured = true;
if( currentSpan < touch.startSpan ) {
activateOverview();
}
else {
deactivateOverview();
}
}
event.preventDefault();
}
// There was only one touch point, look for a swipe
else if( event.touches.length === 1 && touch.startCount !== 2 ) {
var deltaX = currentX - touch.startX,
deltaY = currentY - touch.startY;
if( deltaX > touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
touch.captured = true;
navigateLeft();
}
else if( deltaX < -touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
touch.captured = true;
navigateRight();
}
else if( deltaY > touch.threshold ) {
touch.captured = true;
navigateUp();
}
else if( deltaY < -touch.threshold ) {
touch.captured = true;
navigateDown();
}
// If we're embedded, only block touch events if they have
// triggered an action
if( config.embedded ) {
if( touch.captured || isVerticalSlide( currentSlide ) ) {
event.preventDefault();
}
}
// Not embedded? Block them all to avoid needless tossing
// around of the viewport in iOS
else {
event.preventDefault();
}
}
}
// There's a bug with swiping on some Android devices unless
// the default action is always prevented
else if( navigator.userAgent.match( /android/gi ) ) {
event.preventDefault();
}
}
/**
* Handler for the 'touchend' event.
*/
function onTouchEnd( event ) {
touch.captured = false;
}
/**
* Convert pointer down to touch start.
*/
function onPointerDown( event ) {
if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
onTouchStart( event );
}
}
/**
* Convert pointer move to touch move.
*/
function onPointerMove( event ) {
if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
onTouchMove( event );
}
}
/**
* Convert pointer up to touch end.
*/
function onPointerUp( event ) {
if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
onTouchEnd( event );
}
}
/**
* Handles mouse wheel scrolling, throttled to avoid skipping
* multiple slides.
*/
function onDocumentMouseScroll( event ) {
if( Date.now() - lastMouseWheelStep > 600 ) {
lastMouseWheelStep = Date.now();
var delta = event.detail || -event.wheelDelta;
if( delta > 0 ) {
navigateNext();
}
else {
navigatePrev();
}
}
}
/**
* Clicking on the progress bar results in a navigation to the
* closest approximate horizontal slide using this equation:
*
* ( clickX / presentationWidth ) * numberOfSlides
*/
function onProgressClicked( event ) {
onUserInput( event );
event.preventDefault();
var slidesTotal = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
var slideIndex = Math.floor( ( event.clientX / dom.wrapper.offsetWidth ) * slidesTotal );
slide( slideIndex );
}
/**
* Event handler for navigation control buttons.
*/
function onNavigateLeftClicked( event ) { event.preventDefault(); onUserInput(); navigateLeft(); }
function onNavigateRightClicked( event ) { event.preventDefault(); onUserInput(); navigateRight(); }
function onNavigateUpClicked( event ) { event.preventDefault(); onUserInput(); navigateUp(); }
function onNavigateDownClicked( event ) { event.preventDefault(); onUserInput(); navigateDown(); }
function onNavigatePrevClicked( event ) { event.preventDefault(); onUserInput(); navigatePrev(); }
function onNavigateNextClicked( event ) { event.preventDefault(); onUserInput(); navigateNext(); }
/**
* Handler for the window level 'hashchange' event.
*/
function onWindowHashChange( event ) {
readURL();
}
/**
* Handler for the window level 'resize' event.
*/
function onWindowResize( event ) {
layout();
}
/**
* Handle for the window level 'visibilitychange' event.
*/
function onPageVisibilityChange( event ) {
var isHidden = document.webkitHidden ||
document.msHidden ||
document.hidden;
// If, after clicking a link or similar and we're coming back,
// focus the document.body to ensure we can use keyboard shortcuts
if( isHidden === false && document.activeElement !== document.body ) {
document.activeElement.blur();
document.body.focus();
}
}
/**
* Invoked when a slide is and we're in the overview.
*/
function onOverviewSlideClicked( event ) {
// TODO There's a bug here where the event listeners are not
// removed after deactivating the overview.
if( eventsAreBound && isOverview() ) {
event.preventDefault();
var element = event.target;
while( element && !element.nodeName.match( /section/gi ) ) {
element = element.parentNode;
}
if( element && !element.classList.contains( 'disabled' ) ) {
deactivateOverview();
if( element.nodeName.match( /section/gi ) ) {
var h = parseInt( element.getAttribute( 'data-index-h' ), 10 ),
v = parseInt( element.getAttribute( 'data-index-v' ), 10 );
slide( h, v );
}
}
}
}
/**
* Handles clicks on links that are set to preview in the
* iframe overlay.
*/
function onPreviewLinkClicked( event ) {
if( event.currentTarget && event.currentTarget.hasAttribute( 'href' ) ) {
var url = event.currentTarget.getAttribute( 'href' );
if( url ) {
showPreview( url );
event.preventDefault();
}
}
}
/**
* Handles click on the auto-sliding controls element.
*/
function onAutoSlidePlayerClick( event ) {
// Replay
if( Reveal.isLastSlide() && config.loop === false ) {
slide( 0, 0 );
resumeAutoSlide();
}
// Resume
else if( autoSlidePaused ) {
resumeAutoSlide();
}
// Pause
else {
pauseAutoSlide();
}
}
// --------------------------------------------------------------------//
// ------------------------ PLAYBACK COMPONENT ------------------------//
// --------------------------------------------------------------------//
/**
* Constructor for the playback component, which displays
* play/pause/progress controls.
*
* @param {HTMLElement} container The component will append
* itself to this
* @param {Function} progressCheck A method which will be
* called frequently to get the current progress on a range
* of 0-1
*/
function Playback( container, progressCheck ) {
// Cosmetics
this.diameter = 50;
this.thickness = 3;
// Flags if we are currently playing
this.playing = false;
// Current progress on a 0-1 range
this.progress = 0;
// Used to loop the animation smoothly
this.progressOffset = 1;
this.container = container;
this.progressCheck = progressCheck;
this.canvas = document.createElement( 'canvas' );
this.canvas.className = 'playback';
this.canvas.width = this.diameter;
this.canvas.height = this.diameter;
this.context = this.canvas.getContext( '2d' );
this.container.appendChild( this.canvas );
this.render();
}
Playback.prototype.setPlaying = function( value ) {
var wasPlaying = this.playing;
this.playing = value;
// Start repainting if we weren't already
if( !wasPlaying && this.playing ) {
this.animate();
}
else {
this.render();
}
};
Playback.prototype.animate = function() {
var progressBefore = this.progress;
this.progress = this.progressCheck();
// When we loop, offset the progress so that it eases
// smoothly rather than immediately resetting
if( progressBefore > 0.8 && this.progress < 0.2 ) {
this.progressOffset = this.progress;
}
this.render();
if( this.playing ) {
features.requestAnimationFrameMethod.call( window, this.animate.bind( this ) );
}
};
/**
* Renders the current progress and playback state.
*/
Playback.prototype.render = function() {
var progress = this.playing ? this.progress : 0,
radius = ( this.diameter / 2 ) - this.thickness,
x = this.diameter / 2,
y = this.diameter / 2,
iconSize = 14;
// Ease towards 1
this.progressOffset += ( 1 - this.progressOffset ) * 0.1;
var endAngle = ( - Math.PI / 2 ) + ( progress * ( Math.PI * 2 ) );
var startAngle = ( - Math.PI / 2 ) + ( this.progressOffset * ( Math.PI * 2 ) );
this.context.save();
this.context.clearRect( 0, 0, this.diameter, this.diameter );
// Solid background color
this.context.beginPath();
this.context.arc( x, y, radius + 2, 0, Math.PI * 2, false );
this.context.fillStyle = 'rgba( 0, 0, 0, 0.4 )';
this.context.fill();
// Draw progress track
this.context.beginPath();
this.context.arc( x, y, radius, 0, Math.PI * 2, false );
this.context.lineWidth = this.thickness;
this.context.strokeStyle = '#666';
this.context.stroke();
if( this.playing ) {
// Draw progress on top of track
this.context.beginPath();
this.context.arc( x, y, radius, startAngle, endAngle, false );
this.context.lineWidth = this.thickness;
this.context.strokeStyle = '#fff';
this.context.stroke();
}
this.context.translate( x - ( iconSize / 2 ), y - ( iconSize / 2 ) );
// Draw play/pause icons
if( this.playing ) {
this.context.fillStyle = '#fff';
this.context.fillRect( 0, 0, iconSize / 2 - 2, iconSize );
this.context.fillRect( iconSize / 2 + 2, 0, iconSize / 2 - 2, iconSize );
}
else {
this.context.beginPath();
this.context.translate( 2, 0 );
this.context.moveTo( 0, 0 );
this.context.lineTo( iconSize - 2, iconSize / 2 );
this.context.lineTo( 0, iconSize );
this.context.fillStyle = '#fff';
this.context.fill();
}
this.context.restore();
};
Playback.prototype.on = function( type, listener ) {
this.canvas.addEventListener( type, listener, false );
};
Playback.prototype.off = function( type, listener ) {
this.canvas.removeEventListener( type, listener, false );
};
Playback.prototype.destroy = function() {
this.playing = false;
if( this.canvas.parentNode ) {
this.container.removeChild( this.canvas );
}
};
// --------------------------------------------------------------------//
// ------------------------------- API --------------------------------//
// --------------------------------------------------------------------//
Reveal = {
initialize: initialize,
configure: configure,
sync: sync,
// Navigation methods
slide: slide,
left: navigateLeft,
right: navigateRight,
up: navigateUp,
down: navigateDown,
prev: navigatePrev,
next: navigateNext,
// Fragment methods
navigateFragment: navigateFragment,
prevFragment: previousFragment,
nextFragment: nextFragment,
// Deprecated aliases
navigateTo: slide,
navigateLeft: navigateLeft,
navigateRight: navigateRight,
navigateUp: navigateUp,
navigateDown: navigateDown,
navigatePrev: navigatePrev,
navigateNext: navigateNext,
// Forces an update in slide layout
layout: layout,
// Returns an object with the available routes as booleans (left/right/top/bottom)
availableRoutes: availableRoutes,
// Returns an object with the available fragments as booleans (prev/next)
availableFragments: availableFragments,
// Toggles the overview mode on/off
toggleOverview: toggleOverview,
// Toggles the "black screen" mode on/off
togglePause: togglePause,
// Toggles the auto slide mode on/off
toggleAutoSlide: toggleAutoSlide,
// State checks
isOverview: isOverview,
isPaused: isPaused,
isAutoSliding: isAutoSliding,
// Adds or removes all internal event listeners (such as keyboard)
addEventListeners: addEventListeners,
removeEventListeners: removeEventListeners,
// Facility for persisting and restoring the presentation state
getState: getState,
setState: setState,
// Presentation progress on range of 0-1
getProgress: getProgress,
// Returns the indices of the current, or specified, slide
getIndices: getIndices,
getTotalSlides: getTotalSlides,
// Returns the slide element at the specified index
getSlide: getSlide,
// Returns the slide background element at the specified index
getSlideBackground: getSlideBackground,
// Returns the previous slide element, may be null
getPreviousSlide: function() {
return previousSlide;
},
// Returns the current slide element
getCurrentSlide: function() {
return currentSlide;
},
// Returns the current scale of the presentation content
getScale: function() {
return scale;
},
// Returns the current configuration object
getConfig: function() {
return config;
},
// Helper method, retrieves query string as a key/value hash
getQueryHash: function() {
var query = {};
location.search.replace( /[A-Z0-9]+?=([\w\.%-]*)/gi, function(a) {
query[ a.split( '=' ).shift() ] = a.split( '=' ).pop();
} );
// Basic deserialization
for( var i in query ) {
var value = query[ i ];
query[ i ] = deserialize( unescape( value ) );
}
return query;
},
// Returns true if we're currently on the first slide
isFirstSlide: function() {
return ( indexh === 0 && indexv === 0 );
},
// Returns true if we're currently on the last slide
isLastSlide: function() {
if( currentSlide ) {
// Does this slide has next a sibling?
if( currentSlide.nextElementSibling ) return false;
// If it's vertical, does its parent have a next sibling?
if( isVerticalSlide( currentSlide ) && currentSlide.parentNode.nextElementSibling ) return false;
return true;
}
return false;
},
// Checks if reveal.js has been loaded and is ready for use
isReady: function() {
return loaded;
},
// Forward event binding to the reveal DOM element
addEventListener: function( type, listener, useCapture ) {
if( 'addEventListener' in window ) {
( dom.wrapper || document.querySelector( '.reveal' ) ).addEventListener( type, listener, useCapture );
}
},
removeEventListener: function( type, listener, useCapture ) {
if( 'addEventListener' in window ) {
( dom.wrapper || document.querySelector( '.reveal' ) ).removeEventListener( type, listener, useCapture );
}
},
// Programatically triggers a keyboard event
triggerKey: function( keyCode ) {
onDocumentKeyDown( { keyCode: keyCode } );
}
};
return Reveal;
}));
================================================
FILE: presentation/lib/css/zenburn.css
================================================
/*
Zenburn style from voldmar.ru (c) Vladimir Epifanov
based on dark.css by Ivan Sagalaev
*/
.hljs {
display: block; padding: 0.5em;
background: #3F3F3F;
color: #DCDCDC;
}
.hljs-keyword,
.hljs-tag,
.css .hljs-class,
.css .hljs-id,
.lisp .hljs-title,
.nginx .hljs-title,
.hljs-request,
.hljs-status,
.clojure .hljs-attribute {
color: #E3CEAB;
}
.django .hljs-template_tag,
.django .hljs-variable,
.django .hljs-filter .hljs-argument {
color: #DCDCDC;
}
.hljs-number,
.hljs-date {
color: #8CD0D3;
}
.dos .hljs-envvar,
.dos .hljs-stream,
.hljs-variable,
.apache .hljs-sqbracket {
color: #EFDCBC;
}
.dos .hljs-flow,
.diff .hljs-change,
.python .exception,
.python .hljs-built_in,
.hljs-literal,
.tex .hljs-special {
color: #EFEFAF;
}
.diff .hljs-chunk,
.hljs-subst {
color: #8F8F8F;
}
.dos .hljs-keyword,
.python .hljs-decorator,
.hljs-title,
.haskell .hljs-type,
.diff .hljs-header,
.ruby .hljs-class .hljs-parent,
.apache .hljs-tag,
.nginx .hljs-built_in,
.tex .hljs-command,
.hljs-prompt {
color: #efef8f;
}
.dos .hljs-winutils,
.ruby .hljs-symbol,
.ruby .hljs-symbol .hljs-string,
.ruby .hljs-string {
color: #DCA3A3;
}
.diff .hljs-deletion,
.hljs-string,
.hljs-tag .hljs-value,
.hljs-preprocessor,
.hljs-pragma,
.hljs-built_in,
.sql .hljs-aggregate,
.hljs-javadoc,
.smalltalk .hljs-class,
.smalltalk .hljs-localvars,
.smalltalk .hljs-array,
.css .hljs-rules .hljs-value,
.hljs-attr_selector,
.hljs-pseudo,
.apache .hljs-cbracket,
.tex .hljs-formula,
.coffeescript .hljs-attribute {
color: #CC9393;
}
.hljs-shebang,
.diff .hljs-addition,
.hljs-comment,
.java .hljs-annotation,
.hljs-template_comment,
.hljs-pi,
.hljs-doctype {
color: #7F9F7F;
}
.coffeescript .javascript,
.javascript .xml,
.tex .hljs-formula,
.xml .javascript,
.xml .vbscript,
.xml .css,
.xml .hljs-cdata {
opacity: 0.5;
}
================================================
FILE: presentation/lib/font/league-gothic/LICENSE
================================================
SIL Open Font License (OFL)
http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL
================================================
FILE: presentation/lib/font/league-gothic/league-gothic.css
================================================
@font-face {
font-family: 'League Gothic';
src: url('league-gothic.eot');
src: url('league-gothic.eot?#iefix') format('embedded-opentype'),
url('league-gothic.woff') format('woff'),
url('league-gothic.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
================================================
FILE: presentation/lib/font/source-sans-pro/LICENSE
================================================
SIL Open Font License
Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name ‘Source’. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
—————————————————————————————-
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
—————————————————————————————-
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
DEFINITIONS
“Font Software” refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
“Reserved Font Name” refers to any names specified as such after the copyright statement(s).
“Original Version” refers to the collection of Font Software components as distributed by the Copyright Holder(s).
“Modified Version” refers to any derivative made by adding to, deleting, or substituting—in part or in whole—any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
“Author” refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
================================================
FILE: presentation/lib/font/source-sans-pro/source-sans-pro.css
================================================
@font-face {
font-family: 'Source Sans Pro';
src: url('source-sans-pro-regular.eot');
src: url('source-sans-pro-regular.eot?#iefix') format('embedded-opentype'),
url('source-sans-pro-regular.woff') format('woff'),
url('source-sans-pro-regular.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Source Sans Pro';
src: url('source-sans-pro-italic.eot');
src: url('source-sans-pro-italic.eot?#iefix') format('embedded-opentype'),
url('source-sans-pro-italic.woff') format('woff'),
url('source-sans-pro-italic.ttf') format('truetype');
font-weight: normal;
font-style: italic;
}
@font-face {
font-family: 'Source Sans Pro';
src: url('source-sans-pro-semibold.eot');
src: url('source-sans-pro-semibold.eot?#iefix') format('embedded-opentype'),
url('source-sans-pro-semibold.woff') format('woff'),
url('source-sans-pro-semibold.ttf') format('truetype');
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: 'Source Sans Pro';
src: url('source-sans-pro-semibolditalic.eot');
src: url('source-sans-pro-semibolditalic.eot?#iefix') format('embedded-opentype'),
url('source-sans-pro-semibolditalic.woff') format('woff'),
url('source-sans-pro-semibolditalic.ttf') format('truetype');
font-weight: 600;
font-style: italic;
}
================================================
FILE: presentation/lib/js/classList.js
================================================
/*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/
if(typeof document!=="undefined"&&!("classList" in document.createElement("a"))){(function(j){var a="classList",f="prototype",m=(j.HTMLElement||j.Element)[f],b=Object,k=String[f].trim||function(){return this.replace(/^\s+|\s+$/g,"")},c=Array[f].indexOf||function(q){var p=0,o=this.length;for(;p/g,">");
}
// re-highlight when focus is lost (for edited code)
element.addEventListener( 'focusout', function( event ) {
hljs.highlightBlock( event.currentTarget );
}, false );
}
}
})();
// END CUSTOM REVEAL.JS INTEGRATION
// highlight.js v8.2 with support for all available languages
var hljs=new function(){function j(v){return v.replace(/&/gm,"&").replace(//gm,">")}function t(v){return v.nodeName.toLowerCase()}function h(w,x){var v=w&&w.exec(x);return v&&v.index==0}function r(w){var v=(w.className+" "+(w.parentNode?w.parentNode.className:"")).split(/\s+/);v=v.map(function(x){return x.replace(/^lang(uage)?-/,"")});return v.filter(function(x){return i(x)||/no(-?)highlight/.test(x)})[0]}function o(x,y){var v={};for(var w in x){v[w]=x[w]}if(y){for(var w in y){v[w]=y[w]}}return v}function u(x){var v=[];(function w(y,z){for(var A=y.firstChild;A;A=A.nextSibling){if(A.nodeType==3){z+=A.nodeValue.length}else{if(A.nodeType==1){v.push({event:"start",offset:z,node:A});z=w(A,z);if(!t(A).match(/br|hr|img|input/)){v.push({event:"stop",offset:z,node:A})}}}}return z})(x,0);return v}function q(w,y,C){var x=0;var F="";var z=[];function B(){if(!w.length||!y.length){return w.length?w:y}if(w[0].offset!=y[0].offset){return(w[0].offset"}function E(G){F+=""+t(G)+">"}function v(G){(G.event=="start"?A:E)(G.node)}while(w.length||y.length){var D=B();F+=j(C.substr(x,D[0].offset-x));x=D[0].offset;if(D==w){z.reverse().forEach(E);do{v(D.splice(0,1)[0]);D=B()}while(D==w&&D.length&&D[0].offset==x);z.reverse().forEach(A)}else{if(D[0].event=="start"){z.push(D[0].node)}else{z.pop()}v(D.splice(0,1)[0])}}return F+j(C.substr(x))}function m(y){function v(z){return(z&&z.source)||z}function w(A,z){return RegExp(v(A),"m"+(y.cI?"i":"")+(z?"g":""))}function x(D,C){if(D.compiled){return}D.compiled=true;D.k=D.k||D.bK;if(D.k){var z={};var E=function(G,F){if(y.cI){F=F.toLowerCase()}F.split(" ").forEach(function(H){var I=H.split("|");z[I[0]]=[G,I[1]?Number(I[1]):1]})};if(typeof D.k=="string"){E("keyword",D.k)}else{Object.keys(D.k).forEach(function(F){E(F,D.k[F])})}D.k=z}D.lR=w(D.l||/\b[A-Za-z0-9_]+\b/,true);if(C){if(D.bK){D.b="\\b("+D.bK.split(" ").join("|")+")\\b"}if(!D.b){D.b=/\B|\b/}D.bR=w(D.b);if(!D.e&&!D.eW){D.e=/\B|\b/}if(D.e){D.eR=w(D.e)}D.tE=v(D.e)||"";if(D.eW&&C.tE){D.tE+=(D.e?"|":"")+C.tE}}if(D.i){D.iR=w(D.i)}if(D.r===undefined){D.r=1}if(!D.c){D.c=[]}var B=[];D.c.forEach(function(F){if(F.v){F.v.forEach(function(G){B.push(o(F,G))})}else{B.push(F=="self"?D:F)}});D.c=B;D.c.forEach(function(F){x(F,D)});if(D.starts){x(D.starts,C)}var A=D.c.map(function(F){return F.bK?"\\.?("+F.b+")\\.?":F.b}).concat([D.tE,D.i]).map(v).filter(Boolean);D.t=A.length?w(A.join("|"),true):{exec:function(F){return null}}}x(y)}function c(T,L,J,R){function v(V,W){for(var U=0;U";V+=aa+'">';return V+Y+Z}function N(){if(!I.k){return j(C)}var U="";var X=0;I.lR.lastIndex=0;var V=I.lR.exec(C);while(V){U+=j(C.substr(X,V.index-X));var W=E(I,V);if(W){H+=W[1];U+=w(W[0],j(V[0]))}else{U+=j(V[0])}X=I.lR.lastIndex;V=I.lR.exec(C)}return U+j(C.substr(X))}function F(){if(I.sL&&!f[I.sL]){return j(C)}var U=I.sL?c(I.sL,C,true,S):e(C);if(I.r>0){H+=U.r}if(I.subLanguageMode=="continuous"){S=U.top}return w(U.language,U.value,false,true)}function Q(){return I.sL!==undefined?F():N()}function P(W,V){var U=W.cN?w(W.cN,"",true):"";if(W.rB){D+=U;C=""}else{if(W.eB){D+=j(V)+U;C=""}else{D+=U;C=V}}I=Object.create(W,{parent:{value:I}})}function G(U,Y){C+=U;if(Y===undefined){D+=Q();return 0}var W=v(Y,I);if(W){D+=Q();P(W,Y);return W.rB?0:Y.length}var X=z(I,Y);if(X){var V=I;if(!(V.rE||V.eE)){C+=Y}D+=Q();do{if(I.cN){D+=""}H+=I.r;I=I.parent}while(I!=X.parent);if(V.eE){D+=j(Y)}C="";if(X.starts){P(X.starts,"")}return V.rE?0:Y.length}if(A(Y,I)){throw new Error('Illegal lexeme "'+Y+'" for mode "'+(I.cN||"")+'"')}C+=Y;return Y.length||1}var M=i(T);if(!M){throw new Error('Unknown language: "'+T+'"')}m(M);var I=R||M;var S;var D="";for(var K=I;K!=M;K=K.parent){if(K.cN){D=w(K.cN,"",true)+D}}var C="";var H=0;try{var B,y,x=0;while(true){I.t.lastIndex=x;B=I.t.exec(L);if(!B){break}y=G(L.substr(x,B.index-x),B[0]);x=B.index+y}G(L.substr(x));for(var K=I;K.parent;K=K.parent){if(K.cN){D+=""}}return{r:H,value:D,language:T,top:I}}catch(O){if(O.message.indexOf("Illegal")!=-1){return{r:0,value:j(L)}}else{throw O}}}function e(y,x){x=x||b.languages||Object.keys(f);var v={r:0,value:j(y)};var w=v;x.forEach(function(z){if(!i(z)){return}var A=c(z,y,false);A.language=z;if(A.r>w.r){w=A}if(A.r>v.r){w=v;v=A}});if(w.language){v.second_best=w}return v}function g(v){if(b.tabReplace){v=v.replace(/^((<[^>]+>|\t)+)/gm,function(w,z,y,x){return z.replace(/\t/g,b.tabReplace)})}if(b.useBR){v=v.replace(/\n/g," ")}return v}function p(A){var B=r(A);if(/no(-?)highlight/.test(B)){return}var y;if(b.useBR){y=document.createElementNS("http://www.w3.org/1999/xhtml","div");y.innerHTML=A.innerHTML.replace(/\n/g,"").replace(/ /g,"\n")}else{y=A}var z=y.textContent;var v=B?c(B,z,true):e(z);var x=u(y);if(x.length){var w=document.createElementNS("http://www.w3.org/1999/xhtml","div");w.innerHTML=v.value;v.value=q(x,u(w),z)}v.value=g(v.value);A.innerHTML=v.value;A.className+=" hljs "+(!B&&v.language||"");A.result={language:v.language,re:v.r};if(v.second_best){A.second_best={language:v.second_best.language,re:v.second_best.r}}}var b={classPrefix:"hljs-",tabReplace:null,useBR:false,languages:undefined};function s(v){b=o(b,v)}function l(){if(l.called){return}l.called=true;var v=document.querySelectorAll("pre code");Array.prototype.forEach.call(v,p)}function a(){addEventListener("DOMContentLoaded",l,false);addEventListener("load",l,false)}var f={};var n={};function d(v,x){var w=f[v]=x(this);if(w.aliases){w.aliases.forEach(function(y){n[y]=v})}}function k(){return Object.keys(f)}function i(v){return f[v]||f[n[v]]}this.highlight=c;this.highlightAuto=e;this.fixMarkup=g;this.highlightBlock=p;this.configure=s;this.initHighlighting=l;this.initHighlightingOnLoad=a;this.registerLanguage=d;this.listLanguages=k;this.getLanguage=i;this.inherit=o;this.IR="[a-zA-Z][a-zA-Z0-9_]*";this.UIR="[a-zA-Z_][a-zA-Z0-9_]*";this.NR="\\b\\d+(\\.\\d+)?";this.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)";this.BNR="\\b(0b[01]+)";this.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";this.BE={b:"\\\\[\\s\\S]",r:0};this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE]};this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE]};this.PWM={b:/\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such)\b/};this.CLCM={cN:"comment",b:"//",e:"$",c:[this.PWM]};this.CBCM={cN:"comment",b:"/\\*",e:"\\*/",c:[this.PWM]};this.HCM={cN:"comment",b:"#",e:"$",c:[this.PWM]};this.NM={cN:"number",b:this.NR,r:0};this.CNM={cN:"number",b:this.CNR,r:0};this.BNM={cN:"number",b:this.BNR,r:0};this.CSSNM={cN:"number",b:this.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0};this.RM={cN:"regexp",b:/\//,e:/\/[gim]*/,i:/\n/,c:[this.BE,{b:/\[/,e:/\]/,r:0,c:[this.BE]}]};this.TM={cN:"title",b:this.IR,r:0};this.UTM={cN:"title",b:this.UIR,r:0}}();hljs.registerLanguage("bash",function(b){var a={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)\}/}]};var d={cN:"string",b:/"/,e:/"/,c:[b.BE,a,{cN:"variable",b:/\$\(/,e:/\)/,c:[b.BE]}]};var c={cN:"string",b:/'/,e:/'/};return{aliases:["sh","zsh"],l:/-?[a-z\.]+/,k:{keyword:"if then else elif fi for break continue while in do done exit return set declare case esac export exec",literal:"true false",built_in:"printf echo read cd pwd pushd popd dirs let eval unset typeset readonly getopts source shopt caller type hash bind help sudo",operator:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"shebang",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:true,c:[b.inherit(b.TM,{b:/\w[\w\d_]*/})],r:0},b.HCM,b.NM,d,c,a]}});hljs.registerLanguage("fix",function(a){return{c:[{b:/[^\u2401\u0001]+/,e:/[\u2401\u0001]/,eE:true,rB:true,rE:false,c:[{b:/([^\u2401\u0001=]+)/,e:/=([^\u2401\u0001=]+)/,rE:true,rB:false,cN:"attribute"},{b:/=/,e:/([\u2401\u0001])/,eE:true,eB:true,cN:"string"}]}],cI:true}});hljs.registerLanguage("nsis",function(a){var c={cN:"symbol",b:"\\$(ADMINTOOLS|APPDATA|CDBURN_AREA|CMDLINE|COMMONFILES32|COMMONFILES64|COMMONFILES|COOKIES|DESKTOP|DOCUMENTS|EXEDIR|EXEFILE|EXEPATH|FAVORITES|FONTS|HISTORY|HWNDPARENT|INSTDIR|INTERNET_CACHE|LANGUAGE|LOCALAPPDATA|MUSIC|NETHOOD|OUTDIR|PICTURES|PLUGINSDIR|PRINTHOOD|PROFILE|PROGRAMFILES32|PROGRAMFILES64|PROGRAMFILES|QUICKLAUNCH|RECENT|RESOURCES_LOCALIZED|RESOURCES|SENDTO|SMPROGRAMS|SMSTARTUP|STARTMENU|SYSDIR|TEMP|TEMPLATES|VIDEOS|WINDIR)"};var b={cN:"constant",b:"\\$+{[a-zA-Z0-9_]+}"};var f={cN:"variable",b:"\\$+[a-zA-Z0-9_]+",i:"\\(\\){}"};var e={cN:"constant",b:"\\$+\\([a-zA-Z0-9_]+\\)"};var g={cN:"params",b:"(ARCHIVE|FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_NORMAL|FILE_ATTRIBUTE_OFFLINE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_TEMPORARY|HKCR|HKCU|HKDD|HKEY_CLASSES_ROOT|HKEY_CURRENT_CONFIG|HKEY_CURRENT_USER|HKEY_DYN_DATA|HKEY_LOCAL_MACHINE|HKEY_PERFORMANCE_DATA|HKEY_USERS|HKLM|HKPD|HKU|IDABORT|IDCANCEL|IDIGNORE|IDNO|IDOK|IDRETRY|IDYES|MB_ABORTRETRYIGNORE|MB_DEFBUTTON1|MB_DEFBUTTON2|MB_DEFBUTTON3|MB_DEFBUTTON4|MB_ICONEXCLAMATION|MB_ICONINFORMATION|MB_ICONQUESTION|MB_ICONSTOP|MB_OK|MB_OKCANCEL|MB_RETRYCANCEL|MB_RIGHT|MB_RTLREADING|MB_SETFOREGROUND|MB_TOPMOST|MB_USERICON|MB_YESNO|NORMAL|OFFLINE|READONLY|SHCTX|SHELL_CONTEXT|SYSTEM|TEMPORARY)"};var d={cN:"constant",b:"\\!(addincludedir|addplugindir|appendfile|cd|define|delfile|echo|else|endif|error|execute|finalize|getdllversionsystem|ifdef|ifmacrodef|ifmacrondef|ifndef|if|include|insertmacro|macroend|macro|packhdr|searchparse|searchreplace|tempfile|undef|verbose|warning)"};return{cI:false,k:{keyword:"Abort AddBrandingImage AddSize AllowRootDirInstall AllowSkipFiles AutoCloseWindow BGFont BGGradient BrandingText BringToFront Call CallInstDLL Caption ChangeUI CheckBitmap ClearErrors CompletedText ComponentText CopyFiles CRCCheck CreateDirectory CreateFont CreateShortCut Delete DeleteINISec DeleteINIStr DeleteRegKey DeleteRegValue DetailPrint DetailsButtonText DirText DirVar DirVerify EnableWindow EnumRegKey EnumRegValue Exch Exec ExecShell ExecWait ExpandEnvStrings File FileBufSize FileClose FileErrorText FileOpen FileRead FileReadByte FileReadUTF16LE FileReadWord FileSeek FileWrite FileWriteByte FileWriteUTF16LE FileWriteWord FindClose FindFirst FindNext FindWindow FlushINI FunctionEnd GetCurInstType GetCurrentAddress GetDlgItem GetDLLVersion GetDLLVersionLocal GetErrorLevel GetFileTime GetFileTimeLocal GetFullPathName GetFunctionAddress GetInstDirError GetLabelAddress GetTempFileName Goto HideWindow Icon IfAbort IfErrors IfFileExists IfRebootFlag IfSilent InitPluginsDir InstallButtonText InstallColors InstallDir InstallDirRegKey InstProgressFlags InstType InstTypeGetText InstTypeSetText IntCmp IntCmpU IntFmt IntOp IsWindow LangString LicenseBkColor LicenseData LicenseForceSelection LicenseLangString LicenseText LoadLanguageFile LockWindow LogSet LogText ManifestDPIAware ManifestSupportedOS MessageBox MiscButtonText Name Nop OutFile Page PageCallbacks PageExEnd Pop Push Quit ReadEnvStr ReadINIStr ReadRegDWORD ReadRegStr Reboot RegDLL Rename RequestExecutionLevel ReserveFile Return RMDir SearchPath SectionEnd SectionGetFlags SectionGetInstTypes SectionGetSize SectionGetText SectionGroupEnd SectionIn SectionSetFlags SectionSetInstTypes SectionSetSize SectionSetText SendMessage SetAutoClose SetBrandingImage SetCompress SetCompressor SetCompressorDictSize SetCtlColors SetCurInstType SetDatablockOptimize SetDateSave SetDetailsPrint SetDetailsView SetErrorLevel SetErrors SetFileAttributes SetFont SetOutPath SetOverwrite SetPluginUnload SetRebootFlag SetRegView SetShellVarContext SetSilent ShowInstDetails ShowUninstDetails ShowWindow SilentInstall SilentUnInstall Sleep SpaceTexts StrCmp StrCmpS StrCpy StrLen SubCaption SubSectionEnd Unicode UninstallButtonText UninstallCaption UninstallIcon UninstallSubCaption UninstallText UninstPage UnRegDLL Var VIAddVersionKey VIFileVersion VIProductVersion WindowIcon WriteINIStr WriteRegBin WriteRegDWORD WriteRegExpandStr WriteRegStr WriteUninstaller XPStyle",literal:"admin all auto both colored current false force hide highest lastused leave listonly none normal notset off on open print show silent silentlog smooth textonly true user "},c:[a.HCM,a.CBCM,{cN:"string",b:'"',e:'"',i:"\\n",c:[{cN:"symbol",b:"\\$(\\\\(n|r|t)|\\$)"},c,b,f,e]},{cN:"comment",b:";",e:"$",r:0},{cN:"function",bK:"Function PageEx Section SectionGroup SubSection",e:"$"},d,b,f,e,g,a.NM,{cN:"literal",b:a.IR+"::"+a.IR}]}});hljs.registerLanguage("haxe",function(a){var c="[a-zA-Z_$][a-zA-Z0-9_$]*";var b="([*]|[a-zA-Z_$][a-zA-Z0-9_$]*)";return{aliases:["hx"],k:{keyword:"break callback case cast catch class continue default do dynamic else enum extends extern for function here if implements import in inline interface never new override package private public return static super switch this throw trace try typedef untyped using var while",literal:"true false null"},c:[a.ASM,a.QSM,a.CLCM,a.CBCM,a.CNM,{cN:"class",bK:"class interface",e:"{",eE:true,c:[{bK:"extends implements"},a.TM]},{cN:"preprocessor",b:"#",e:"$",k:"if else elseif end error"},{cN:"function",bK:"function",e:"[{;]",eE:true,i:"\\S",c:[a.TM,{cN:"params",b:"\\(",e:"\\)",c:[a.ASM,a.QSM,a.CLCM,a.CBCM]},{cN:"type",b:":",e:b,r:10}]}]}});hljs.registerLanguage("erlang",function(i){var c="[a-z'][a-zA-Z0-9_']*";var o="("+c+":"+c+"|"+c+")";var f={keyword:"after and andalso|10 band begin bnot bor bsl bzr bxor case catch cond div end fun if let not of orelse|10 query receive rem try when xor",literal:"false true"};var l={cN:"comment",b:"%",e:"$"};var e={cN:"number",b:"\\b(\\d+#[a-fA-F0-9]+|\\d+(\\.\\d+)?([eE][-+]?\\d+)?)",r:0};var g={b:"fun\\s+"+c+"/\\d+"};var n={b:o+"\\(",e:"\\)",rB:true,r:0,c:[{cN:"function_name",b:o,r:0},{b:"\\(",e:"\\)",eW:true,rE:true,r:0}]};var h={cN:"tuple",b:"{",e:"}",r:0};var a={cN:"variable",b:"\\b_([A-Z][A-Za-z0-9_]*)?",r:0};var m={cN:"variable",b:"[A-Z][a-zA-Z0-9_]*",r:0};var b={b:"#"+i.UIR,r:0,rB:true,c:[{cN:"record_name",b:"#"+i.UIR,r:0},{b:"{",e:"}",r:0}]};var k={bK:"fun receive if try case",e:"end",k:f};k.c=[l,g,i.inherit(i.ASM,{cN:""}),k,n,i.QSM,e,h,a,m,b];var j=[l,g,k,n,i.QSM,e,h,a,m,b];n.c[1].c=j;h.c=j;b.c[1].c=j;var d={cN:"params",b:"\\(",e:"\\)",c:j};return{aliases:["erl"],k:f,i:"(|\\*=|\\+=|-=|/\\*|\\*/|\\(\\*|\\*\\))",c:[{cN:"function",b:"^"+c+"\\s*\\(",e:"->",rB:true,i:"\\(|#|//|/\\*|\\\\|:|;",c:[d,i.inherit(i.TM,{b:c})],starts:{e:";|\\.",k:f,c:j}},l,{cN:"pp",b:"^-",e:"\\.",r:0,eE:true,rB:true,l:"-"+i.IR,k:"-module -record -undef -export -ifdef -ifndef -author -copyright -doc -vsn -import -include -include_lib -compile -define -else -endif -file -behaviour -behavior -spec",c:[d]},e,i.QSM,b,a,m,h,{b:/\.$/}]}});hljs.registerLanguage("cs",function(c){var b="abstract as base bool break byte case catch char checked const continue decimal default delegate do double else enum event explicit extern false finally fixed float for foreach goto if implicit in int interface internal is lock long new null object operator out override params private protected public readonly ref return sbyte sealed short sizeof stackalloc static string struct switch this throw true try typeof uint ulong unchecked unsafe ushort using virtual volatile void while async await protected public private internal ascending descending from get group into join let orderby partial select set value var where yield";var a=c.IR+"(<"+c.IR+">)?";return{aliases:["csharp"],k:b,i:/::/,c:[{cN:"comment",b:"///",e:"$",rB:true,c:[{cN:"xmlDocTag",v:[{b:"///",r:0},{b:""},{b:"?",e:">"}]}]},c.CLCM,c.CBCM,{cN:"preprocessor",b:"#",e:"$",k:"if else elif endif define undef warning error line region endregion pragma checksum"},{cN:"string",b:'@"',e:'"',c:[{b:'""'}]},c.ASM,c.QSM,c.CNM,{bK:"class namespace interface",e:/[{;=]/,i:/[^\s:]/,c:[c.TM,c.CLCM,c.CBCM]},{bK:"new",e:/\s/,r:0},{cN:"function",b:"("+a+"\\s+)+"+c.IR+"\\s*\\(",rB:true,e:/[{;=]/,eE:true,k:b,c:[{b:c.IR+"\\s*\\(",rB:true,c:[c.TM]},{cN:"params",b:/\(/,e:/\)/,k:b,c:[c.ASM,c.QSM,c.CNM,c.CBCM]},c.CLCM,c.CBCM]}]}});hljs.registerLanguage("protobuf",function(a){return{k:{keyword:"package import option optional required repeated group",built_in:"double float int32 int64 uint32 uint64 sint32 sint64 fixed32 fixed64 sfixed32 sfixed64 bool string bytes",literal:"true false"},c:[a.QSM,a.NM,a.CLCM,{cN:"class",bK:"message enum service",e:/\{/,i:/\n/,c:[a.inherit(a.TM,{starts:{eW:true,eE:true}})]},{cN:"function",bK:"rpc",e:/;/,eE:true,k:"rpc returns"},{cN:"constant",b:/^\s*[A-Z_]+/,e:/\s*=/,eE:true}]}});hljs.registerLanguage("vim",function(a){return{l:/[!#@\w]+/,k:{keyword:"N|0 P|0 X|0 a|0 ab abc abo al am an|0 ar arga argd arge argdo argg argl argu as au aug aun b|0 bN ba bad bd be bel bf bl bm bn bo bp br brea breaka breakd breakl bro bufdo buffers bun bw c|0 cN cNf ca cabc caddb cad caddf cal cat cb cc ccl cd ce cex cf cfir cgetb cgete cg changes chd che checkt cl cla clo cm cmapc cme cn cnew cnf cno cnorea cnoreme co col colo com comc comp con conf cope cp cpf cq cr cs cst cu cuna cunme cw d|0 delm deb debugg delc delf dif diffg diffo diffp diffpu diffs diffthis dig di dl dell dj dli do doautoa dp dr ds dsp e|0 ea ec echoe echoh echom echon el elsei em en endfo endf endt endw ene ex exe exi exu f|0 files filet fin fina fini fir fix fo foldc foldd folddoc foldo for fu g|0 go gr grepa gu gv ha h|0 helpf helpg helpt hi hid his i|0 ia iabc if ij il im imapc ime ino inorea inoreme int is isp iu iuna iunme j|0 ju k|0 keepa kee keepj lN lNf l|0 lad laddb laddf la lan lat lb lc lch lcl lcs le lefta let lex lf lfir lgetb lgete lg lgr lgrepa lh ll lla lli lmak lm lmapc lne lnew lnf ln loadk lo loc lockv lol lope lp lpf lr ls lt lu lua luad luaf lv lvimgrepa lw m|0 ma mak map mapc marks mat me menut mes mk mks mksp mkv mkvie mod mz mzf nbc nb nbs n|0 new nm nmapc nme nn nnoreme noa no noh norea noreme norm nu nun nunme ol o|0 om omapc ome on ono onoreme opt ou ounme ow p|0 profd prof pro promptr pc ped pe perld po popu pp pre prev ps pt ptN ptf ptj ptl ptn ptp ptr pts pu pw py3 python3 py3d py3f py pyd pyf q|0 quita qa r|0 rec red redi redr redraws reg res ret retu rew ri rightb rub rubyd rubyf rund ru rv s|0 sN san sa sal sav sb sbN sba sbf sbl sbm sbn sbp sbr scrip scripte scs se setf setg setl sf sfir sh sim sig sil sl sla sm smap smapc sme sn sni sno snor snoreme sor so spelld spe spelli spellr spellu spellw sp spr sre st sta startg startr star stopi stj sts sun sunm sunme sus sv sw sy synti sync t|0 tN tabN tabc tabdo tabe tabf tabfir tabl tabm tabnew tabn tabo tabp tabr tabs tab ta tags tc tcld tclf te tf th tj tl tm tn to tp tr try ts tu u|0 undoj undol una unh unl unlo unm unme uns up v|0 ve verb vert vim vimgrepa vi viu vie vm vmapc vme vne vn vnoreme vs vu vunme windo w|0 wN wa wh wi winc winp wn wp wq wqa ws wu wv x|0 xa xmapc xm xme xn xnoreme xu xunme y|0 z|0 ~ Next Print append abbreviate abclear aboveleft all amenu anoremenu args argadd argdelete argedit argglobal arglocal argument ascii autocmd augroup aunmenu buffer bNext ball badd bdelete behave belowright bfirst blast bmodified bnext botright bprevious brewind break breakadd breakdel breaklist browse bunload bwipeout change cNext cNfile cabbrev cabclear caddbuffer caddexpr caddfile call catch cbuffer cclose center cexpr cfile cfirst cgetbuffer cgetexpr cgetfile chdir checkpath checktime clist clast close cmap cmapclear cmenu cnext cnewer cnfile cnoremap cnoreabbrev cnoremenu copy colder colorscheme command comclear compiler continue confirm copen cprevious cpfile cquit crewind cscope cstag cunmap cunabbrev cunmenu cwindow delete delmarks debug debuggreedy delcommand delfunction diffupdate diffget diffoff diffpatch diffput diffsplit digraphs display deletel djump dlist doautocmd doautoall deletep drop dsearch dsplit edit earlier echo echoerr echohl echomsg else elseif emenu endif endfor endfunction endtry endwhile enew execute exit exusage file filetype find finally finish first fixdel fold foldclose folddoopen folddoclosed foldopen function global goto grep grepadd gui gvim hardcopy help helpfind helpgrep helptags highlight hide history insert iabbrev iabclear ijump ilist imap imapclear imenu inoremap inoreabbrev inoremenu intro isearch isplit iunmap iunabbrev iunmenu join jumps keepalt keepmarks keepjumps lNext lNfile list laddexpr laddbuffer laddfile last language later lbuffer lcd lchdir lclose lcscope left leftabove lexpr lfile lfirst lgetbuffer lgetexpr lgetfile lgrep lgrepadd lhelpgrep llast llist lmake lmap lmapclear lnext lnewer lnfile lnoremap loadkeymap loadview lockmarks lockvar lolder lopen lprevious lpfile lrewind ltag lunmap luado luafile lvimgrep lvimgrepadd lwindow move mark make mapclear match menu menutranslate messages mkexrc mksession mkspell mkvimrc mkview mode mzscheme mzfile nbclose nbkey nbsart next nmap nmapclear nmenu nnoremap nnoremenu noautocmd noremap nohlsearch noreabbrev noremenu normal number nunmap nunmenu oldfiles open omap omapclear omenu only onoremap onoremenu options ounmap ounmenu ownsyntax print profdel profile promptfind promptrepl pclose pedit perl perldo pop popup ppop preserve previous psearch ptag ptNext ptfirst ptjump ptlast ptnext ptprevious ptrewind ptselect put pwd py3do py3file python pydo pyfile quit quitall qall read recover redo redir redraw redrawstatus registers resize retab return rewind right rightbelow ruby rubydo rubyfile rundo runtime rviminfo substitute sNext sandbox sargument sall saveas sbuffer sbNext sball sbfirst sblast sbmodified sbnext sbprevious sbrewind scriptnames scriptencoding scscope set setfiletype setglobal setlocal sfind sfirst shell simalt sign silent sleep slast smagic smapclear smenu snext sniff snomagic snoremap snoremenu sort source spelldump spellgood spellinfo spellrepall spellundo spellwrong split sprevious srewind stop stag startgreplace startreplace startinsert stopinsert stjump stselect sunhide sunmap sunmenu suspend sview swapname syntax syntime syncbind tNext tabNext tabclose tabedit tabfind tabfirst tablast tabmove tabnext tabonly tabprevious tabrewind tag tcl tcldo tclfile tearoff tfirst throw tjump tlast tmenu tnext topleft tprevious trewind tselect tunmenu undo undojoin undolist unabbreviate unhide unlet unlockvar unmap unmenu unsilent update vglobal version verbose vertical vimgrep vimgrepadd visual viusage view vmap vmapclear vmenu vnew vnoremap vnoremenu vsplit vunmap vunmenu write wNext wall while winsize wincmd winpos wnext wprevious wqall wsverb wundo wviminfo xit xall xmapclear xmap xmenu xnoremap xnoremenu xunmap xunmenu yank",built_in:"abs acos add and append argc argidx argv asin atan atan2 browse browsedir bufexists buflisted bufloaded bufname bufnr bufwinnr byte2line byteidx call ceil changenr char2nr cindent clearmatches col complete complete_add complete_check confirm copy cos cosh count cscope_connection cursor deepcopy delete did_filetype diff_filler diff_hlID empty escape eval eventhandler executable exists exp expand extend feedkeys filereadable filewritable filter finddir findfile float2nr floor fmod fnameescape fnamemodify foldclosed foldclosedend foldlevel foldtext foldtextresult foreground function garbagecollect get getbufline getbufvar getchar getcharmod getcmdline getcmdpos getcmdtype getcwd getfontname getfperm getfsize getftime getftype getline getloclist getmatches getpid getpos getqflist getreg getregtype gettabvar gettabwinvar getwinposx getwinposy getwinvar glob globpath has has_key haslocaldir hasmapto histadd histdel histget histnr hlexists hlID hostname iconv indent index input inputdialog inputlist inputrestore inputsave inputsecret insert invert isdirectory islocked items join keys len libcall libcallnr line line2byte lispindent localtime log log10 luaeval map maparg mapcheck match matchadd matcharg matchdelete matchend matchlist matchstr max min mkdir mode mzeval nextnonblank nr2char or pathshorten pow prevnonblank printf pumvisible py3eval pyeval range readfile reltime reltimestr remote_expr remote_foreground remote_peek remote_read remote_send remove rename repeat resolve reverse round screenattr screenchar screencol screenrow search searchdecl searchpair searchpairpos searchpos server2client serverlist setbufvar setcmdpos setline setloclist setmatches setpos setqflist setreg settabvar settabwinvar setwinvar sha256 shellescape shiftwidth simplify sin sinh sort soundfold spellbadword spellsuggest split sqrt str2float str2nr strchars strdisplaywidth strftime stridx string strlen strpart strridx strtrans strwidth submatch substitute synconcealed synID synIDattr synIDtrans synstack system tabpagebuflist tabpagenr tabpagewinnr tagfiles taglist tan tanh tempname tolower toupper tr trunc type undofile undotree values virtcol visualmode wildmenumode winbufnr wincol winheight winline winnr winrestcmd winrestview winsaveview winwidth writefile xor"},i:/[{:]/,c:[a.NM,a.ASM,{cN:"string",b:/"((\\")|[^"\n])*("|\n)/},{cN:"variable",b:/[bwtglsav]:[\w\d_]*/},{cN:"function",bK:"function function!",e:"$",r:0,c:[a.TM,{cN:"params",b:"\\(",e:"\\)"}]}]}});hljs.registerLanguage("brainfuck",function(b){var a={cN:"literal",b:"[\\+\\-]",r:0};return{aliases:["bf"],c:[{cN:"comment",b:"[^\\[\\]\\.,\\+\\-<> \r\n]",rE:true,e:"[\\[\\]\\.,\\+\\-<> \r\n]",r:0},{cN:"title",b:"[\\[\\]]",r:0},{cN:"string",b:"[\\.,]",r:0},{b:/\+\+|\-\-/,rB:true,c:[a]},a]}});hljs.registerLanguage("ruby",function(f){var j="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?";var i="and false then defined module in return redo if BEGIN retry end for true self when next until do begin unless END rescue nil else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor";var b={cN:"yardoctag",b:"@[A-Za-z]+"};var c={cN:"value",b:"#<",e:">"};var k={cN:"comment",v:[{b:"#",e:"$",c:[b]},{b:"^\\=begin",e:"^\\=end",c:[b],r:10},{b:"^__END__",e:"\\n$"}]};var d={cN:"subst",b:"#\\{",e:"}",k:i};var e={cN:"string",c:[f.BE,d],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:"%[qw]?\\(",e:"\\)"},{b:"%[qw]?\\[",e:"\\]"},{b:"%[qw]?{",e:"}"},{b:"%[qw]?<",e:">"},{b:"%[qw]?/",e:"/"},{b:"%[qw]?%",e:"%"},{b:"%[qw]?-",e:"-"},{b:"%[qw]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/}]};var a={cN:"params",b:"\\(",e:"\\)",k:i};var h=[e,c,k,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[f.inherit(f.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{cN:"inheritance",b:"<\\s*",c:[{cN:"parent",b:"("+f.IR+"::)?"+f.IR}]},k]},{cN:"function",bK:"def",e:" |$|;",r:0,c:[f.inherit(f.TM,{b:j}),a,k]},{cN:"constant",b:"(::)?(\\b[A-Z]\\w*(::)?)+",r:0},{cN:"symbol",b:f.UIR+"(\\!|\\?)?:",r:0},{cN:"symbol",b:":",c:[e,{b:j}],r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{cN:"variable",b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{b:"("+f.RSR+")\\s*",c:[c,k,{cN:"regexp",c:[f.BE,d],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}],r:0}];d.c=h;a.c=h;var g=[{b:/^\s*=>/,cN:"status",starts:{e:"$",c:h}},{cN:"prompt",b:/^\S[^=>\n]*>+/,starts:{e:"$",c:h}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:i,c:[k].concat(g).concat(h)}});hljs.registerLanguage("nimrod",function(a){return{k:{keyword:"addr and as asm bind block break|0 case|0 cast const|0 continue|0 converter discard distinct|10 div do elif else|0 end|0 enum|0 except export finally for from generic if|0 import|0 in include|0 interface is isnot|10 iterator|10 let|0 macro method|10 mixin mod nil not notin|10 object|0 of or out proc|10 ptr raise ref|10 return shl shr static template|10 try|0 tuple type|0 using|0 var|0 when while|0 with without xor yield",literal:"shared guarded stdin stdout stderr result|10 true false"},c:[{cN:"decorator",b:/{\./,e:/\.}/,r:10},{cN:"string",b:/[a-zA-Z]\w*"/,e:/"/,c:[{b:/""/}]},{cN:"string",b:/([a-zA-Z]\w*)?"""/,e:/"""/},{cN:"string",b:/"/,e:/"/,i:/\n/,c:[{b:/\\./}]},{cN:"type",b:/\b[A-Z]\w+\b/,r:0},{cN:"type",b:/\b(int|int8|int16|int32|int64|uint|uint8|uint16|uint32|uint64|float|float32|float64|bool|char|string|cstring|pointer|expr|stmt|void|auto|any|range|array|openarray|varargs|seq|set|clong|culong|cchar|cschar|cshort|cint|csize|clonglong|cfloat|cdouble|clongdouble|cuchar|cushort|cuint|culonglong|cstringarray|semistatic)\b/},{cN:"number",b:/\b(0[xX][0-9a-fA-F][_0-9a-fA-F]*)('?[iIuU](8|16|32|64))?/,r:0},{cN:"number",b:/\b(0o[0-7][_0-7]*)('?[iIuUfF](8|16|32|64))?/,r:0},{cN:"number",b:/\b(0(b|B)[01][_01]*)('?[iIuUfF](8|16|32|64))?/,r:0},{cN:"number",b:/\b(\d[_\d]*)('?[iIuUfF](8|16|32|64))?/,r:0},a.HCM]}});hljs.registerLanguage("rust",function(a){return{aliases:["rs"],k:{keyword:"alignof as be box break const continue crate do else enum extern false fn for if impl in let loop match mod mut offsetof once priv proc pub pure ref return self sizeof static struct super trait true type typeof unsafe unsized use virtual while yield int i8 i16 i32 i64 uint u8 u32 u64 float f32 f64 str char bool",built_in:"assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! fail! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln!"},l:a.IR+"!?",i:"",c:[a.CLCM,a.CBCM,a.inherit(a.QSM,{i:null}),{cN:"string",b:/r(#*)".*?"\1(?!#)/},{cN:"string",b:/'\\?(x\w{2}|u\w{4}|U\w{8}|.)'/},{b:/'[a-zA-Z_][a-zA-Z0-9_]*/},{cN:"number",b:"\\b(0[xb][A-Za-z0-9_]+|[0-9_]+(\\.[0-9_]+)?([uif](8|16|32|64)?)?)",r:0},{cN:"function",bK:"fn",e:"(\\(|<)",eE:true,c:[a.UTM]},{cN:"preprocessor",b:"#\\[",e:"\\]"},{bK:"type",e:"(=|<)",c:[a.UTM],i:"\\S"},{bK:"trait enum",e:"({|<)",c:[a.UTM],i:"\\S"},{b:a.IR+"::"},{b:"->"}]}});hljs.registerLanguage("ruleslanguage",function(a){return{k:{keyword:"BILL_PERIOD BILL_START BILL_STOP RS_EFFECTIVE_START RS_EFFECTIVE_STOP RS_JURIS_CODE RS_OPCO_CODE INTDADDATTRIBUTE|5 INTDADDVMSG|5 INTDBLOCKOP|5 INTDBLOCKOPNA|5 INTDCLOSE|5 INTDCOUNT|5 INTDCOUNTSTATUSCODE|5 INTDCREATEMASK|5 INTDCREATEDAYMASK|5 INTDCREATEFACTORMASK|5 INTDCREATEHANDLE|5 INTDCREATEOVERRIDEDAYMASK|5 INTDCREATEOVERRIDEMASK|5 INTDCREATESTATUSCODEMASK|5 INTDCREATETOUPERIOD|5 INTDDELETE|5 INTDDIPTEST|5 INTDEXPORT|5 INTDGETERRORCODE|5 INTDGETERRORMESSAGE|5 INTDISEQUAL|5 INTDJOIN|5 INTDLOAD|5 INTDLOADACTUALCUT|5 INTDLOADDATES|5 INTDLOADHIST|5 INTDLOADLIST|5 INTDLOADLISTDATES|5 INTDLOADLISTENERGY|5 INTDLOADLISTHIST|5 INTDLOADRELATEDCHANNEL|5 INTDLOADSP|5 INTDLOADSTAGING|5 INTDLOADUOM|5 INTDLOADUOMDATES|5 INTDLOADUOMHIST|5 INTDLOADVERSION|5 INTDOPEN|5 INTDREADFIRST|5 INTDREADNEXT|5 INTDRECCOUNT|5 INTDRELEASE|5 INTDREPLACE|5 INTDROLLAVG|5 INTDROLLPEAK|5 INTDSCALAROP|5 INTDSCALE|5 INTDSETATTRIBUTE|5 INTDSETDSTPARTICIPANT|5 INTDSETSTRING|5 INTDSETVALUE|5 INTDSETVALUESTATUS|5 INTDSHIFTSTARTTIME|5 INTDSMOOTH|5 INTDSORT|5 INTDSPIKETEST|5 INTDSUBSET|5 INTDTOU|5 INTDTOURELEASE|5 INTDTOUVALUE|5 INTDUPDATESTATS|5 INTDVALUE|5 STDEV INTDDELETEEX|5 INTDLOADEXACTUAL|5 INTDLOADEXCUT|5 INTDLOADEXDATES|5 INTDLOADEX|5 INTDLOADEXRELATEDCHANNEL|5 INTDSAVEEX|5 MVLOAD|5 MVLOADACCT|5 MVLOADACCTDATES|5 MVLOADACCTHIST|5 MVLOADDATES|5 MVLOADHIST|5 MVLOADLIST|5 MVLOADLISTDATES|5 MVLOADLISTHIST|5 IF FOR NEXT DONE SELECT END CALL ABORT CLEAR CHANNEL FACTOR LIST NUMBER OVERRIDE SET WEEK DISTRIBUTIONNODE ELSE WHEN THEN OTHERWISE IENUM CSV INCLUDE LEAVE RIDER SAVE DELETE NOVALUE SECTION WARN SAVE_UPDATE DETERMINANT LABEL REPORT REVENUE EACH IN FROM TOTAL CHARGE BLOCK AND OR CSV_FILE RATE_CODE AUXILIARY_DEMAND UIDACCOUNT RS BILL_PERIOD_SELECT HOURS_PER_MONTH INTD_ERROR_STOP SEASON_SCHEDULE_NAME ACCOUNTFACTOR ARRAYUPPERBOUND CALLSTOREDPROC GETADOCONNECTION GETCONNECT GETDATASOURCE GETQUALIFIER GETUSERID HASVALUE LISTCOUNT LISTOP LISTUPDATE LISTVALUE PRORATEFACTOR RSPRORATE SETBINPATH SETDBMONITOR WQ_OPEN BILLINGHOURS DATE DATEFROMFLOAT DATETIMEFROMSTRING DATETIMETOSTRING DATETOFLOAT DAY DAYDIFF DAYNAME DBDATETIME HOUR MINUTE MONTH MONTHDIFF MONTHHOURS MONTHNAME ROUNDDATE SAMEWEEKDAYLASTYEAR SECOND WEEKDAY WEEKDIFF YEAR YEARDAY YEARSTR COMPSUM HISTCOUNT HISTMAX HISTMIN HISTMINNZ HISTVALUE MAXNRANGE MAXRANGE MINRANGE COMPIKVA COMPKVA COMPKVARFROMKQKW COMPLF IDATTR FLAG LF2KW LF2KWH MAXKW POWERFACTOR READING2USAGE AVGSEASON MAXSEASON MONTHLYMERGE SEASONVALUE SUMSEASON ACCTREADDATES ACCTTABLELOAD CONFIGADD CONFIGGET CREATEOBJECT CREATEREPORT EMAILCLIENT EXPBLKMDMUSAGE EXPMDMUSAGE EXPORT_USAGE FACTORINEFFECT GETUSERSPECIFIEDSTOP INEFFECT ISHOLIDAY RUNRATE SAVE_PROFILE SETREPORTTITLE USEREXIT WATFORRUNRATE TO TABLE ACOS ASIN ATAN ATAN2 BITAND CEIL COS COSECANT COSH COTANGENT DIVQUOT DIVREM EXP FABS FLOOR FMOD FREPM FREXPN LOG LOG10 MAX MAXN MIN MINNZ MODF POW ROUND ROUND2VALUE ROUNDINT SECANT SIN SINH SQROOT TAN TANH FLOAT2STRING FLOAT2STRINGNC INSTR LEFT LEN LTRIM MID RIGHT RTRIM STRING STRINGNC TOLOWER TOUPPER TRIM NUMDAYS READ_DATE STAGING",built_in:"IDENTIFIER OPTIONS XML_ELEMENT XML_OP XML_ELEMENT_OF DOMDOCCREATE DOMDOCLOADFILE DOMDOCLOADXML DOMDOCSAVEFILE DOMDOCGETROOT DOMDOCADDPI DOMNODEGETNAME DOMNODEGETTYPE DOMNODEGETVALUE DOMNODEGETCHILDCT DOMNODEGETFIRSTCHILD DOMNODEGETSIBLING DOMNODECREATECHILDELEMENT DOMNODESETATTRIBUTE DOMNODEGETCHILDELEMENTCT DOMNODEGETFIRSTCHILDELEMENT DOMNODEGETSIBLINGELEMENT DOMNODEGETATTRIBUTECT DOMNODEGETATTRIBUTEI DOMNODEGETATTRIBUTEBYNAME DOMNODEGETBYNAME"},c:[a.CLCM,a.CBCM,a.ASM,a.QSM,a.CNM,{cN:"array",b:"#[a-zA-Z .]+"}]}});hljs.registerLanguage("rib",function(a){return{k:"ArchiveRecord AreaLightSource Atmosphere Attribute AttributeBegin AttributeEnd Basis Begin Blobby Bound Clipping ClippingPlane Color ColorSamples ConcatTransform Cone CoordinateSystem CoordSysTransform CropWindow Curves Cylinder DepthOfField Detail DetailRange Disk Displacement Display End ErrorHandler Exposure Exterior Format FrameAspectRatio FrameBegin FrameEnd GeneralPolygon GeometricApproximation Geometry Hider Hyperboloid Identity Illuminate Imager Interior LightSource MakeCubeFaceEnvironment MakeLatLongEnvironment MakeShadow MakeTexture Matte MotionBegin MotionEnd NuPatch ObjectBegin ObjectEnd ObjectInstance Opacity Option Orientation Paraboloid Patch PatchMesh Perspective PixelFilter PixelSamples PixelVariance Points PointsGeneralPolygons PointsPolygons Polygon Procedural Projection Quantize ReadArchive RelativeDetail ReverseOrientation Rotate Scale ScreenWindow ShadingInterpolation ShadingRate Shutter Sides Skew SolidBegin SolidEnd Sphere SubdivisionMesh Surface TextureCoordinates Torus Transform TransformBegin TransformEnd TransformPoints Translate TrimCurve WorldBegin WorldEnd",i:"",c:[a.HCM,a.CNM,a.ASM,a.QSM]}});hljs.registerLanguage("diff",function(a){return{aliases:["patch"],c:[{cN:"chunk",r:10,v:[{b:/^\@\@ +\-\d+,\d+ +\+\d+,\d+ +\@\@$/},{b:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{b:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{cN:"header",v:[{b:/Index: /,e:/$/},{b:/=====/,e:/=====$/},{b:/^\-\-\-/,e:/$/},{b:/^\*{3} /,e:/$/},{b:/^\+\+\+/,e:/$/},{b:/\*{5}/,e:/\*{5}$/}]},{cN:"addition",b:"^\\+",e:"$"},{cN:"deletion",b:"^\\-",e:"$"},{cN:"change",b:"^\\!",e:"$"}]}});hljs.registerLanguage("markdown",function(a){return{aliases:["md","mkdown","mkd"],c:[{cN:"header",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"blockquote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"`.+?`"},{b:"^( {4}|\t)",e:"$",r:0}]},{cN:"horizontal_rule",b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].*?[\\)\\]]",rB:true,c:[{cN:"link_label",b:"\\[",e:"\\]",eB:true,rE:true,r:0},{cN:"link_url",b:"\\]\\(",e:"\\)",eB:true,eE:true},{cN:"link_reference",b:"\\]\\[",e:"\\]",eB:true,eE:true}],r:10},{b:"^\\[.+\\]:",rB:true,c:[{cN:"link_reference",b:"\\[",e:"\\]:",eB:true,eE:true,starts:{cN:"link_url",e:"$"}}]}]}});hljs.registerLanguage("dart",function(b){var d={cN:"subst",b:"\\$\\{",e:"}",k:"true false null this is new super"};var c={cN:"string",v:[{b:"r'''",e:"'''"},{b:'r"""',e:'"""'},{b:"r'",e:"'",i:"\\n"},{b:'r"',e:'"',i:"\\n"},{b:"'''",e:"'''",c:[b.BE,d]},{b:'"""',e:'"""',c:[b.BE,d]},{b:"'",e:"'",i:"\\n",c:[b.BE,d]},{b:'"',e:'"',i:"\\n",c:[b.BE,d]}]};d.c=[b.CNM,c];var a={keyword:"assert break case catch class const continue default do else enum extends false final finally for if in is new null rethrow return super switch this throw true try var void while with",literal:"abstract as dynamic export external factory get implements import library operator part set static typedef",built_in:"print Comparable DateTime Duration Function Iterable Iterator List Map Match Null Object Pattern RegExp Set Stopwatch String StringBuffer StringSink Symbol Type Uri bool double int num document window querySelector querySelectorAll Element ElementList"};return{k:a,c:[c,{cN:"dartdoc",b:"/\\*\\*",e:"\\*/",sL:"markdown",subLanguageMode:"continuous"},{cN:"dartdoc",b:"///",e:"$",sL:"markdown",subLanguageMode:"continuous"},b.CLCM,b.CBCM,{cN:"class",bK:"class interface",e:"{",eE:true,c:[{bK:"extends implements"},b.UTM]},b.CNM,{cN:"annotation",b:"@[A-Za-z]+"},{b:"=>"}]}});hljs.registerLanguage("haml",function(a){return{cI:true,c:[{cN:"doctype",b:"^!!!( (5|1\\.1|Strict|Frameset|Basic|Mobile|RDFa|XML\\b.*))?$",r:10},{cN:"comment",b:"^\\s*(!=#|=#|-#|/).*$",r:0},{b:"^\\s*(-|=|!=)(?!#)",starts:{e:"\\n",sL:"ruby"}},{cN:"tag",b:"^\\s*%",c:[{cN:"title",b:"\\w+"},{cN:"value",b:"[#\\.]\\w+"},{b:"{\\s*",e:"\\s*}",eE:true,c:[{b:":\\w+\\s*=>",e:",\\s+",rB:true,eW:true,c:[{cN:"symbol",b:":\\w+"},{cN:"string",b:'"',e:'"'},{cN:"string",b:"'",e:"'"},{b:"\\w+",r:0}]}]},{b:"\\(\\s*",e:"\\s*\\)",eE:true,c:[{b:"\\w+\\s*=",e:"\\s+",rB:true,eW:true,c:[{cN:"attribute",b:"\\w+",r:0},{cN:"string",b:'"',e:'"'},{cN:"string",b:"'",e:"'"},{b:"\\w+",r:0}]}]}]},{cN:"bullet",b:"^\\s*[=~]\\s*",r:0},{b:"#{",starts:{e:"}",sL:"ruby"}}]}});hljs.registerLanguage("javascript",function(a){return{aliases:["js"],k:{keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document"},c:[{cN:"pi",b:/^\s*('|")use strict('|")/,r:10},a.ASM,a.QSM,a.CLCM,a.CBCM,a.CNM,{b:"("+a.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[a.CLCM,a.CBCM,a.RM,{b:/,e:/>;/,r:0,sL:"xml"}],r:0},{cN:"function",bK:"function",e:/\{/,eE:true,c:[a.inherit(a.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,c:[a.CLCM,a.CBCM],i:/["'\(]/}],i:/\[|%/},{b:/\$[(.]/},{b:"\\."+a.IR,r:0}]}});hljs.registerLanguage("xml",function(a){var c="[A-Za-z0-9\\._:-]+";var d={b:/<\?(php)?(?!\w)/,e:/\?>/,sL:"php",subLanguageMode:"continuous"};var b={eW:true,i:/,r:0,c:[d,{cN:"attribute",b:c,r:0},{b:"=",r:0,c:[{cN:"value",v:[{b:/"/,e:/"/},{b:/'/,e:/'/},{b:/[^\s\/>]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xsl","plist"],cI:true,c:[{cN:"doctype",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},{cN:"comment",b:"",r:10},{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"",rE:true,sL:"css"}},{cN:"tag",b:"
================================================
FILE: presentation/plugin/markdown/example.md
================================================
# Markdown Demo
## External 1.1
Content 1.1
Note: This will only appear in the speaker notes window.
## External 1.2
Content 1.2
## External 2
Content 2.1
## External 3.1
Content 3.1
## External 3.2
Content 3.2
================================================
FILE: presentation/plugin/markdown/markdown.js
================================================
/**
* The reveal.js markdown plugin. Handles parsing of
* markdown inside of presentations as well as loading
* of external markdown documents.
*/
(function( root, factory ) {
if( typeof exports === 'object' ) {
module.exports = factory( require( './marked' ) );
}
else {
// Browser globals (root is window)
root.RevealMarkdown = factory( root.marked );
root.RevealMarkdown.initialize();
}
}( this, function( marked ) {
if( typeof marked === 'undefined' ) {
throw 'The reveal.js Markdown plugin requires marked to be loaded';
}
if( typeof hljs !== 'undefined' ) {
marked.setOptions({
highlight: function( lang, code ) {
return hljs.highlightAuto( lang, code ).value;
}
});
}
var DEFAULT_SLIDE_SEPARATOR = '^\n---\n$',
DEFAULT_NOTES_SEPARATOR = 'note:',
DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR = '\\\.element\\\s*?(.+?)$',
DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR = '\\\.slide:\\\s*?(\\\S.+?)$';
/**
* Retrieves the markdown contents of a slide section
* element. Normalizes leading tabs/whitespace.
*/
function getMarkdownFromSlide( section ) {
var template = section.querySelector( 'script' );
// strip leading whitespace so it isn't evaluated as code
var text = ( template || section ).textContent;
var leadingWs = text.match( /^\n?(\s*)/ )[1].length,
leadingTabs = text.match( /^\n?(\t*)/ )[1].length;
if( leadingTabs > 0 ) {
text = text.replace( new RegExp('\\n?\\t{' + leadingTabs + '}','g'), '\n' );
}
else if( leadingWs > 1 ) {
text = text.replace( new RegExp('\\n? {' + leadingWs + '}'), '\n' );
}
return text;
}
/**
* Given a markdown slide section element, this will
* return all arguments that aren't related to markdown
* parsing. Used to forward any other user-defined arguments
* to the output markdown slide.
*/
function getForwardedAttributes( section ) {
var attributes = section.attributes;
var result = [];
for( var i = 0, len = attributes.length; i < len; i++ ) {
var name = attributes[i].name,
value = attributes[i].value;
// disregard attributes that are used for markdown loading/parsing
if( /data\-(markdown|separator|vertical|notes)/gi.test( name ) ) continue;
if( value ) {
result.push( name + '=' + value );
}
else {
result.push( name );
}
}
return result.join( ' ' );
}
/**
* Inspects the given options and fills out default
* values for what's not defined.
*/
function getSlidifyOptions( options ) {
options = options || {};
options.separator = options.separator || DEFAULT_SLIDE_SEPARATOR;
options.notesSeparator = options.notesSeparator || DEFAULT_NOTES_SEPARATOR;
options.attributes = options.attributes || '';
return options;
}
/**
* Helper function for constructing a markdown slide.
*/
function createMarkdownSlide( content, options ) {
options = getSlidifyOptions( options );
var notesMatch = content.split( new RegExp( options.notesSeparator, 'mgi' ) );
if( notesMatch.length === 2 ) {
content = notesMatch[0] + '';
}
return '';
}
/**
* Parses a data string into multiple slides based
* on the passed in separator arguments.
*/
function slidify( markdown, options ) {
options = getSlidifyOptions( options );
var separatorRegex = new RegExp( options.separator + ( options.verticalSeparator ? '|' + options.verticalSeparator : '' ), 'mg' ),
horizontalSeparatorRegex = new RegExp( options.separator );
var matches,
lastIndex = 0,
isHorizontal,
wasHorizontal = true,
content,
sectionStack = [];
// iterate until all blocks between separators are stacked up
while( matches = separatorRegex.exec( markdown ) ) {
notes = null;
// determine direction (horizontal by default)
isHorizontal = horizontalSeparatorRegex.test( matches[0] );
if( !isHorizontal && wasHorizontal ) {
// create vertical stack
sectionStack.push( [] );
}
// pluck slide content from markdown input
content = markdown.substring( lastIndex, matches.index );
if( isHorizontal && wasHorizontal ) {
// add to horizontal stack
sectionStack.push( content );
}
else {
// add to vertical stack
sectionStack[sectionStack.length-1].push( content );
}
lastIndex = separatorRegex.lastIndex;
wasHorizontal = isHorizontal;
}
// add the remaining slide
( wasHorizontal ? sectionStack : sectionStack[sectionStack.length-1] ).push( markdown.substring( lastIndex ) );
var markdownSections = '';
// flatten the hierarchical stack, and insert tags
for( var i = 0, len = sectionStack.length; i < len; i++ ) {
// vertical
if( sectionStack[i] instanceof Array ) {
markdownSections += '';
sectionStack[i].forEach( function( child ) {
markdownSections += '' + createMarkdownSlide( child, options ) + '';
} );
markdownSections += '';
}
else {
markdownSections += '' + createMarkdownSlide( sectionStack[i], options ) + '';
}
}
return markdownSections;
}
/**
* Parses any current data-markdown slides, splits
* multi-slide markdown into separate sections and
* handles loading of external markdown.
*/
function processSlides() {
var sections = document.querySelectorAll( '[data-markdown]'),
section;
for( var i = 0, len = sections.length; i < len; i++ ) {
section = sections[i];
if( section.getAttribute( 'data-markdown' ).length ) {
var xhr = new XMLHttpRequest(),
url = section.getAttribute( 'data-markdown' );
datacharset = section.getAttribute( 'data-charset' );
// see https://developer.mozilla.org/en-US/docs/Web/API/element.getAttribute#Notes
if( datacharset != null && datacharset != '' ) {
xhr.overrideMimeType( 'text/html; charset=' + datacharset );
}
xhr.onreadystatechange = function() {
if( xhr.readyState === 4 ) {
// file protocol yields status code 0 (useful for local debug, mobile applications etc.)
if ( ( xhr.status >= 200 && xhr.status < 300 ) || xhr.status === 0 ) {
section.outerHTML = slidify( xhr.responseText, {
separator: section.getAttribute( 'data-separator' ),
verticalSeparator: section.getAttribute( 'data-separator-vertical' ),
notesSeparator: section.getAttribute( 'data-separator-notes' ),
attributes: getForwardedAttributes( section )
});
}
else {
section.outerHTML = '' +
'ERROR: The attempt to fetch ' + url + ' failed with HTTP status ' + xhr.status + '.' +
'Check your browser\'s JavaScript console for more details.' +
'
Remember that you need to serve the presentation HTML from a HTTP server.
' +
'';
}
}
};
xhr.open( 'GET', url, false );
try {
xhr.send();
}
catch ( e ) {
alert( 'Failed to get the Markdown file ' + url + '. Make sure that the presentation and the file are served by a HTTP server and the file can be found there. ' + e );
}
}
else if( section.getAttribute( 'data-separator' ) || section.getAttribute( 'data-separator-vertical' ) || section.getAttribute( 'data-separator-notes' ) ) {
section.outerHTML = slidify( getMarkdownFromSlide( section ), {
separator: section.getAttribute( 'data-separator' ),
verticalSeparator: section.getAttribute( 'data-separator-vertical' ),
notesSeparator: section.getAttribute( 'data-separator-notes' ),
attributes: getForwardedAttributes( section )
});
}
else {
section.innerHTML = createMarkdownSlide( getMarkdownFromSlide( section ) );
}
}
}
/**
* Check if a node value has the attributes pattern.
* If yes, extract it and add that value as one or several attributes
* the the terget element.
*
* You need Cache Killer on Chrome to see the effect on any FOM transformation
* directly on refresh (F5)
* http://stackoverflow.com/questions/5690269/disabling-chrome-cache-for-website-development/7000899#answer-11786277
*/
function addAttributeInElement( node, elementTarget, separator ) {
var mardownClassesInElementsRegex = new RegExp( separator, 'mg' );
var mardownClassRegex = new RegExp( "([^\"= ]+?)=\"([^\"=]+?)\"", 'mg' );
var nodeValue = node.nodeValue;
if( matches = mardownClassesInElementsRegex.exec( nodeValue ) ) {
var classes = matches[1];
nodeValue = nodeValue.substring( 0, matches.index ) + nodeValue.substring( mardownClassesInElementsRegex.lastIndex );
node.nodeValue = nodeValue;
while( matchesClass = mardownClassRegex.exec( classes ) ) {
elementTarget.setAttribute( matchesClass[1], matchesClass[2] );
}
return true;
}
return false;
}
/**
* Add attributes to the parent element of a text node,
* or the element of an attribute node.
*/
function addAttributes( section, element, previousElement, separatorElementAttributes, separatorSectionAttributes ) {
if ( element != null && element.childNodes != undefined && element.childNodes.length > 0 ) {
previousParentElement = element;
for( var i = 0; i < element.childNodes.length; i++ ) {
childElement = element.childNodes[i];
if ( i > 0 ) {
j = i - 1;
while ( j >= 0 ) {
aPreviousChildElement = element.childNodes[j];
if ( typeof aPreviousChildElement.setAttribute == 'function' && aPreviousChildElement.tagName != "BR" ) {
previousParentElement = aPreviousChildElement;
break;
}
j = j - 1;
}
}
parentSection = section;
if( childElement.nodeName == "section" ) {
parentSection = childElement ;
previousParentElement = childElement ;
}
if ( typeof childElement.setAttribute == 'function' || childElement.nodeType == Node.COMMENT_NODE ) {
addAttributes( parentSection, childElement, previousParentElement, separatorElementAttributes, separatorSectionAttributes );
}
}
}
if ( element.nodeType == Node.COMMENT_NODE ) {
if ( addAttributeInElement( element, previousElement, separatorElementAttributes ) == false ) {
addAttributeInElement( element, section, separatorSectionAttributes );
}
}
}
/**
* Converts any current data-markdown slides in the
* DOM to HTML.
*/
function convertSlides() {
var sections = document.querySelectorAll( '[data-markdown]');
for( var i = 0, len = sections.length; i < len; i++ ) {
var section = sections[i];
// Only parse the same slide once
if( !section.getAttribute( 'data-markdown-parsed' ) ) {
section.setAttribute( 'data-markdown-parsed', true )
var notes = section.querySelector( 'aside.notes' );
var markdown = getMarkdownFromSlide( section );
section.innerHTML = marked( markdown );
addAttributes( section, section, null, section.getAttribute( 'data-element-attributes' ) ||
section.parentNode.getAttribute( 'data-element-attributes' ) ||
DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR,
section.getAttribute( 'data-attributes' ) ||
section.parentNode.getAttribute( 'data-attributes' ) ||
DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR);
// If there were notes, we need to re-add them after
// having overwritten the section's HTML
if( notes ) {
section.appendChild( notes );
}
}
}
}
// API
return {
initialize: function() {
processSlides();
convertSlides();
},
// TODO: Do these belong in the API?
processSlides: processSlides,
convertSlides: convertSlides,
slidify: slidify
};
}));
================================================
FILE: presentation/plugin/markdown/marked.js
================================================
/**
* marked - a markdown parser
* Copyright (c) 2011-2013, Christopher Jeffrey. (MIT Licensed)
* https://github.com/chjj/marked
*/
(function(){var block={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:noop,hr:/^( *[-*_]){3,} *(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,nptable:noop,lheading:/^([^\n]+)\n *(=|-){3,} *\n*/,blockquote:/^( *>[^\n]+(\n[^\n]+)*\n*)+/,list:/^( *)(bull) [\s\S]+?(?:hr|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:/^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/,def:/^ *\[([^\]]+)\]: *([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,table:noop,paragraph:/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,
text:/^[^\n]+/};block.bullet=/(?:[*+-]|\d+\.)/;block.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;block.item=replace(block.item,"gm")(/bull/g,block.bullet)();block.list=replace(block.list)(/bull/g,block.bullet)("hr",/\n+(?=(?: *[-*_]){3,} *(?:\n+|$))/)();block._tag="(?!(?:"+"a|em|strong|small|s|cite|q|dfn|abbr|data|time|code"+"|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo"+"|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|@)\\b";block.html=replace(block.html)("comment",/\x3c!--[\s\S]*?--\x3e/)("closed",
/<(tag)[\s\S]+?<\/\1>/)("closing",/])*?>/)(/tag/g,block._tag)();block.paragraph=replace(block.paragraph)("hr",block.hr)("heading",block.heading)("lheading",block.lheading)("blockquote",block.blockquote)("tag","<"+block._tag)("def",block.def)();block.normal=merge({},block);block.gfm=merge({},block.normal,{fences:/^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n+|$)/,paragraph:/^/});block.gfm.paragraph=replace(block.paragraph)("(?!","(?!"+block.gfm.fences.source.replace("\\1",
"\\2")+"|")();block.tables=merge({},block.gfm,{nptable:/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,table:/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/});function Lexer(options){this.tokens=[];this.tokens.links={};this.options=options||marked.defaults;this.rules=block.normal;if(this.options.gfm)if(this.options.tables)this.rules=block.tables;else this.rules=block.gfm}Lexer.rules=block;Lexer.lex=function(src,options){var lexer=new Lexer(options);return lexer.lex(src)};
Lexer.prototype.lex=function(src){src=src.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n");return this.token(src,true)};Lexer.prototype.token=function(src,top){var src=src.replace(/^ +$/gm,""),next,loose,cap,bull,b,item,space,i,l;while(src){if(cap=this.rules.newline.exec(src)){src=src.substring(cap[0].length);if(cap[0].length>1)this.tokens.push({type:"space"})}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);cap=cap[0].replace(/^ {4}/gm,
"");this.tokens.push({type:"code",text:!this.options.pedantic?cap.replace(/\n+$/,""):cap});continue}if(cap=this.rules.fences.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"code",lang:cap[2],text:cap[3]});continue}if(cap=this.rules.heading.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"heading",depth:cap[1].length,text:cap[2]});continue}if(top&&(cap=this.rules.nptable.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,
"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/\n$/,"").split("\n")};for(i=0;i ?/gm,"");this.token(cap,top);this.tokens.push({type:"blockquote_end"});continue}if(cap=this.rules.list.exec(src)){src=src.substring(cap[0].length);
bull=cap[2];this.tokens.push({type:"list_start",ordered:bull.length>1});cap=cap[0].match(this.rules.item);next=false;l=cap.length;i=0;for(;i1&&b.length>1)){src=cap.slice(i+
1).join("\n")+src;i=l-1}}loose=next||/\n\n(?!\s*$)/.test(item);if(i!==l-1){next=item[item.length-1]==="\n";if(!loose)loose=next}this.tokens.push({type:loose?"loose_item_start":"list_item_start"});this.token(item,false);this.tokens.push({type:"list_item_end"})}this.tokens.push({type:"list_end"});continue}if(cap=this.rules.html.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:this.options.sanitize?"paragraph":"html",pre:cap[1]==="pre"||cap[1]==="script",text:cap[0]});continue}if(top&&
(cap=this.rules.def.exec(src))){src=src.substring(cap[0].length);this.tokens.links[cap[1].toLowerCase()]={href:cap[2],title:cap[3]};continue}if(top&&(cap=this.rules.table.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/(?: *\| *)?\n$/,"").split("\n")};for(i=0;i])/,autolink:/^<([^ >]+(@|:\/)[^ >]+)>/,url:noop,tag:/^\x3c!--[\s\S]*?--\x3e|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,link:/^!?\[(inside)\]\(href\)/,reflink:/^!?\[(inside)\]\s*\[([^\]]*)\]/,nolink:/^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,strong:/^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,em:/^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,
code:/^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,br:/^ {2,}\n(?!\s*$)/,del:noop,text:/^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/;inline.link=replace(inline.link)("inside",inline._inside)("href",inline._href)();inline.reflink=replace(inline.reflink)("inside",inline._inside)();inline.normal=merge({},inline);inline.pedantic=merge({},inline.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/});inline.gfm=merge({},inline.normal,{escape:replace(inline.escape)("])","~|])")(),url:/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,del:/^~~(?=\S)([\s\S]*?\S)~~/,text:replace(inline.text)("]|","~]|")("|","|https?://|")()});inline.breaks=merge({},inline.gfm,{br:replace(inline.br)("{2,}","*")(),text:replace(inline.gfm.text)("{2,}","*")()});function InlineLexer(links,options){this.options=options||marked.defaults;this.links=links;this.rules=inline.normal;
if(!this.links)throw new Error("Tokens array requires a `links` property.");if(this.options.gfm)if(this.options.breaks)this.rules=inline.breaks;else this.rules=inline.gfm;else if(this.options.pedantic)this.rules=inline.pedantic}InlineLexer.rules=inline;InlineLexer.output=function(src,links,options){var inline=new InlineLexer(links,options);return inline.output(src)};InlineLexer.prototype.output=function(src){var out="",link,text,href,cap;while(src){if(cap=this.rules.escape.exec(src)){src=src.substring(cap[0].length);
out+=cap[1];continue}if(cap=this.rules.autolink.exec(src)){src=src.substring(cap[0].length);if(cap[2]==="@"){text=cap[1][6]===":"?this.mangle(cap[1].substring(7)):this.mangle(cap[1]);href=this.mangle("mailto:")+text}else{text=escape(cap[1]);href=text}out+=''+text+"";continue}if(cap=this.rules.url.exec(src)){src=src.substring(cap[0].length);text=escape(cap[1]);href=text;out+=''+text+"";continue}if(cap=this.rules.tag.exec(src)){src=src.substring(cap[0].length);
out+=this.options.sanitize?escape(cap[0]):cap[0];continue}if(cap=this.rules.link.exec(src)){src=src.substring(cap[0].length);out+=this.outputLink(cap,{href:cap[2],title:cap[3]});continue}if((cap=this.rules.reflink.exec(src))||(cap=this.rules.nolink.exec(src))){src=src.substring(cap[0].length);link=(cap[2]||cap[1]).replace(/\s+/g," ");link=this.links[link.toLowerCase()];if(!link||!link.href){out+=cap[0][0];src=cap[0].substring(1)+src;continue}out+=this.outputLink(cap,link);continue}if(cap=this.rules.strong.exec(src)){src=
src.substring(cap[0].length);out+=""+this.output(cap[2]||cap[1])+"";continue}if(cap=this.rules.em.exec(src)){src=src.substring(cap[0].length);out+=""+this.output(cap[2]||cap[1])+"";continue}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);out+=""+escape(cap[2],true)+"";continue}if(cap=this.rules.br.exec(src)){src=src.substring(cap[0].length);out+=" ";continue}if(cap=this.rules.del.exec(src)){src=src.substring(cap[0].length);out+=""+
this.output(cap[1])+"";continue}if(cap=this.rules.text.exec(src)){src=src.substring(cap[0].length);out+=escape(cap[0]);continue}if(src)throw new Error("Infinite loop on byte: "+src.charCodeAt(0));}return out};InlineLexer.prototype.outputLink=function(cap,link){if(cap[0][0]!=="!")return'"+this.output(cap[1])+"";else return'"};InlineLexer.prototype.smartypants=function(text){if(!this.options.smartypants)return text;return text.replace(/--/g,"\u2014").replace(/'([^']*)'/g,"\u2018$1\u2019").replace(/"([^"]*)"/g,"\u201c$1\u201d").replace(/\.{3}/g,"\u2026")};InlineLexer.prototype.mangle=function(text){var out="",l=text.length,i=0,ch;for(;i0.5)ch="x"+ch.toString(16);out+=""+ch+";"}return out};function Parser(options){this.tokens=[];this.token=null;
this.options=options||marked.defaults}Parser.parse=function(src,options){var parser=new Parser(options);return parser.parse(src)};Parser.prototype.parse=function(src){this.inline=new InlineLexer(src.links,this.options);this.tokens=src.reverse();var out="";while(this.next())out+=this.tok();return out};Parser.prototype.next=function(){return this.token=this.tokens.pop()};Parser.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0};Parser.prototype.parseText=function(){var body=this.token.text;
while(this.peek().type==="text")body+="\n"+this.next().text;return this.inline.output(body)};Parser.prototype.tok=function(){switch(this.token.type){case "space":return"";case "hr":return"\n";case "heading":return""+this.inline.output(this.token.text)+"\n";case "code":if(this.options.highlight){var code=this.options.highlight(this.token.text,this.token.lang);if(code!=null&&code!==this.token.text){this.token.escaped=true;this.token.text=code}}if(!this.token.escaped)this.token.text=
escape(this.token.text,true);return"
\n"}};function escape(html,encode){return html.replace(!encode?/&(?!#?\w+;)/g:/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function replace(regex,opt){regex=regex.source;opt=opt||"";return function self(name,val){if(!name)return new RegExp(regex,opt);val=val.source||val;val=val.replace(/(^|[^\[])\^/g,"$1");regex=regex.replace(name,val);return self}}function noop(){}noop.exec=noop;function merge(obj){var i=
1,target,key;for(;iAn error occured:
"+escape(e.message+"",true)+"
";throw e;}}marked.options=marked.setOptions=function(opt){merge(marked.defaults,opt);return marked};marked.defaults={gfm:true,tables:true,breaks:false,pedantic:false,sanitize:false,smartLists:false,silent:false,highlight:null,langPrefix:""};marked.Parser=Parser;marked.parser=Parser.parse;marked.Lexer=Lexer;marked.lexer=Lexer.lex;marked.InlineLexer=InlineLexer;marked.inlineLexer=InlineLexer.output;
marked.parse=marked;if(typeof exports==="object")module.exports=marked;else if(typeof define==="function"&&define.amd)define(function(){return marked});else this.marked=marked}).call(function(){return this||(typeof window!=="undefined"?window:global)}());
================================================
FILE: presentation/plugin/math/math.js
================================================
/**
* A plugin which enables rendering of math equations inside
* of reveal.js slides. Essentially a thin wrapper for MathJax.
*
* @author Hakim El Hattab
*/
var RevealMath = window.RevealMath || (function(){
var options = Reveal.getConfig().math || {};
options.mathjax = options.mathjax || 'http://cdn.mathjax.org/mathjax/latest/MathJax.js';
options.config = options.config || 'TeX-AMS_HTML-full';
loadScript( options.mathjax + '?config=' + options.config, function() {
MathJax.Hub.Config({
messageStyle: 'none',
tex2jax: { inlineMath: [['$','$'],['\\(','\\)']] },
skipStartupTypeset: true
});
// Typeset followed by an immediate reveal.js layout since
// the typesetting process could affect slide height
MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub ] );
MathJax.Hub.Queue( Reveal.layout );
// Reprocess equations in slides when they turn visible
Reveal.addEventListener( 'slidechanged', function( event ) {
MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub, event.currentSlide ] );
} );
} );
function loadScript( url, callback ) {
var head = document.querySelector( 'head' );
var script = document.createElement( 'script' );
script.type = 'text/javascript';
script.src = url;
// Wrapper for callback to make sure it only fires once
var finish = function() {
if( typeof callback === 'function' ) {
callback.call();
callback = null;
}
}
script.onload = finish;
// IE
script.onreadystatechange = function() {
if ( this.readyState === 'loaded' ) {
finish();
}
}
// Normal browsers
head.appendChild( script );
}
})();
================================================
FILE: presentation/plugin/multiplex/client.js
================================================
(function() {
var multiplex = Reveal.getConfig().multiplex;
var socketId = multiplex.id;
var socket = io.connect(multiplex.url);
socket.on(multiplex.id, function(data) {
// ignore data from sockets that aren't ours
if (data.socketId !== socketId) { return; }
if( window.location.host === 'localhost:1947' ) return;
Reveal.slide(data.indexh, data.indexv, data.indexf, 'remote');
});
}());
================================================
FILE: presentation/plugin/multiplex/index.js
================================================
var express = require('express');
var fs = require('fs');
var io = require('socket.io');
var crypto = require('crypto');
var app = express.createServer();
var staticDir = express.static;
io = io.listen(app);
var opts = {
port: 1948,
baseDir : __dirname + '/../../'
};
io.sockets.on('connection', function(socket) {
socket.on('slidechanged', function(slideData) {
if (typeof slideData.secret == 'undefined' || slideData.secret == null || slideData.secret === '') return;
if (createHash(slideData.secret) === slideData.socketId) {
slideData.secret = null;
socket.broadcast.emit(slideData.socketId, slideData);
};
});
});
app.configure(function() {
[ 'css', 'js', 'plugin', 'lib' ].forEach(function(dir) {
app.use('/' + dir, staticDir(opts.baseDir + dir));
});
});
app.get("/", function(req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
fs.createReadStream(opts.baseDir + '/index.html').pipe(res);
});
app.get("/token", function(req,res) {
var ts = new Date().getTime();
var rand = Math.floor(Math.random()*9999999);
var secret = ts.toString() + rand.toString();
res.send({secret: secret, socketId: createHash(secret)});
});
var createHash = function(secret) {
var cipher = crypto.createCipher('blowfish', secret);
return(cipher.final('hex'));
};
// Actually listen
app.listen(opts.port || null);
var brown = '\033[33m',
green = '\033[32m',
reset = '\033[0m';
console.log( brown + "reveal.js:" + reset + " Multiplex running on port " + green + opts.port + reset );
================================================
FILE: presentation/plugin/multiplex/master.js
================================================
(function() {
// Don't emit events from inside of notes windows
if ( window.location.search.match( /receiver/gi ) ) { return; }
var multiplex = Reveal.getConfig().multiplex;
var socket = io.connect(multiplex.url);
var notify = function( slideElement, indexh, indexv, origin ) {
if( typeof origin === 'undefined' && origin !== 'remote' ) {
var nextindexh;
var nextindexv;
var fragmentindex = Reveal.getIndices().f;
if (typeof fragmentindex == 'undefined') {
fragmentindex = 0;
}
if (slideElement.nextElementSibling && slideElement.parentNode.nodeName == 'SECTION') {
nextindexh = indexh;
nextindexv = indexv + 1;
} else {
nextindexh = indexh + 1;
nextindexv = 0;
}
var slideData = {
indexh : indexh,
indexv : indexv,
indexf : fragmentindex,
nextindexh : nextindexh,
nextindexv : nextindexv,
secret: multiplex.secret,
socketId : multiplex.id
};
socket.emit('slidechanged', slideData);
}
}
Reveal.addEventListener( 'slidechanged', function( event ) {
notify( event.currentSlide, event.indexh, event.indexv, event.origin );
} );
var fragmentNotify = function( event ) {
notify( Reveal.getCurrentSlide(), Reveal.getIndices().h, Reveal.getIndices().v, event.origin );
};
Reveal.addEventListener( 'fragmentshown', fragmentNotify );
Reveal.addEventListener( 'fragmenthidden', fragmentNotify );
}());
================================================
FILE: presentation/plugin/notes/notes.html
================================================
reveal.js - Slide Notes
UPCOMING:
Time Click to Reset
0:00 AM
00:00:00
Notes
================================================
FILE: presentation/plugin/notes/notes.js
================================================
/**
* Handles opening of and synchronization with the reveal.js
* notes window.
*
* Handshake process:
* 1. This window posts 'connect' to notes window
* - Includes URL of presentation to show
* 2. Notes window responds with 'connected' when it is available
* 3. This window proceeds to send the current presentation state
* to the notes window
*/
var RevealNotes = (function() {
function openNotes() {
var jsFileLocation = document.querySelector('script[src$="notes.js"]').src; // this js file path
jsFileLocation = jsFileLocation.replace(/notes\.js(\?.*)?$/, ''); // the js folder path
var notesPopup = window.open( jsFileLocation + 'notes.html', 'reveal.js - Notes', 'width=1100,height=700' );
/**
* Connect to the notes window through a postmessage handshake.
* Using postmessage enables us to work in situations where the
* origins differ, such as a presentation being opened from the
* file system.
*/
function connect() {
// Keep trying to connect until we get a 'connected' message back
var connectInterval = setInterval( function() {
notesPopup.postMessage( JSON.stringify( {
namespace: 'reveal-notes',
type: 'connect',
url: window.location.protocol + '//' + window.location.host + window.location.pathname,
state: Reveal.getState()
} ), '*' );
}, 500 );
window.addEventListener( 'message', function( event ) {
var data = JSON.parse( event.data );
if( data && data.namespace === 'reveal-notes' && data.type === 'connected' ) {
clearInterval( connectInterval );
onConnected();
}
} );
}
/**
* Posts the current slide data to the notes window
*/
function post() {
var slideElement = Reveal.getCurrentSlide(),
notesElement = slideElement.querySelector( 'aside.notes' );
var messageData = {
namespace: 'reveal-notes',
type: 'state',
notes: '',
markdown: false,
state: Reveal.getState()
};
// Look for notes defined in a slide attribute
if( slideElement.hasAttribute( 'data-notes' ) ) {
messageData.notes = slideElement.getAttribute( 'data-notes' );
}
// Look for notes defined in an aside element
if( notesElement ) {
messageData.notes = notesElement.innerHTML;
messageData.markdown = typeof notesElement.getAttribute( 'data-markdown' ) === 'string';
}
notesPopup.postMessage( JSON.stringify( messageData ), '*' );
}
/**
* Called once we have established a connection to the notes
* window.
*/
function onConnected() {
// Monitor events that trigger a change in state
Reveal.addEventListener( 'slidechanged', post );
Reveal.addEventListener( 'fragmentshown', post );
Reveal.addEventListener( 'fragmenthidden', post );
Reveal.addEventListener( 'overviewhidden', post );
Reveal.addEventListener( 'overviewshown', post );
Reveal.addEventListener( 'paused', post );
Reveal.addEventListener( 'resumed', post );
// Post the initial state
post();
}
connect();
}
if( !/receiver/i.test( window.location.search ) ) {
// If the there's a 'notes' query set, open directly
if( window.location.search.match( /(\?|\&)notes/gi ) !== null ) {
openNotes();
}
// Open the notes when the 's' key is hit
document.addEventListener( 'keydown', function( event ) {
// Disregard the event if the target is editable or a
// modifier is present
if ( document.querySelector( ':focus' ) !== null || event.shiftKey || event.altKey || event.ctrlKey || event.metaKey ) return;
if( event.keyCode === 83 ) {
event.preventDefault();
openNotes();
}
}, false );
}
return { open: openNotes };
})();
================================================
FILE: presentation/plugin/notes-server/client.js
================================================
(function() {
// don't emit events from inside the previews themselves
if( window.location.search.match( /receiver/gi ) ) { return; }
var socket = io.connect( window.location.origin ),
socketId = Math.random().toString().slice( 2 );
console.log( 'View slide notes at ' + window.location.origin + '/notes/' + socketId );
window.open( window.location.origin + '/notes/' + socketId, 'notes-' + socketId );
/**
* Posts the current slide data to the notes window
*/
function post() {
var slideElement = Reveal.getCurrentSlide(),
notesElement = slideElement.querySelector( 'aside.notes' );
var messageData = {
notes: '',
markdown: false,
socketId: socketId,
state: Reveal.getState()
};
// Look for notes defined in a slide attribute
if( slideElement.hasAttribute( 'data-notes' ) ) {
messageData.notes = slideElement.getAttribute( 'data-notes' );
}
// Look for notes defined in an aside element
if( notesElement ) {
messageData.notes = notesElement.innerHTML;
messageData.markdown = typeof notesElement.getAttribute( 'data-markdown' ) === 'string';
}
socket.emit( 'statechanged', messageData );
}
// When a new notes window connects, post our current state
socket.on( 'connect', function( data ) {
post();
} );
// Monitor events that trigger a change in state
Reveal.addEventListener( 'slidechanged', post );
Reveal.addEventListener( 'fragmentshown', post );
Reveal.addEventListener( 'fragmenthidden', post );
Reveal.addEventListener( 'overviewhidden', post );
Reveal.addEventListener( 'overviewshown', post );
Reveal.addEventListener( 'paused', post );
Reveal.addEventListener( 'resumed', post );
// Post the initial state
post();
}());
================================================
FILE: presentation/plugin/notes-server/index.js
================================================
var express = require('express');
var fs = require('fs');
var io = require('socket.io');
var _ = require('underscore');
var Mustache = require('mustache');
var app = express.createServer();
var staticDir = express.static;
io = io.listen(app);
var opts = {
port : 1947,
baseDir : __dirname + '/../../'
};
io.sockets.on( 'connection', function( socket ) {
socket.on( 'connect', function( data ) {
socket.broadcast.emit( 'connect', data );
});
socket.on( 'statechanged', function( data ) {
socket.broadcast.emit( 'statechanged', data );
});
});
app.configure( function() {
[ 'css', 'js', 'images', 'plugin', 'lib' ].forEach( function( dir ) {
app.use( '/' + dir, staticDir( opts.baseDir + dir ) );
});
});
app.get('/', function( req, res ) {
res.writeHead( 200, { 'Content-Type': 'text/html' } );
fs.createReadStream( opts.baseDir + '/index.html' ).pipe( res );
});
app.get( '/notes/:socketId', function( req, res ) {
fs.readFile( opts.baseDir + 'plugin/notes-server/notes.html', function( err, data ) {
res.send( Mustache.to_html( data.toString(), {
socketId : req.params.socketId
}));
});
});
// Actually listen
app.listen( opts.port || null );
var brown = '\033[33m',
green = '\033[32m',
reset = '\033[0m';
var slidesLocation = 'http://localhost' + ( opts.port ? ( ':' + opts.port ) : '' );
console.log( brown + 'reveal.js - Speaker Notes' + reset );
console.log( '1. Open the slides at ' + green + slidesLocation + reset );
console.log( '2. Click on the link your JS console to go to the notes page' );
console.log( '3. Advance through your slides and your notes will advance automatically' );
================================================
FILE: presentation/plugin/notes-server/notes.html
================================================
reveal.js - Slide Notes
UPCOMING:
Time Click to Reset
0:00 AM
00:00:00
Notes
================================================
FILE: presentation/plugin/print-pdf/print-pdf.js
================================================
/**
* phantomjs script for printing presentations to PDF.
*
* Example:
* phantomjs print-pdf.js "http://lab.hakim.se/reveal-js?print-pdf" reveal-demo.pdf
*
* By Manuel Bieh (https://github.com/manuelbieh)
*/
// html2pdf.js
var page = new WebPage();
var system = require( 'system' );
var slideWidth = system.args[3] ? system.args[3].split( 'x' )[0] : 960;
var slideHeight = system.args[3] ? system.args[3].split( 'x' )[1] : 700;
page.viewportSize = {
width: slideWidth,
height: slideHeight
};
// TODO
// Something is wrong with these config values. An input
// paper width of 1920px actually results in a 756px wide
// PDF.
page.paperSize = {
width: Math.round( slideWidth * 2 ),
height: Math.round( slideHeight * 2 ),
border: 0
};
var inputFile = system.args[1] || 'index.html?print-pdf';
var outputFile = system.args[2] || 'slides.pdf';
if( outputFile.match( /\.pdf$/gi ) === null ) {
outputFile += '.pdf';
}
console.log( 'Printing PDF (Paper size: '+ page.paperSize.width + 'x' + page.paperSize.height +')' );
page.open( inputFile, function( status ) {
window.setTimeout( function() {
console.log( 'Printed succesfully' );
page.render( outputFile );
phantom.exit();
}, 1000 );
} );
================================================
FILE: presentation/plugin/remotes/remotes.js
================================================
/**
* Touch-based remote controller for your presentation courtesy
* of the folks at http://remotes.io
*/
(function(window){
/**
* Detects if we are dealing with a touch enabled device (with some false positives)
* Borrowed from modernizr: https://github.com/Modernizr/Modernizr/blob/master/feature-detects/touch.js
*/
var hasTouch = (function(){
return ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
})();
/**
* Detects if notes are enable and the current page is opened inside an /iframe
* this prevents loading Remotes.io several times
*/
var isNotesAndIframe = (function(){
return window.RevealNotes && !(self == top);
})();
if(!hasTouch && !isNotesAndIframe){
head.ready( 'remotes.ne.min.js', function() {
new Remotes("preview")
.on("swipe-left", function(e){ Reveal.right(); })
.on("swipe-right", function(e){ Reveal.left(); })
.on("swipe-up", function(e){ Reveal.down(); })
.on("swipe-down", function(e){ Reveal.up(); })
.on("tap", function(e){ Reveal.next(); })
.on("zoom-out", function(e){ Reveal.toggleOverview(true); })
.on("zoom-in", function(e){ Reveal.toggleOverview(false); })
;
} );
head.js('https://hakim-static.s3.amazonaws.com/reveal-js/remotes.ne.min.js');
}
})(window);
================================================
FILE: presentation/plugin/search/search.js
================================================
/*
* Handles finding a text string anywhere in the slides and showing the next occurrence to the user
* by navigatating to that slide and highlighting it.
*
* By Jon Snyder , February 2013
*/
var RevealSearch = (function() {
var matchedSlides;
var currentMatchedIndex;
var searchboxDirty;
var myHilitor;
// Original JavaScript code by Chirp Internet: www.chirp.com.au
// Please acknowledge use of this code by including this header.
// 2/2013 jon: modified regex to display any match, not restricted to word boundaries.
function Hilitor(id, tag)
{
var targetNode = document.getElementById(id) || document.body;
var hiliteTag = tag || "EM";
var skipTags = new RegExp("^(?:" + hiliteTag + "|SCRIPT|FORM|SPAN)$");
var colors = ["#ff6", "#a0ffff", "#9f9", "#f99", "#f6f"];
var wordColor = [];
var colorIdx = 0;
var matchRegex = "";
var matchingSlides = [];
this.setRegex = function(input)
{
input = input.replace(/^[^\w]+|[^\w]+$/g, "").replace(/[^\w'-]+/g, "|");
matchRegex = new RegExp("(" + input + ")","i");
}
this.getRegex = function()
{
return matchRegex.toString().replace(/^\/\\b\(|\)\\b\/i$/g, "").replace(/\|/g, " ");
}
// recursively apply word highlighting
this.hiliteWords = function(node)
{
if(node == undefined || !node) return;
if(!matchRegex) return;
if(skipTags.test(node.nodeName)) return;
if(node.hasChildNodes()) {
for(var i=0; i < node.childNodes.length; i++)
this.hiliteWords(node.childNodes[i]);
}
if(node.nodeType == 3) { // NODE_TEXT
if((nv = node.nodeValue) && (regs = matchRegex.exec(nv))) {
//find the slide's section element and save it in our list of matching slides
var secnode = node.parentNode;
while (secnode.nodeName != 'SECTION') {
secnode = secnode.parentNode;
}
var slideIndex = Reveal.getIndices(secnode);
var slidelen = matchingSlides.length;
var alreadyAdded = false;
for (var i=0; i < slidelen; i++) {
if ( (matchingSlides[i].h === slideIndex.h) && (matchingSlides[i].v === slideIndex.v) ) {
alreadyAdded = true;
}
}
if (! alreadyAdded) {
matchingSlides.push(slideIndex);
}
if(!wordColor[regs[0].toLowerCase()]) {
wordColor[regs[0].toLowerCase()] = colors[colorIdx++ % colors.length];
}
var match = document.createElement(hiliteTag);
match.appendChild(document.createTextNode(regs[0]));
match.style.backgroundColor = wordColor[regs[0].toLowerCase()];
match.style.fontStyle = "inherit";
match.style.color = "#000";
var after = node.splitText(regs.index);
after.nodeValue = after.nodeValue.substring(regs[0].length);
node.parentNode.insertBefore(match, after);
}
}
};
// remove highlighting
this.remove = function()
{
var arr = document.getElementsByTagName(hiliteTag);
while(arr.length && (el = arr[0])) {
el.parentNode.replaceChild(el.firstChild, el);
}
};
// start highlighting at target node
this.apply = function(input)
{
if(input == undefined || !input) return;
this.remove();
this.setRegex(input);
this.hiliteWords(targetNode);
return matchingSlides;
};
}
function openSearch() {
//ensure the search term input dialog is visible and has focus:
var inputbox = document.getElementById("searchinput");
inputbox.style.display = "inline";
inputbox.focus();
inputbox.select();
}
function toggleSearch() {
var inputbox = document.getElementById("searchinput");
if (inputbox.style.display !== "inline") {
openSearch();
}
else {
inputbox.style.display = "none";
myHilitor.remove();
}
}
function doSearch() {
//if there's been a change in the search term, perform a new search:
if (searchboxDirty) {
var searchstring = document.getElementById("searchinput").value;
//find the keyword amongst the slides
myHilitor = new Hilitor("slidecontent");
matchedSlides = myHilitor.apply(searchstring);
currentMatchedIndex = 0;
}
//navigate to the next slide that has the keyword, wrapping to the first if necessary
if (matchedSlides.length && (matchedSlides.length <= currentMatchedIndex)) {
currentMatchedIndex = 0;
}
if (matchedSlides.length > currentMatchedIndex) {
Reveal.slide(matchedSlides[currentMatchedIndex].h, matchedSlides[currentMatchedIndex].v);
currentMatchedIndex++;
}
}
var dom = {};
dom.wrapper = document.querySelector( '.reveal' );
if( !dom.wrapper.querySelector( '.searchbox' ) ) {
var searchElement = document.createElement( 'div' );
searchElement.id = "searchinputdiv";
searchElement.classList.add( 'searchdiv' );
searchElement.style.position = 'absolute';
searchElement.style.top = '10px';
searchElement.style.left = '10px';
//embedded base64 search icon Designed by Sketchdock - http://www.sketchdock.com/:
searchElement.innerHTML = '';
dom.wrapper.appendChild( searchElement );
}
document.getElementById("searchbutton").addEventListener( 'click', function(event) {
doSearch();
}, false );
document.getElementById("searchinput").addEventListener( 'keyup', function( event ) {
switch (event.keyCode) {
case 13:
event.preventDefault();
doSearch();
searchboxDirty = false;
break;
default:
searchboxDirty = true;
}
}, false );
// Open the search when the 's' key is hit (yes, this conflicts with the notes plugin, disabling for now)
/*
document.addEventListener( 'keydown', function( event ) {
// Disregard the event if the target is editable or a
// modifier is present
if ( document.querySelector( ':focus' ) !== null || event.shiftKey || event.altKey || event.ctrlKey || event.metaKey ) return;
if( event.keyCode === 83 ) {
event.preventDefault();
openSearch();
}
}, false );
*/
return { open: openSearch };
})();
================================================
FILE: presentation/plugin/zoom-js/zoom.js
================================================
// Custom reveal.js integration
(function(){
var isEnabled = true;
document.querySelector( '.reveal' ).addEventListener( 'mousedown', function( event ) {
var modifier = ( Reveal.getConfig().zoomKey ? Reveal.getConfig().zoomKey : 'alt' ) + 'Key';
var zoomPadding = 20;
var revealScale = Reveal.getScale();
if( event[ modifier ] && isEnabled ) {
event.preventDefault();
var bounds = event.target.getBoundingClientRect();
zoom.to({
x: ( bounds.left * revealScale ) - zoomPadding,
y: ( bounds.top * revealScale ) - zoomPadding,
width: ( bounds.width * revealScale ) + ( zoomPadding * 2 ),
height: ( bounds.height * revealScale ) + ( zoomPadding * 2 ),
pan: false
});
}
} );
Reveal.addEventListener( 'overviewshown', function() { isEnabled = false; } );
Reveal.addEventListener( 'overviewhidden', function() { isEnabled = true; } );
})();
/*!
* zoom.js 0.3 (modified for use with reveal.js)
* http://lab.hakim.se/zoom-js
* MIT licensed
*
* Copyright (C) 2011-2014 Hakim El Hattab, http://hakim.se
*/
var zoom = (function(){
// The current zoom level (scale)
var level = 1;
// The current mouse position, used for panning
var mouseX = 0,
mouseY = 0;
// Timeout before pan is activated
var panEngageTimeout = -1,
panUpdateInterval = -1;
// Check for transform support so that we can fallback otherwise
var supportsTransforms = 'WebkitTransform' in document.body.style ||
'MozTransform' in document.body.style ||
'msTransform' in document.body.style ||
'OTransform' in document.body.style ||
'transform' in document.body.style;
if( supportsTransforms ) {
// The easing that will be applied when we zoom in/out
document.body.style.transition = 'transform 0.8s ease';
document.body.style.OTransition = '-o-transform 0.8s ease';
document.body.style.msTransition = '-ms-transform 0.8s ease';
document.body.style.MozTransition = '-moz-transform 0.8s ease';
document.body.style.WebkitTransition = '-webkit-transform 0.8s ease';
}
// Zoom out if the user hits escape
document.addEventListener( 'keyup', function( event ) {
if( level !== 1 && event.keyCode === 27 ) {
zoom.out();
}
} );
// Monitor mouse movement for panning
document.addEventListener( 'mousemove', function( event ) {
if( level !== 1 ) {
mouseX = event.clientX;
mouseY = event.clientY;
}
} );
/**
* Applies the CSS required to zoom in, prefers the use of CSS3
* transforms but falls back on zoom for IE.
*
* @param {Object} rect
* @param {Number} scale
*/
function magnify( rect, scale ) {
var scrollOffset = getScrollOffset();
// Ensure a width/height is set
rect.width = rect.width || 1;
rect.height = rect.height || 1;
// Center the rect within the zoomed viewport
rect.x -= ( window.innerWidth - ( rect.width * scale ) ) / 2;
rect.y -= ( window.innerHeight - ( rect.height * scale ) ) / 2;
if( supportsTransforms ) {
// Reset
if( scale === 1 ) {
document.body.style.transform = '';
document.body.style.OTransform = '';
document.body.style.msTransform = '';
document.body.style.MozTransform = '';
document.body.style.WebkitTransform = '';
}
// Scale
else {
var origin = scrollOffset.x +'px '+ scrollOffset.y +'px',
transform = 'translate('+ -rect.x +'px,'+ -rect.y +'px) scale('+ scale +')';
document.body.style.transformOrigin = origin;
document.body.style.OTransformOrigin = origin;
document.body.style.msTransformOrigin = origin;
document.body.style.MozTransformOrigin = origin;
document.body.style.WebkitTransformOrigin = origin;
document.body.style.transform = transform;
document.body.style.OTransform = transform;
document.body.style.msTransform = transform;
document.body.style.MozTransform = transform;
document.body.style.WebkitTransform = transform;
}
}
else {
// Reset
if( scale === 1 ) {
document.body.style.position = '';
document.body.style.left = '';
document.body.style.top = '';
document.body.style.width = '';
document.body.style.height = '';
document.body.style.zoom = '';
}
// Scale
else {
document.body.style.position = 'relative';
document.body.style.left = ( - ( scrollOffset.x + rect.x ) / scale ) + 'px';
document.body.style.top = ( - ( scrollOffset.y + rect.y ) / scale ) + 'px';
document.body.style.width = ( scale * 100 ) + '%';
document.body.style.height = ( scale * 100 ) + '%';
document.body.style.zoom = scale;
}
}
level = scale;
if( document.documentElement.classList ) {
if( level !== 1 ) {
document.documentElement.classList.add( 'zoomed' );
}
else {
document.documentElement.classList.remove( 'zoomed' );
}
}
}
/**
* Pan the document when the mosue cursor approaches the edges
* of the window.
*/
function pan() {
var range = 0.12,
rangeX = window.innerWidth * range,
rangeY = window.innerHeight * range,
scrollOffset = getScrollOffset();
// Up
if( mouseY < rangeY ) {
window.scroll( scrollOffset.x, scrollOffset.y - ( 1 - ( mouseY / rangeY ) ) * ( 14 / level ) );
}
// Down
else if( mouseY > window.innerHeight - rangeY ) {
window.scroll( scrollOffset.x, scrollOffset.y + ( 1 - ( window.innerHeight - mouseY ) / rangeY ) * ( 14 / level ) );
}
// Left
if( mouseX < rangeX ) {
window.scroll( scrollOffset.x - ( 1 - ( mouseX / rangeX ) ) * ( 14 / level ), scrollOffset.y );
}
// Right
else if( mouseX > window.innerWidth - rangeX ) {
window.scroll( scrollOffset.x + ( 1 - ( window.innerWidth - mouseX ) / rangeX ) * ( 14 / level ), scrollOffset.y );
}
}
function getScrollOffset() {
return {
x: window.scrollX !== undefined ? window.scrollX : window.pageXOffset,
y: window.scrollY !== undefined ? window.scrollY : window.pageYOffset
}
}
return {
/**
* Zooms in on either a rectangle or HTML element.
*
* @param {Object} options
* - element: HTML element to zoom in on
* OR
* - x/y: coordinates in non-transformed space to zoom in on
* - width/height: the portion of the screen to zoom in on
* - scale: can be used instead of width/height to explicitly set scale
*/
to: function( options ) {
// Due to an implementation limitation we can't zoom in
// to another element without zooming out first
if( level !== 1 ) {
zoom.out();
}
else {
options.x = options.x || 0;
options.y = options.y || 0;
// If an element is set, that takes precedence
if( !!options.element ) {
// Space around the zoomed in element to leave on screen
var padding = 20;
var bounds = options.element.getBoundingClientRect();
options.x = bounds.left - padding;
options.y = bounds.top - padding;
options.width = bounds.width + ( padding * 2 );
options.height = bounds.height + ( padding * 2 );
}
// If width/height values are set, calculate scale from those values
if( options.width !== undefined && options.height !== undefined ) {
options.scale = Math.max( Math.min( window.innerWidth / options.width, window.innerHeight / options.height ), 1 );
}
if( options.scale > 1 ) {
options.x *= options.scale;
options.y *= options.scale;
magnify( options, options.scale );
if( options.pan !== false ) {
// Wait with engaging panning as it may conflict with the
// zoom transition
panEngageTimeout = setTimeout( function() {
panUpdateInterval = setInterval( pan, 1000 / 60 );
}, 800 );
}
}
}
},
/**
* Resets the document zoom state to its default.
*/
out: function() {
clearTimeout( panEngageTimeout );
clearInterval( panUpdateInterval );
magnify( { x: 0, y: 0 }, 1 );
level = 1;
},
// Alias
magnify: function( options ) { this.to( options ) },
reset: function() { this.out() },
zoomLevel: function() {
return level;
}
}
})();
================================================
FILE: requirements.txt
================================================
-i https://pypi.python.org/simple
alabaster==0.7.12
apipkg==1.5
atomicwrites==1.3.0
attrs==19.1.0
babel==2.7.0
brotli==1.0.7
certifi==2019.6.16
chardet==3.0.4
coverage==4.5.4
coveralls==1.5.1
dateparser==0.7.0
django-cors-headers==2.4.0
django-crispy-forms==1.7.2
django-extensions==2.1.4
django-filter==2.1.0
django==2.0.10
djangoql==0.12.3
djangorestframework==3.9.1
docopt==0.6.2
docutils==0.15.2
execnet==1.6.1
factory-boy==2.11.1
faker==2.0.0
filelock==3.0.12
filemagic==1.6
fuzzywuzzy[speedup]==0.15.0
gunicorn==20.0.4
idna==2.8
imagesize==1.1.0
importlib-metadata==0.19
inotify-simple==1.1.8; sys_platform == 'linux'
jinja2==2.10.1
langdetect==1.0.7
markupsafe==1.1.1
more-itertools==7.2.0
packaging==19.1
pdftotext==2.1.1
pillow==5.4.1
pluggy==0.12.0
ply==3.11
psycopg2==2.8.4
py==1.8.0
pycodestyle==2.4.0
pygments==2.4.2
pyocr==0.5.3
pyparsing==2.4.2
pytest-cov==2.6.1
pytest-django==3.4.5
pytest-env==0.6.2
pytest-forked==1.0.2
pytest-sugar==0.9.2
pytest-xdist==1.26.0
pytest==4.1.1
python-dateutil==2.7.5
python-dotenv==0.10.1
python-gnupg==0.4.4
python-levenshtein==0.12.0
pytz==2018.9
regex==2019.6.8
requests==2.22.0
six==1.12.0
snowballstemmer==1.9.0
sphinx==1.8.3
sphinxcontrib-applehelp==1.0.1
sphinxcontrib-devhelp==1.0.1
sphinxcontrib-htmlhelp==1.0.2
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.2
sphinxcontrib-serializinghtml==1.1.3
termcolor==1.1.0
text-unidecode==1.2
toml==0.10.0
tox==3.7.0
tzlocal==2.0.0
urllib3==1.25.3
virtualenv==16.7.2
wcwidth==0.1.7
whitenoise==4.1.3
zipp==0.5.2
================================================
FILE: resources/logo/print/eps/Black logo - no background.eps
================================================
%!PS-Adobe-3.0 EPSF-3.0
%Produced by poppler pdftops version: 0.59.0 (http://poppler.freedesktop.org)
%%Creator: Chromium
%%LanguageLevel: 3
%%DocumentSuppliedResources: (atend)
%%BoundingBox: 0 0 2409 909
%%HiResBoundingBox: 0 0 2409 909
%%DocumentSuppliedResources: (atend)
%%EndComments
%%BeginProlog
%%BeginResource: procset xpdf 3.00 0
%%Copyright: Copyright 1996-2011 Glyph & Cog, LLC
/xpdf 75 dict def xpdf begin
% PDF special state
/pdfDictSize 15 def
/pdfSetup {
/setpagedevice where {
pop 2 dict begin
/Policies 1 dict dup begin /PageSize 6 def end def
{ /Duplex true def } if
currentdict end setpagedevice
} {
pop
} ifelse
} def
/pdfSetupPaper {
% Change paper size, but only if different from previous paper size otherwise
% duplex fails. PLRM specifies a tolerance of 5 pts when matching paper size
% so we use the same when checking if the size changes.
/setpagedevice where {
pop currentpagedevice
/PageSize known {
2 copy
currentpagedevice /PageSize get aload pop
exch 4 1 roll
sub abs 5 gt
3 1 roll
sub abs 5 gt
or
} {
true
} ifelse
{
2 array astore
2 dict begin
/PageSize exch def
/ImagingBBox null def
currentdict end
setpagedevice
} {
pop pop
} ifelse
} {
pop
} ifelse
} def
/pdfStartPage {
pdfDictSize dict begin
/pdfFillCS [] def
/pdfFillXform {} def
/pdfStrokeCS [] def
/pdfStrokeXform {} def
/pdfFill [0] def
/pdfStroke [0] def
/pdfFillOP false def
/pdfStrokeOP false def
/pdfOPM false def
/pdfLastFill false def
/pdfLastStroke false def
/pdfTextMat [1 0 0 1 0 0] def
/pdfFontSize 0 def
/pdfCharSpacing 0 def
/pdfTextRender 0 def
/pdfPatternCS false def
/pdfTextRise 0 def
/pdfWordSpacing 0 def
/pdfHorizScaling 1 def
/pdfTextClipPath [] def
} def
/pdfEndPage { end } def
% PDF color state
/opm { dup /pdfOPM exch def
/setoverprintmode where{pop setoverprintmode}{pop}ifelse } def
/cs { /pdfFillXform exch def dup /pdfFillCS exch def
setcolorspace } def
/CS { /pdfStrokeXform exch def dup /pdfStrokeCS exch def
setcolorspace } def
/sc { pdfLastFill not { pdfFillCS setcolorspace } if
dup /pdfFill exch def aload pop pdfFillXform setcolor
/pdfLastFill true def /pdfLastStroke false def } def
/SC { pdfLastStroke not { pdfStrokeCS setcolorspace } if
dup /pdfStroke exch def aload pop pdfStrokeXform setcolor
/pdfLastStroke true def /pdfLastFill false def } def
/op { /pdfFillOP exch def
pdfLastFill { pdfFillOP setoverprint } if } def
/OP { /pdfStrokeOP exch def
pdfLastStroke { pdfStrokeOP setoverprint } if } def
/fCol {
pdfLastFill not {
pdfFillCS setcolorspace
pdfFill aload pop pdfFillXform setcolor
pdfFillOP setoverprint
/pdfLastFill true def /pdfLastStroke false def
} if
} def
/sCol {
pdfLastStroke not {
pdfStrokeCS setcolorspace
pdfStroke aload pop pdfStrokeXform setcolor
pdfStrokeOP setoverprint
/pdfLastStroke true def /pdfLastFill false def
} if
} def
% build a font
/pdfMakeFont {
4 3 roll findfont
4 2 roll matrix scale makefont
dup length dict begin
{ 1 index /FID ne { def } { pop pop } ifelse } forall
/Encoding exch def
currentdict
end
definefont pop
} def
/pdfMakeFont16 {
exch findfont
dup length dict begin
{ 1 index /FID ne { def } { pop pop } ifelse } forall
/WMode exch def
currentdict
end
definefont pop
} def
/pdfMakeFont16L3 {
1 index /CIDFont resourcestatus {
pop pop 1 index /CIDFont findresource /CIDFontType known
} {
false
} ifelse
{
0 eq { /Identity-H } { /Identity-V } ifelse
exch 1 array astore composefont pop
} {
pdfMakeFont16
} ifelse
} def
% graphics state operators
/q { gsave pdfDictSize dict begin } def
/Q {
end grestore
/pdfLastFill where {
pop
pdfLastFill {
pdfFillOP setoverprint
} {
pdfStrokeOP setoverprint
} ifelse
} if
/pdfOPM where {
pop
pdfOPM /setoverprintmode where{pop setoverprintmode}{pop}ifelse
} if
} def
/cm { concat } def
/d { setdash } def
/i { setflat } def
/j { setlinejoin } def
/J { setlinecap } def
/M { setmiterlimit } def
/w { setlinewidth } def
% path segment operators
/m { moveto } def
/l { lineto } def
/c { curveto } def
/re { 4 2 roll moveto 1 index 0 rlineto 0 exch rlineto
neg 0 rlineto closepath } def
/h { closepath } def
% path painting operators
/S { sCol stroke } def
/Sf { fCol stroke } def
/f { fCol fill } def
/f* { fCol eofill } def
% clipping operators
/W { clip newpath } def
/W* { eoclip newpath } def
/Ws { strokepath clip newpath } def
% text state operators
/Tc { /pdfCharSpacing exch def } def
/Tf { dup /pdfFontSize exch def
dup pdfHorizScaling mul exch matrix scale
pdfTextMat matrix concatmatrix dup 4 0 put dup 5 0 put
exch findfont exch makefont setfont } def
/Tr { /pdfTextRender exch def } def
/Tp { /pdfPatternCS exch def } def
/Ts { /pdfTextRise exch def } def
/Tw { /pdfWordSpacing exch def } def
/Tz { /pdfHorizScaling exch def } def
% text positioning operators
/Td { pdfTextMat transform moveto } def
/Tm { /pdfTextMat exch def } def
% text string operators
/xyshow where {
pop
/xyshow2 {
dup length array
0 2 2 index length 1 sub {
2 index 1 index 2 copy get 3 1 roll 1 add get
pdfTextMat dtransform
4 2 roll 2 copy 6 5 roll put 1 add 3 1 roll dup 4 2 roll put
} for
exch pop
xyshow
} def
}{
/xyshow2 {
currentfont /FontType get 0 eq {
0 2 3 index length 1 sub {
currentpoint 4 index 3 index 2 getinterval show moveto
2 copy get 2 index 3 2 roll 1 add get
pdfTextMat dtransform rmoveto
} for
} {
0 1 3 index length 1 sub {
currentpoint 4 index 3 index 1 getinterval show moveto
2 copy 2 mul get 2 index 3 2 roll 2 mul 1 add get
pdfTextMat dtransform rmoveto
} for
} ifelse
pop pop
} def
} ifelse
/cshow where {
pop
/xycp {
0 3 2 roll
{
pop pop currentpoint 3 2 roll
1 string dup 0 4 3 roll put false charpath moveto
2 copy get 2 index 2 index 1 add get
pdfTextMat dtransform rmoveto
2 add
} exch cshow
pop pop
} def
}{
/xycp {
currentfont /FontType get 0 eq {
0 2 3 index length 1 sub {
currentpoint 4 index 3 index 2 getinterval false charpath moveto
2 copy get 2 index 3 2 roll 1 add get
pdfTextMat dtransform rmoveto
} for
} {
0 1 3 index length 1 sub {
currentpoint 4 index 3 index 1 getinterval false charpath moveto
2 copy 2 mul get 2 index 3 2 roll 2 mul 1 add get
pdfTextMat dtransform rmoveto
} for
} ifelse
pop pop
} def
} ifelse
/Tj {
fCol
0 pdfTextRise pdfTextMat dtransform rmoveto
currentpoint 4 2 roll
pdfTextRender 1 and 0 eq {
2 copy xyshow2
} if
pdfTextRender 3 and dup 1 eq exch 2 eq or {
3 index 3 index moveto
2 copy
currentfont /FontType get 3 eq { fCol } { sCol } ifelse
xycp currentpoint stroke moveto
} if
pdfTextRender 4 and 0 ne {
4 2 roll moveto xycp
/pdfTextClipPath [ pdfTextClipPath aload pop
{/moveto cvx}
{/lineto cvx}
{/curveto cvx}
{/closepath cvx}
pathforall ] def
currentpoint newpath moveto
} {
pop pop pop pop
} ifelse
0 pdfTextRise neg pdfTextMat dtransform rmoveto
} def
/TJm { 0.001 mul pdfFontSize mul pdfHorizScaling mul neg 0
pdfTextMat dtransform rmoveto } def
/TJmV { 0.001 mul pdfFontSize mul neg 0 exch
pdfTextMat dtransform rmoveto } def
/Tclip { pdfTextClipPath cvx exec clip newpath
/pdfTextClipPath [] def } def
/Tclip* { pdfTextClipPath cvx exec eoclip newpath
/pdfTextClipPath [] def } def
% Level 2/3 image operators
/pdfImBuf 100 string def
/pdfImStr {
2 copy exch length lt {
2 copy get exch 1 add exch
} {
()
} ifelse
} def
/skipEOD {
{ currentfile pdfImBuf readline
not { pop exit } if
(%-EOD-) eq { exit } if } loop
} def
/pdfIm { image skipEOD } def
/pdfMask {
/ReusableStreamDecode filter
skipEOD
/maskStream exch def
} def
/pdfMaskEnd { maskStream closefile } def
/pdfMaskInit {
/maskArray exch def
/maskIdx 0 def
} def
/pdfMaskSrc {
maskIdx maskArray length lt {
maskArray maskIdx get
/maskIdx maskIdx 1 add def
} {
()
} ifelse
} def
/pdfImM { fCol imagemask skipEOD } def
/pr { 2 index 2 index 3 2 roll putinterval 4 add } def
/pdfImClip {
gsave
0 2 4 index length 1 sub {
dup 4 index exch 2 copy
get 5 index div put
1 add 3 index exch 2 copy
get 3 index div put
} for
pop pop rectclip
} def
/pdfImClipEnd { grestore } def
% shading operators
/colordelta {
false 0 1 3 index length 1 sub {
dup 4 index exch get 3 index 3 2 roll get sub abs 0.004 gt {
pop true
} if
} for
exch pop exch pop
} def
/funcCol { func n array astore } def
/funcSH {
dup 0 eq {
true
} {
dup 6 eq {
false
} {
4 index 4 index funcCol dup
6 index 4 index funcCol dup
3 1 roll colordelta 3 1 roll
5 index 5 index funcCol dup
3 1 roll colordelta 3 1 roll
6 index 8 index funcCol dup
3 1 roll colordelta 3 1 roll
colordelta or or or
} ifelse
} ifelse
{
1 add
4 index 3 index add 0.5 mul exch 4 index 3 index add 0.5 mul exch
6 index 6 index 4 index 4 index 4 index funcSH
2 index 6 index 6 index 4 index 4 index funcSH
6 index 2 index 4 index 6 index 4 index funcSH
5 3 roll 3 2 roll funcSH pop pop
} {
pop 3 index 2 index add 0.5 mul 3 index 2 index add 0.5 mul
funcCol sc
dup 4 index exch mat transform m
3 index 3 index mat transform l
1 index 3 index mat transform l
mat transform l pop pop h f*
} ifelse
} def
/axialCol {
dup 0 lt {
pop t0
} {
dup 1 gt {
pop t1
} {
dt mul t0 add
} ifelse
} ifelse
func n array astore
} def
/axialSH {
dup 0 eq {
true
} {
dup 8 eq {
false
} {
2 index axialCol 2 index axialCol colordelta
} ifelse
} ifelse
{
1 add 3 1 roll 2 copy add 0.5 mul
dup 4 3 roll exch 4 index axialSH
exch 3 2 roll axialSH
} {
pop 2 copy add 0.5 mul
axialCol sc
exch dup dx mul x0 add exch dy mul y0 add
3 2 roll dup dx mul x0 add exch dy mul y0 add
dx abs dy abs ge {
2 copy yMin sub dy mul dx div add yMin m
yMax sub dy mul dx div add yMax l
2 copy yMax sub dy mul dx div add yMax l
yMin sub dy mul dx div add yMin l
h f*
} {
exch 2 copy xMin sub dx mul dy div add xMin exch m
xMax sub dx mul dy div add xMax exch l
exch 2 copy xMax sub dx mul dy div add xMax exch l
xMin sub dx mul dy div add xMin exch l
h f*
} ifelse
} ifelse
} def
/radialCol {
dup t0 lt {
pop t0
} {
dup t1 gt {
pop t1
} if
} ifelse
func n array astore
} def
/radialSH {
dup 0 eq {
true
} {
dup 8 eq {
false
} {
2 index dt mul t0 add radialCol
2 index dt mul t0 add radialCol colordelta
} ifelse
} ifelse
{
1 add 3 1 roll 2 copy add 0.5 mul
dup 4 3 roll exch 4 index radialSH
exch 3 2 roll radialSH
} {
pop 2 copy add 0.5 mul dt mul t0 add
radialCol sc
encl {
exch dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
0 360 arc h
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
360 0 arcn h f
} {
2 copy
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
a1 a2 arcn
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
a2 a1 arcn h
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
a1 a2 arc
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
a2 a1 arc h f
} ifelse
} ifelse
} def
end
%%EndResource
/CIDInit /ProcSet findresource begin
10 dict begin
begincmap
/CMapType 1 def
/CMapName /Identity-H def
/CIDSystemInfo 3 dict dup begin
/Registry (Adobe) def
/Ordering (Identity) def
/Supplement 0 def
end def
1 begincodespacerange
<0000>
endcodespacerange
0 usefont
1 begincidrange
<0000> 0
endcidrange
endcmap
currentdict CMapName exch /CMap defineresource pop
end
10 dict begin
begincmap
/CMapType 1 def
/CMapName /Identity-V def
/CIDSystemInfo 3 dict dup begin
/Registry (Adobe) def
/Ordering (Identity) def
/Supplement 0 def
end def
/WMode 1 def
1 begincodespacerange
<0000>
endcodespacerange
0 usefont
1 begincidrange
<0000> 0
endcidrange
endcmap
currentdict CMapName exch /CMap defineresource pop
end
end
%%EndProlog
%%BeginSetup
xpdf begin
%%EndSetup
pdfStartPage
%%EndPageSetup
[] 0 d
1 i
0 j
0 J
10 M
1 w
/DeviceGray {} cs
[0] sc
/DeviceGray {} CS
[0] SC
false op
false OP
{} settransfer
0 0 2409 909 re
W
q
[1 0 0 -1 0 909] cm
q
0 0 2409 908.7038 re
W*
q
[0.747904 0 0 0.747904 0 -908.7038] cm
/DeviceRGB {} CS
[1 1 1] SC
/DeviceRGB {} cs
[1 1 1] sc
0 0 3221 2436 re
f
Q
Q
q
5.983235 0 2403.0168 907.20795 re
W*
q
[0.822879 0 0 0.822978 95.330643 145.991699] cm
/DeviceRGB {} CS
[0.09 0.329 0.122] SC
/DeviceRGB {} cs
[0.09 0.329 0.122] sc
231 798 m
227 779 219 741 218 741 c
49 640 69 465 125 365 c
137 491 360 578 230 732 c
229 734 236 758 242 780 c
268 736 307 683 305 678 c
145 288 645 258 749 16 c
796 250 725 612 323 704 c
321 705 250 830 247 831 c
247 829 217 830 221 820 c
223 814 227 806 231 798 c
h
330 625 m
267 476 452 312 544 271 c
356 439 324 564 330 625 c
h
226 704 m
277 645 217 544 181 511 c
242 616 238 677 226 704 c
h
f
Q
q
[26.582434 0 0 26.585632 866.89978 103.116905] cm
6.5 8.62 m
6.513333 8.940001 6.52 9.303333 6.52 9.71 c
6.52 10.116667 6.513333 10.473333 6.5 10.78 c
6.5 11.139999 6.436667 11.496666 6.31 11.849999 c
6.183333 12.203333 5.993333 12.52 5.74 12.799999 c
5.486666 13.079999 5.17 13.299999 4.79 13.459999 c
4.41 13.619999 3.973334 13.699999 3.48 13.699999 c
2.46 13.699999 l
2.46 18.919998 l
2.46 19.106665 2.486667 19.243332 2.54 19.329998 c
2.593333 19.416664 2.646667 19.466663 2.7 19.479998 c
2.78 19.519997 2.866667 19.519997 2.96 19.479998 c
2.96 19.999998 l
0.54 19.999998 l
0.54 19.479998 l
0.646667 19.519997 0.726667 19.519997 0.78 19.479998 c
0.846667 19.466663 0.913333 19.416664 0.98 19.329998 c
1.046667 19.243332 1.08 19.106665 1.08 18.919998 c
1.08 6.779998 l
1.08 6.579998 1.046667 6.436665 0.98 6.349998 c
0.913333 6.263331 0.846667 6.206665 0.78 6.179998 c
0.726667 6.153331 0.646667 6.153331 0.54 6.179998 c
0.54 5.699998 l
3.48 5.699998 l
3.973334 5.699998 4.41 5.779998 4.79 5.939998 c
5.17 6.099998 5.486666 6.319997 5.74 6.599998 c
5.993333 6.879998 6.183333 7.193331 6.31 7.539998 c
6.436667 7.886664 6.5 8.246664 6.5 8.619998 c
6.5 8.62 l
h
5.08 8.18 m
5.08 7.833334 5.023333 7.533334 4.91 7.28 c
4.796667 7.026667 4.663333 6.826667 4.51 6.68 c
4.356667 6.533334 4.196666 6.416667 4.03 6.33 c
3.863333 6.243334 3.72 6.2 3.6 6.2 c
2.46 6.2 l
2.46 13.16 l
3.599999 13.16 l
3.72 13.16 3.863333 13.126666 4.029999 13.06 c
4.196666 12.993333 4.356666 12.876666 4.509999 12.709999 c
4.663333 12.543332 4.796666 12.339999 4.909999 12.099999 c
5.023333 11.86 5.079999 11.56 5.079999 11.2 c
5.079999 8.18 l
5.08 8.18 l
h
14.58 19.139999 m
14.58 19.246666 14.553333 19.356667 14.5 19.469999 c
14.446667 19.583332 14.37 19.689999 14.270001 19.789999 c
14.170001 19.889999 14.043334 19.98 13.89 20.059999 c
13.736667 20.139999 13.56 20.18 13.360001 20.18 c
13.040001 20.18 12.753334 20.106667 12.500001 19.960001 c
12.246668 19.813335 12.073335 19.620001 11.980001 19.380001 c
11.900002 19.246668 11.840001 19.093334 11.800001 18.920002 c
11.626668 19.280003 11.363335 19.580002 11.010001 19.820002 c
10.656668 20.060001 10.266668 20.18 9.840001 20.180002 c
9.306667 20.180002 8.840001 19.98667 8.440002 19.600002 c
8.213335 19.400003 8.026669 19.140003 7.880002 18.820002 c
7.733335 18.5 7.646668 18.133333 7.620002 17.720001 c
7.606669 17.680002 7.600002 17.600002 7.600002 17.480001 c
7.600002 17.460001 l
7.613335 17.233334 7.653335 16.996668 7.720002 16.750002 c
7.786668 16.503336 7.890002 16.25667 8.030002 16.010002 c
8.170002 15.763335 8.336668 15.530002 8.530002 15.310002 c
8.723335 15.090003 8.933335 14.900003 9.160002 14.740003 c
9.213335 14.713336 9.256668 14.683336 9.290002 14.650003 c
9.323336 14.616669 9.373335 14.580003 9.440002 14.540003 c
9.500002 14.510003 l
9.560002 14.480003 l
9.800002 14.333337 10.010002 14.200004 10.190002 14.080004 c
10.370003 13.960004 10.533336 13.846671 10.680002 13.740004 c
10.760002 13.68667 10.840002 13.62667 10.920002 13.560003 c
11.000002 13.493337 11.086669 13.42667 11.180002 13.360004 c
11.300002 13.280004 11.413336 13.18667 11.520002 13.080004 c
11.626669 12.973337 11.706669 12.84667 11.760002 12.700004 c
11.840002 12.56667 11.880002 12.406671 11.880002 12.220003 c
11.880002 12.033336 11.860002 11.86667 11.820002 11.720003 c
11.766668 11.42667 11.630002 11.173337 11.410002 10.960003 c
11.190002 10.746669 10.946669 10.606669 10.680002 10.540003 c
10.560002 10.500003 10.446669 10.480002 10.340002 10.480002 c
9.966668 10.480002 9.646668 10.546669 9.380002 10.680002 c
9.206669 10.760002 9.080002 10.850002 9.000002 10.950003 c
8.920002 11.050003 8.876669 11.143336 8.870002 11.230002 c
8.863335 11.316669 8.880002 11.386668 8.920002 11.440002 c
8.960002 11.493337 9.013335 11.533337 9.080002 11.560002 c
9.106669 11.573336 9.123335 11.580003 9.130002 11.580003 c
9.136669 11.580003 9.153336 11.58667 9.180002 11.600003 c
9.353335 11.66667 9.496669 11.783337 9.610003 11.950004 c
9.723336 12.116671 9.780003 12.300004 9.780003 12.500004 c
9.780003 12.753337 9.690002 12.970004 9.510002 13.150003 c
9.330002 13.330003 9.113336 13.420003 8.860003 13.420004 c
8.593336 13.420004 8.36667 13.330004 8.180002 13.150003 c
7.993335 12.970003 7.900002 12.753337 7.900002 12.500004 c
7.900002 12.273337 7.930002 12.036671 7.990002 11.790004 c
8.050002 11.543337 8.140002 11.320004 8.260002 11.120004 c
8.513335 10.74667 8.816669 10.47667 9.170002 10.310003 c
9.523336 10.143336 9.913335 10.060003 10.340002 10.060003 c
10.660002 10.060003 10.976668 10.120004 11.290002 10.240004 c
11.603335 10.360004 11.883335 10.530004 12.130002 10.750004 c
12.376669 10.970004 12.583335 11.233337 12.750002 11.540004 c
12.916669 11.84667 13.000002 12.200004 13.000002 12.600004 c
13.000002 18.480003 l
13.000002 18.720003 13.003335 18.876671 13.010002 18.950003 c
13.016669 19.023335 13.020002 19.073336 13.020002 19.100002 c
13.020002 19.273336 13.073336 19.413336 13.180002 19.520002 c
13.286669 19.626669 13.413336 19.680002 13.560002 19.680002 c
13.733335 19.680002 13.873336 19.630003 13.980002 19.530003 c
14.086669 19.430002 14.146669 19.300003 14.160003 19.140003 c
14.580003 19.140003 l
14.58 19.139999 l
h
11.44 18.639999 m
11.546666 18.453333 11.61 18.253332 11.629999 18.039999 c
11.649999 17.826666 11.659999 17.626665 11.659999 17.439999 c
11.659999 13.739999 l
11.606666 13.793332 11.549999 13.843332 11.489999 13.889998 c
11.429998 13.936665 11.366666 13.993332 11.299999 14.059999 c
10.913333 14.339998 10.583333 14.596665 10.31 14.829998 c
10.036666 15.063331 9.806666 15.303331 9.619999 15.549998 c
9.433332 15.796665 9.286665 16.06 9.179999 16.339998 c
9.073334 16.619999 9.006667 16.939999 8.98 17.299999 c
8.98 17.599998 l
8.993333 18.106665 9.13 18.503332 9.389999 18.789999 c
9.649999 19.076666 10.019999 19.219999 10.499999 19.219999 c
10.713332 19.219999 10.906666 19.166666 11.079999 19.059999 c
11.253332 18.939999 11.373332 18.799999 11.439999 18.639999 c
11.44 18.639999 l
h
18.32 10.08 m
19.08 10.093333 19.686666 10.323334 20.139999 10.770001 c
20.593332 11.216667 20.82 11.926667 20.82 12.900001 c
20.82 17.380001 l
20.82 18.353334 20.593332 19.059999 20.139999 19.5 c
19.686666 19.940001 19.08 20.173334 18.32 20.200001 c
18.26 20.200001 l
18.033333 20.200001 17.826666 20.173334 17.639999 20.120001 c
17.619999 20.120001 l
17.606665 20.120001 17.599998 20.113335 17.599998 20.1 c
17.293333 19.993334 17.026667 19.846666 16.799999 19.66 c
16.609999 19.469999 l
16.609999 19.469999 16.56 19.4 16.459999 19.26 c
16.459999 22.58 l
16.459999 22.766666 16.486666 22.903334 16.539999 22.99 c
16.593332 23.076666 16.646666 23.133333 16.699999 23.16 c
16.779999 23.199999 16.866667 23.199999 16.959999 23.16 c
16.959999 23.66 l
14.539999 23.66 l
14.539999 23.16 l
14.646666 23.199999 14.726666 23.199999 14.779999 23.16 c
14.846665 23.133333 14.909999 23.076666 14.969998 22.99 c
15.029998 22.903334 15.059998 22.766666 15.059999 22.58 c
15.059999 11.3 l
15.059999 11.113334 15.029999 10.976667 14.969998 10.89 c
14.909998 10.803333 14.846665 10.746667 14.779999 10.72 c
14.726666 10.693334 14.646666 10.693334 14.539999 10.72 c
14.539999 10.24 l
16.459999 10.24 l
16.459999 11.02 l
16.553331 10.873333 16.666666 10.74 16.799999 10.62 c
17.026667 10.42 17.293333 10.273334 17.599998 10.18 c
17.599998 10.166667 17.606665 10.16 17.619999 10.16 c
17.639999 10.16 l
17.853333 10.106667 18.059999 10.08 18.26 10.08 c
18.32 10.08 l
h
19.32 12.46 m
19.32 12.44 l
19.306665 11.76 19.223333 11.28 19.07 11 c
18.916666 10.72 18.666666 10.566667 18.32 10.54 c
18.08 10.526667 17.853333 10.556666 17.639999 10.63 c
17.426666 10.703334 17.24 10.826667 17.08 11 c
16.92 11.173333 16.786667 11.406666 16.68 11.7 c
16.573334 11.993334 16.5 12.34 16.460001 12.74 c
16.460001 17.540001 l
16.5 17.953335 16.573334 18.303335 16.68 18.59 c
16.786667 18.876665 16.92 19.103333 17.08 19.27 c
17.24 19.436668 17.426666 19.560001 17.639999 19.640001 c
17.853333 19.720001 18.08 19.753334 18.32 19.740002 c
18.666666 19.713335 18.916666 19.560001 19.07 19.280003 c
19.223333 19.000004 19.306665 18.520004 19.32 17.840002 c
19.32 17.820002 l
19.32 12.460001 l
19.32 12.46 l
h
24.379999 10.08 m
24.686665 10.08 24.999998 10.133333 25.32 10.24 c
25.640001 10.346666 25.926668 10.513333 26.18 10.74 c
26.433332 10.966666 26.636667 11.253333 26.790001 11.599999 c
26.943335 11.946666 27.020002 12.359999 27.02 12.839999 c
27.02 14.859999 l
27.02 14.919999 l
27.02 15.379999 l
23.040001 15.379999 l
23.040001 17.439999 l
23.040001 18.266665 23.183334 18.856665 23.470001 19.209999 c
23.756668 19.563334 24.113335 19.733334 24.540001 19.719999 c
24.793333 19.706665 25.043333 19.663332 25.290001 19.59 c
25.536669 19.516668 25.750002 19.393333 25.93 19.219999 c
26.109999 19.046665 26.26 18.799997 26.380001 18.48 c
26.500002 18.160002 26.560003 17.740002 26.560001 17.219999 c
27.02 17.219999 l
27.02 17.5 l
27.02 17.860001 26.98 18.203335 26.9 18.530001 c
26.82 18.856667 26.679998 19.140001 26.48 19.380001 c
26.280001 19.620001 26.023333 19.810001 25.709999 19.950001 c
25.396666 20.09 25.013332 20.166666 24.559999 20.18 c
24.24 20.18 23.913334 20.120001 23.58 20 c
23.246666 19.879999 22.946667 19.699999 22.68 19.459999 c
22.413334 19.219999 22.193335 18.936665 22.02 18.609999 c
21.846666 18.283333 21.76 17.913332 21.76 17.499998 c
21.76 17.029999 l
21.76 17.029999 21.756666 16.773333 21.75 16.259998 c
21.743334 15.746665 21.74 15.429998 21.74 15.309999 c
21.74 15.189999 21.736666 14.859999 21.73 14.319999 c
21.723333 13.779999 21.719999 13.489999 21.719999 13.449999 c
21.719999 12.859999 l
21.719999 12.379998 21.803333 11.963332 21.969999 11.609999 c
22.136665 11.256665 22.343332 10.969999 22.59 10.749999 c
22.836668 10.529999 23.116667 10.363333 23.43 10.249999 c
23.743334 10.136665 24.059999 10.079999 24.380001 10.079999 c
24.379999 10.08 l
h
25.68 14.86 m
25.68 13.28 l
25.68 12.773333 25.646667 12.349999 25.58 12.01 c
25.513332 11.670001 25.423332 11.396668 25.309999 11.190001 c
25.196667 10.983334 25.059999 10.84 24.9 10.76 c
24.74 10.68 24.559999 10.64 24.359999 10.64 c
23.933332 10.64 23.609999 10.826667 23.389999 11.200001 c
23.17 11.573335 23.059999 12.266667 23.059999 13.280001 c
23.059999 14.860001 l
25.68 14.860001 l
25.68 14.86 l
h
33.540001 11.66 m
33.540001 11.686667 33.546669 11.74 33.560001 11.82 c
33.573334 11.9 33.573334 11.966666 33.560001 12.02 c
33.546669 12.299999 33.450001 12.53 33.27 12.709999 c
33.09 12.889998 32.873333 12.979999 32.619999 12.98 c
32.353333 12.98 32.129997 12.886666 31.949999 12.7 c
31.77 12.513333 31.68 12.286667 31.679998 12.02 c
31.679998 11.686667 31.806665 11.433332 32.059998 11.259999 c
32.086666 11.246666 32.103333 11.236666 32.109997 11.23 c
32.139996 11.2 l
32.23333 11.133333 32.293327 11.036666 32.319996 10.91 c
32.346664 10.783334 32.266663 10.673333 32.079994 10.58 c
31.959993 10.513333 31.839994 10.48 31.719994 10.48 c
31.319994 10.453333 30.96666 10.573333 30.659994 10.839999 c
30.353329 11.106666 30.146662 11.573332 30.039993 12.239999 c
30.039993 18.919998 l
30.039993 19.106665 30.06666 19.243332 30.119993 19.329998 c
30.173326 19.416664 30.22666 19.466663 30.279993 19.479998 c
30.359993 19.519997 30.446661 19.519997 30.539993 19.479998 c
30.539993 19.999998 l
28.119993 19.999998 l
28.119993 19.479998 l
28.22666 19.519997 28.30666 19.519997 28.359993 19.479998 c
28.426661 19.466663 28.493326 19.416664 28.559994 19.329998 c
28.626661 19.243332 28.659994 19.106665 28.659994 18.919998 c
28.659994 11.299998 l
28.659994 11.113332 28.626661 10.976666 28.559994 10.889998 c
28.493326 10.803331 28.426661 10.746665 28.359993 10.719998 c
28.30666 10.693332 28.22666 10.693332 28.119993 10.719998 c
28.119993 10.219998 l
30.039993 10.219998 l
30.039993 11.039998 l
30.133326 10.893332 30.22666 10.756664 30.319994 10.629998 c
30.413328 10.503332 30.523329 10.396666 30.649994 10.309999 c
30.776659 10.223331 30.923326 10.153332 31.089994 10.099999 c
31.256662 10.046665 31.466661 10.019999 31.719994 10.019999 c
32.106663 10.019999 32.466663 10.159999 32.799995 10.439999 c
33.133327 10.719998 33.379997 11.126665 33.539997 11.659999 c
33.540001 11.66 l
h
36.240002 18.92 m
36.240002 19.106667 36.27 19.243334 36.330002 19.33 c
36.390003 19.416666 36.446667 19.466665 36.5 19.48 c
36.566666 19.519999 36.653332 19.519999 36.759998 19.48 c
36.759998 20 l
34.339996 20 l
34.339996 19.48 l
34.446663 19.519999 34.539997 19.519999 34.619995 19.48 c
34.659996 19.466665 34.713329 19.416666 34.779995 19.33 c
34.846661 19.243334 34.879993 19.106667 34.879993 18.92 c
34.879993 6.8 l
34.879993 6.6 34.846661 6.456667 34.779995 6.37 c
34.713329 6.283334 34.659996 6.226667 34.619995 6.2 c
34.539997 6.16 34.446663 6.16 34.339996 6.2 c
34.339996 5.72 l
36.239998 5.72 l
36.239998 6.8 l
36.239998 18.92 l
36.240002 18.92 l
h
40.580002 10.08 m
40.886669 10.08 41.200001 10.133333 41.52 10.24 c
41.84 10.346666 42.126667 10.513333 42.380001 10.74 c
42.633335 10.966666 42.83667 11.253333 42.990002 11.599999 c
43.143333 11.946666 43.220001 12.359999 43.220001 12.839999 c
43.220001 14.859999 l
43.220001 14.919999 l
43.220001 15.379999 l
39.240002 15.379999 l
39.240002 17.439999 l
39.240002 18.266665 39.383335 18.856665 39.670002 19.209999 c
39.956669 19.563334 40.313335 19.733334 40.740002 19.719999 c
40.993336 19.706665 41.243336 19.663332 41.490002 19.59 c
41.736668 19.516668 41.950001 19.393333 42.130001 19.219999 c
42.310001 19.046665 42.460003 18.799997 42.580002 18.48 c
42.700001 18.160002 42.760002 17.740002 42.760002 17.219999 c
43.220001 17.219999 l
43.220001 17.5 l
43.220001 17.860001 43.18 18.203335 43.100002 18.530001 c
43.020004 18.856667 42.880005 19.140001 42.680004 19.380001 c
42.480003 19.620001 42.223339 19.810001 41.910004 19.950001 c
41.596668 20.09 41.213337 20.166666 40.760002 20.18 c
40.440002 20.18 40.113335 20.120001 39.780003 20 c
39.446671 19.879999 39.146667 19.699999 38.880001 19.459999 c
38.613335 19.219999 38.393333 18.936665 38.220001 18.609999 c
38.046669 18.283333 37.960003 17.913332 37.960003 17.499998 c
37.960003 17.029999 l
37.960003 17.029999 37.956669 16.773333 37.950005 16.259998 c
37.94334 15.746665 37.940006 15.429998 37.940006 15.309999 c
37.940006 15.189999 37.936672 14.859999 37.930008 14.319999 c
37.923344 13.779999 37.92001 13.489999 37.92001 13.449999 c
37.92001 12.859999 l
37.92001 12.379998 38.003342 11.963332 38.17001 11.609999 c
38.336678 11.256665 38.543343 10.969999 38.790009 10.749999 c
39.036674 10.529999 39.316673 10.363333 39.630009 10.249999 c
39.943344 10.136665 40.26001 10.079999 40.580009 10.079999 c
40.580002 10.08 l
h
41.880001 14.86 m
41.880001 13.28 l
41.880001 12.773333 41.846668 12.349999 41.780003 12.01 c
41.713337 11.670001 41.623337 11.396668 41.510002 11.190001 c
41.396667 10.983334 41.260002 10.84 41.100002 10.76 c
40.940002 10.68 40.760002 10.64 40.560001 10.64 c
40.133335 10.64 39.810001 10.826667 39.59 11.200001 c
39.369999 11.573335 39.259998 12.266667 39.259998 13.280001 c
39.259998 14.860001 l
41.879997 14.860001 l
41.880001 14.86 l
h
47.880001 19.879999 m
47.866669 19.893333 47.856667 19.9 47.850002 19.9 c
47.843338 19.9 47.833336 19.906666 47.820004 19.92 c
47.800003 19.940001 l
47.773335 19.953335 47.743336 19.966667 47.710003 19.980001 c
47.67667 19.993336 47.646671 20.006668 47.620003 20.020002 c
47.340004 20.140003 47.013336 20.200003 46.640003 20.200003 c
46.42667 20.200003 46.220005 20.150003 46.020004 20.050003 c
45.820004 19.950003 45.646671 19.866671 45.500004 19.800003 c
45.353336 19.733335 45.230003 19.703337 45.130005 19.710003 c
45.030006 19.716669 44.980007 19.813337 44.980003 20.000004 c
44.600002 20.000004 l
44.600002 17.600004 l
45.080002 17.600004 l
45.053333 18.02667 45.100002 18.393337 45.220001 18.700005 c
45.299999 18.966671 45.456669 19.216671 45.690002 19.450005 c
45.923336 19.683338 46.273338 19.800005 46.740002 19.800005 c
47.046669 19.800005 47.299999 19.746672 47.5 19.640005 c
47.846668 19.466671 48.083332 19.260004 48.209999 19.020004 c
48.336666 18.780005 48.386665 18.530005 48.360001 18.270004 c
48.333336 18.010004 48.236668 17.753338 48.07 17.500004 c
47.903332 17.24667 47.699997 17.013336 47.459999 16.800003 c
47.299999 16.680002 47.133331 16.553337 46.959999 16.420004 c
46.786667 16.286671 46.613335 16.146671 46.439999 16.000004 c
45.973331 15.62667 45.603333 15.290004 45.329998 14.990004 c
45.056664 14.690003 44.846664 14.410004 44.699997 14.150003 c
44.553329 13.890003 44.453331 13.643336 44.399998 13.410004 c
44.346664 13.176671 44.32 12.926671 44.319996 12.660004 c
44.319996 12.500004 44.329994 12.353337 44.349995 12.220004 c
44.369995 12.086671 44.393326 11.966671 44.419994 11.860004 c
44.459995 11.633338 44.526661 11.413338 44.619995 11.200005 c
44.713329 10.986671 44.846661 10.796672 45.019997 10.630005 c
45.193333 10.463338 45.406666 10.326672 45.659996 10.220005 c
45.913326 10.113339 46.219994 10.060005 46.579994 10.060005 c
46.779995 10.060005 46.979992 10.103338 47.179993 10.190005 c
47.379993 10.276672 47.559994 10.356672 47.719994 10.430005 c
47.879993 10.503338 48.00666 10.536672 48.099995 10.530006 c
48.193329 10.523339 48.239994 10.420006 48.239994 10.220005 c
48.619995 10.220005 l
48.619995 12.700005 l
48.099995 12.700005 l
48.113327 12.300004 48.066662 11.940004 47.959995 11.620005 c
47.866661 11.340005 47.703327 11.086671 47.469994 10.860004 c
47.23666 10.633338 46.893326 10.520004 46.439995 10.520004 c
46.079994 10.520004 45.786659 10.600004 45.559994 10.760004 c
45.439995 10.853337 45.336658 10.990005 45.249992 11.170004 c
45.163326 11.350003 45.113323 11.54667 45.099991 11.760004 c
45.086658 11.973338 45.119991 12.203338 45.199989 12.450005 c
45.279987 12.696671 45.439987 12.926671 45.679989 13.140005 c
45.706657 13.180005 45.743324 13.220005 45.789989 13.260005 c
45.836655 13.300005 45.886658 13.333338 45.939991 13.360005 c
45.979992 13.386672 46.013325 13.413339 46.039989 13.440005 c
46.119991 13.520005 l
46.266659 13.640005 46.41666 13.760005 46.569992 13.880005 c
46.723324 14.000005 46.879993 14.120005 47.039993 14.240005 c
47.413326 14.560005 47.723328 14.850005 47.969994 15.110004 c
48.21666 15.370004 48.423325 15.613338 48.589993 15.840004 c
48.75666 16.066671 48.879993 16.280004 48.959991 16.480003 c
49.039989 16.680002 49.086658 16.873337 49.099991 17.060003 c
49.233326 17.673336 49.209991 18.190002 49.029991 18.610003 c
48.849991 19.030003 48.619991 19.360003 48.339993 19.600002 c
48.319992 19.600002 l
48.319992 19.626669 48.30666 19.640003 48.279991 19.640003 c
48.253323 19.66667 48.226658 19.68667 48.199989 19.700003 c
48.07999 19.780003 l
48.07999 19.780003 48.039989 19.800003 47.959991 19.840002 c
47.959991 19.860003 l
47.946659 19.860003 47.933323 19.863337 47.919991 19.870003 c
47.906658 19.876669 47.893322 19.880003 47.87999 19.880003 c
47.880001 19.879999 l
h
53.82 19.879999 m
53.806667 19.893333 53.796665 19.9 53.790001 19.9 c
53.783337 19.9 53.773335 19.906666 53.760002 19.92 c
53.740002 19.940001 l
53.713333 19.953335 53.683334 19.966667 53.650002 19.980001 c
53.616669 19.993336 53.58667 20.006668 53.560001 20.020002 c
53.280003 20.140003 52.953335 20.200003 52.580002 20.200003 c
52.366669 20.200003 52.160004 20.150003 51.960003 20.050003 c
51.760002 19.950003 51.58667 19.866671 51.440002 19.800003 c
51.293335 19.733335 51.170002 19.703337 51.070004 19.710003 c
50.970005 19.716669 50.920006 19.813337 50.920002 20.000004 c
50.540001 20.000004 l
50.540001 17.600004 l
51.02 17.600004 l
50.993332 18.02667 51.040001 18.393337 51.16 18.700005 c
51.239998 18.966671 51.396667 19.216671 51.630001 19.450005 c
51.863335 19.683338 52.213337 19.800005 52.68 19.800005 c
52.986668 19.800005 53.239998 19.746672 53.439999 19.640005 c
53.786667 19.466671 54.023331 19.260004 54.149998 19.020004 c
54.276665 18.780005 54.326664 18.530005 54.299999 18.270004 c
54.273335 18.010004 54.176666 17.753338 54.009998 17.500004 c
53.84333 17.24667 53.639996 17.013336 53.399998 16.800003 c
53.239998 16.680002 53.07333 16.553337 52.899998 16.420004 c
52.726665 16.286671 52.553333 16.146671 52.379997 16.000004 c
51.91333 15.62667 51.543331 15.290004 51.269997 14.990004 c
50.996662 14.690003 50.786663 14.410004 50.639996 14.150003 c
50.493328 13.890003 50.39333 13.643336 50.339996 13.410004 c
50.286663 13.176671 50.259998 12.926671 50.259995 12.660004 c
50.259995 12.500004 50.269993 12.353337 50.289993 12.220004 c
50.309994 12.086671 50.333324 11.966671 50.359993 11.860004 c
50.399994 11.633338 50.46666 11.413338 50.559994 11.200005 c
50.653328 10.986671 50.786659 10.796672 50.959995 10.630005 c
51.133331 10.463338 51.346664 10.326672 51.599995 10.220005 c
51.853325 10.113339 52.159992 10.060005 52.519993 10.060005 c
52.719994 10.060005 52.919991 10.103338 53.119991 10.190005 c
53.319992 10.276672 53.499992 10.356672 53.659992 10.430005 c
53.819992 10.503338 53.946659 10.536672 54.039993 10.530006 c
54.133327 10.523339 54.179993 10.420006 54.179993 10.220005 c
54.559994 10.220005 l
54.559994 12.700005 l
54.039993 12.700005 l
54.053326 12.300004 54.00666 11.940004 53.899994 11.620005 c
53.80666 11.340005 53.643326 11.086671 53.409992 10.860004 c
53.176659 10.633338 52.833324 10.520004 52.379993 10.520004 c
52.019993 10.520004 51.726658 10.600004 51.499992 10.760004 c
51.379993 10.853337 51.276657 10.990005 51.189991 11.170004 c
51.103325 11.350003 51.053322 11.54667 51.039989 11.760004 c
51.026657 11.973338 51.05999 12.203338 51.139988 12.450005 c
51.219986 12.696671 51.379986 12.926671 51.619987 13.140005 c
51.646656 13.180005 51.683323 13.220005 51.729988 13.260005 c
51.776653 13.300005 51.826656 13.333338 51.87999 13.360005 c
51.919991 13.386672 51.953323 13.413339 51.979988 13.440005 c
52.05999 13.520005 l
52.206657 13.640005 52.356659 13.760005 52.509991 13.880005 c
52.663322 14.000005 52.819992 14.120005 52.979992 14.240005 c
53.353325 14.560005 53.663326 14.850005 53.909992 15.110004 c
54.156658 15.370004 54.363323 15.613338 54.529991 15.840004 c
54.696659 16.066671 54.819992 16.280004 54.89999 16.480003 c
54.979988 16.680002 55.026657 16.873337 55.039989 17.060003 c
55.173325 17.673336 55.14999 18.190002 54.96999 18.610003 c
54.789989 19.030003 54.55999 19.360003 54.279991 19.600002 c
54.259991 19.600002 l
54.259991 19.626669 54.246658 19.640003 54.21999 19.640003 c
54.193321 19.66667 54.166656 19.68667 54.139988 19.700003 c
54.019989 19.780003 l
54.019989 19.780003 53.979988 19.800003 53.89999 19.840002 c
53.89999 19.860003 l
53.886658 19.860003 53.873322 19.863337 53.859989 19.870003 c
53.846657 19.876669 53.833321 19.880003 53.819988 19.880003 c
53.82 19.879999 l
h
f
Q
Q
Q
showpage
%%PageTrailer
pdfEndPage
%%Trailer
end
%%DocumentSuppliedResources:
%%EOF
================================================
FILE: resources/logo/print/eps/Color logo - no background.eps
================================================
%!PS-Adobe-3.0 EPSF-3.0
%Produced by poppler pdftops version: 0.59.0 (http://poppler.freedesktop.org)
%%Creator: Chromium
%%LanguageLevel: 3
%%DocumentSuppliedResources: (atend)
%%BoundingBox: 0 0 2409 909
%%HiResBoundingBox: 0 0 2409 909
%%DocumentSuppliedResources: (atend)
%%EndComments
%%BeginProlog
%%BeginResource: procset xpdf 3.00 0
%%Copyright: Copyright 1996-2011 Glyph & Cog, LLC
/xpdf 75 dict def xpdf begin
% PDF special state
/pdfDictSize 15 def
/pdfSetup {
/setpagedevice where {
pop 2 dict begin
/Policies 1 dict dup begin /PageSize 6 def end def
{ /Duplex true def } if
currentdict end setpagedevice
} {
pop
} ifelse
} def
/pdfSetupPaper {
% Change paper size, but only if different from previous paper size otherwise
% duplex fails. PLRM specifies a tolerance of 5 pts when matching paper size
% so we use the same when checking if the size changes.
/setpagedevice where {
pop currentpagedevice
/PageSize known {
2 copy
currentpagedevice /PageSize get aload pop
exch 4 1 roll
sub abs 5 gt
3 1 roll
sub abs 5 gt
or
} {
true
} ifelse
{
2 array astore
2 dict begin
/PageSize exch def
/ImagingBBox null def
currentdict end
setpagedevice
} {
pop pop
} ifelse
} {
pop
} ifelse
} def
/pdfStartPage {
pdfDictSize dict begin
/pdfFillCS [] def
/pdfFillXform {} def
/pdfStrokeCS [] def
/pdfStrokeXform {} def
/pdfFill [0] def
/pdfStroke [0] def
/pdfFillOP false def
/pdfStrokeOP false def
/pdfOPM false def
/pdfLastFill false def
/pdfLastStroke false def
/pdfTextMat [1 0 0 1 0 0] def
/pdfFontSize 0 def
/pdfCharSpacing 0 def
/pdfTextRender 0 def
/pdfPatternCS false def
/pdfTextRise 0 def
/pdfWordSpacing 0 def
/pdfHorizScaling 1 def
/pdfTextClipPath [] def
} def
/pdfEndPage { end } def
% PDF color state
/opm { dup /pdfOPM exch def
/setoverprintmode where{pop setoverprintmode}{pop}ifelse } def
/cs { /pdfFillXform exch def dup /pdfFillCS exch def
setcolorspace } def
/CS { /pdfStrokeXform exch def dup /pdfStrokeCS exch def
setcolorspace } def
/sc { pdfLastFill not { pdfFillCS setcolorspace } if
dup /pdfFill exch def aload pop pdfFillXform setcolor
/pdfLastFill true def /pdfLastStroke false def } def
/SC { pdfLastStroke not { pdfStrokeCS setcolorspace } if
dup /pdfStroke exch def aload pop pdfStrokeXform setcolor
/pdfLastStroke true def /pdfLastFill false def } def
/op { /pdfFillOP exch def
pdfLastFill { pdfFillOP setoverprint } if } def
/OP { /pdfStrokeOP exch def
pdfLastStroke { pdfStrokeOP setoverprint } if } def
/fCol {
pdfLastFill not {
pdfFillCS setcolorspace
pdfFill aload pop pdfFillXform setcolor
pdfFillOP setoverprint
/pdfLastFill true def /pdfLastStroke false def
} if
} def
/sCol {
pdfLastStroke not {
pdfStrokeCS setcolorspace
pdfStroke aload pop pdfStrokeXform setcolor
pdfStrokeOP setoverprint
/pdfLastStroke true def /pdfLastFill false def
} if
} def
% build a font
/pdfMakeFont {
4 3 roll findfont
4 2 roll matrix scale makefont
dup length dict begin
{ 1 index /FID ne { def } { pop pop } ifelse } forall
/Encoding exch def
currentdict
end
definefont pop
} def
/pdfMakeFont16 {
exch findfont
dup length dict begin
{ 1 index /FID ne { def } { pop pop } ifelse } forall
/WMode exch def
currentdict
end
definefont pop
} def
/pdfMakeFont16L3 {
1 index /CIDFont resourcestatus {
pop pop 1 index /CIDFont findresource /CIDFontType known
} {
false
} ifelse
{
0 eq { /Identity-H } { /Identity-V } ifelse
exch 1 array astore composefont pop
} {
pdfMakeFont16
} ifelse
} def
% graphics state operators
/q { gsave pdfDictSize dict begin } def
/Q {
end grestore
/pdfLastFill where {
pop
pdfLastFill {
pdfFillOP setoverprint
} {
pdfStrokeOP setoverprint
} ifelse
} if
/pdfOPM where {
pop
pdfOPM /setoverprintmode where{pop setoverprintmode}{pop}ifelse
} if
} def
/cm { concat } def
/d { setdash } def
/i { setflat } def
/j { setlinejoin } def
/J { setlinecap } def
/M { setmiterlimit } def
/w { setlinewidth } def
% path segment operators
/m { moveto } def
/l { lineto } def
/c { curveto } def
/re { 4 2 roll moveto 1 index 0 rlineto 0 exch rlineto
neg 0 rlineto closepath } def
/h { closepath } def
% path painting operators
/S { sCol stroke } def
/Sf { fCol stroke } def
/f { fCol fill } def
/f* { fCol eofill } def
% clipping operators
/W { clip newpath } def
/W* { eoclip newpath } def
/Ws { strokepath clip newpath } def
% text state operators
/Tc { /pdfCharSpacing exch def } def
/Tf { dup /pdfFontSize exch def
dup pdfHorizScaling mul exch matrix scale
pdfTextMat matrix concatmatrix dup 4 0 put dup 5 0 put
exch findfont exch makefont setfont } def
/Tr { /pdfTextRender exch def } def
/Tp { /pdfPatternCS exch def } def
/Ts { /pdfTextRise exch def } def
/Tw { /pdfWordSpacing exch def } def
/Tz { /pdfHorizScaling exch def } def
% text positioning operators
/Td { pdfTextMat transform moveto } def
/Tm { /pdfTextMat exch def } def
% text string operators
/xyshow where {
pop
/xyshow2 {
dup length array
0 2 2 index length 1 sub {
2 index 1 index 2 copy get 3 1 roll 1 add get
pdfTextMat dtransform
4 2 roll 2 copy 6 5 roll put 1 add 3 1 roll dup 4 2 roll put
} for
exch pop
xyshow
} def
}{
/xyshow2 {
currentfont /FontType get 0 eq {
0 2 3 index length 1 sub {
currentpoint 4 index 3 index 2 getinterval show moveto
2 copy get 2 index 3 2 roll 1 add get
pdfTextMat dtransform rmoveto
} for
} {
0 1 3 index length 1 sub {
currentpoint 4 index 3 index 1 getinterval show moveto
2 copy 2 mul get 2 index 3 2 roll 2 mul 1 add get
pdfTextMat dtransform rmoveto
} for
} ifelse
pop pop
} def
} ifelse
/cshow where {
pop
/xycp {
0 3 2 roll
{
pop pop currentpoint 3 2 roll
1 string dup 0 4 3 roll put false charpath moveto
2 copy get 2 index 2 index 1 add get
pdfTextMat dtransform rmoveto
2 add
} exch cshow
pop pop
} def
}{
/xycp {
currentfont /FontType get 0 eq {
0 2 3 index length 1 sub {
currentpoint 4 index 3 index 2 getinterval false charpath moveto
2 copy get 2 index 3 2 roll 1 add get
pdfTextMat dtransform rmoveto
} for
} {
0 1 3 index length 1 sub {
currentpoint 4 index 3 index 1 getinterval false charpath moveto
2 copy 2 mul get 2 index 3 2 roll 2 mul 1 add get
pdfTextMat dtransform rmoveto
} for
} ifelse
pop pop
} def
} ifelse
/Tj {
fCol
0 pdfTextRise pdfTextMat dtransform rmoveto
currentpoint 4 2 roll
pdfTextRender 1 and 0 eq {
2 copy xyshow2
} if
pdfTextRender 3 and dup 1 eq exch 2 eq or {
3 index 3 index moveto
2 copy
currentfont /FontType get 3 eq { fCol } { sCol } ifelse
xycp currentpoint stroke moveto
} if
pdfTextRender 4 and 0 ne {
4 2 roll moveto xycp
/pdfTextClipPath [ pdfTextClipPath aload pop
{/moveto cvx}
{/lineto cvx}
{/curveto cvx}
{/closepath cvx}
pathforall ] def
currentpoint newpath moveto
} {
pop pop pop pop
} ifelse
0 pdfTextRise neg pdfTextMat dtransform rmoveto
} def
/TJm { 0.001 mul pdfFontSize mul pdfHorizScaling mul neg 0
pdfTextMat dtransform rmoveto } def
/TJmV { 0.001 mul pdfFontSize mul neg 0 exch
pdfTextMat dtransform rmoveto } def
/Tclip { pdfTextClipPath cvx exec clip newpath
/pdfTextClipPath [] def } def
/Tclip* { pdfTextClipPath cvx exec eoclip newpath
/pdfTextClipPath [] def } def
% Level 2/3 image operators
/pdfImBuf 100 string def
/pdfImStr {
2 copy exch length lt {
2 copy get exch 1 add exch
} {
()
} ifelse
} def
/skipEOD {
{ currentfile pdfImBuf readline
not { pop exit } if
(%-EOD-) eq { exit } if } loop
} def
/pdfIm { image skipEOD } def
/pdfMask {
/ReusableStreamDecode filter
skipEOD
/maskStream exch def
} def
/pdfMaskEnd { maskStream closefile } def
/pdfMaskInit {
/maskArray exch def
/maskIdx 0 def
} def
/pdfMaskSrc {
maskIdx maskArray length lt {
maskArray maskIdx get
/maskIdx maskIdx 1 add def
} {
()
} ifelse
} def
/pdfImM { fCol imagemask skipEOD } def
/pr { 2 index 2 index 3 2 roll putinterval 4 add } def
/pdfImClip {
gsave
0 2 4 index length 1 sub {
dup 4 index exch 2 copy
get 5 index div put
1 add 3 index exch 2 copy
get 3 index div put
} for
pop pop rectclip
} def
/pdfImClipEnd { grestore } def
% shading operators
/colordelta {
false 0 1 3 index length 1 sub {
dup 4 index exch get 3 index 3 2 roll get sub abs 0.004 gt {
pop true
} if
} for
exch pop exch pop
} def
/funcCol { func n array astore } def
/funcSH {
dup 0 eq {
true
} {
dup 6 eq {
false
} {
4 index 4 index funcCol dup
6 index 4 index funcCol dup
3 1 roll colordelta 3 1 roll
5 index 5 index funcCol dup
3 1 roll colordelta 3 1 roll
6 index 8 index funcCol dup
3 1 roll colordelta 3 1 roll
colordelta or or or
} ifelse
} ifelse
{
1 add
4 index 3 index add 0.5 mul exch 4 index 3 index add 0.5 mul exch
6 index 6 index 4 index 4 index 4 index funcSH
2 index 6 index 6 index 4 index 4 index funcSH
6 index 2 index 4 index 6 index 4 index funcSH
5 3 roll 3 2 roll funcSH pop pop
} {
pop 3 index 2 index add 0.5 mul 3 index 2 index add 0.5 mul
funcCol sc
dup 4 index exch mat transform m
3 index 3 index mat transform l
1 index 3 index mat transform l
mat transform l pop pop h f*
} ifelse
} def
/axialCol {
dup 0 lt {
pop t0
} {
dup 1 gt {
pop t1
} {
dt mul t0 add
} ifelse
} ifelse
func n array astore
} def
/axialSH {
dup 0 eq {
true
} {
dup 8 eq {
false
} {
2 index axialCol 2 index axialCol colordelta
} ifelse
} ifelse
{
1 add 3 1 roll 2 copy add 0.5 mul
dup 4 3 roll exch 4 index axialSH
exch 3 2 roll axialSH
} {
pop 2 copy add 0.5 mul
axialCol sc
exch dup dx mul x0 add exch dy mul y0 add
3 2 roll dup dx mul x0 add exch dy mul y0 add
dx abs dy abs ge {
2 copy yMin sub dy mul dx div add yMin m
yMax sub dy mul dx div add yMax l
2 copy yMax sub dy mul dx div add yMax l
yMin sub dy mul dx div add yMin l
h f*
} {
exch 2 copy xMin sub dx mul dy div add xMin exch m
xMax sub dx mul dy div add xMax exch l
exch 2 copy xMax sub dx mul dy div add xMax exch l
xMin sub dx mul dy div add xMin exch l
h f*
} ifelse
} ifelse
} def
/radialCol {
dup t0 lt {
pop t0
} {
dup t1 gt {
pop t1
} if
} ifelse
func n array astore
} def
/radialSH {
dup 0 eq {
true
} {
dup 8 eq {
false
} {
2 index dt mul t0 add radialCol
2 index dt mul t0 add radialCol colordelta
} ifelse
} ifelse
{
1 add 3 1 roll 2 copy add 0.5 mul
dup 4 3 roll exch 4 index radialSH
exch 3 2 roll radialSH
} {
pop 2 copy add 0.5 mul dt mul t0 add
radialCol sc
encl {
exch dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
0 360 arc h
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
360 0 arcn h f
} {
2 copy
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
a1 a2 arcn
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
a2 a1 arcn h
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
a1 a2 arc
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
a2 a1 arc h f
} ifelse
} ifelse
} def
end
%%EndResource
/CIDInit /ProcSet findresource begin
10 dict begin
begincmap
/CMapType 1 def
/CMapName /Identity-H def
/CIDSystemInfo 3 dict dup begin
/Registry (Adobe) def
/Ordering (Identity) def
/Supplement 0 def
end def
1 begincodespacerange
<0000>
endcodespacerange
0 usefont
1 begincidrange
<0000> 0
endcidrange
endcmap
currentdict CMapName exch /CMap defineresource pop
end
10 dict begin
begincmap
/CMapType 1 def
/CMapName /Identity-V def
/CIDSystemInfo 3 dict dup begin
/Registry (Adobe) def
/Ordering (Identity) def
/Supplement 0 def
end def
/WMode 1 def
1 begincodespacerange
<0000>
endcodespacerange
0 usefont
1 begincidrange
<0000> 0
endcidrange
endcmap
currentdict CMapName exch /CMap defineresource pop
end
end
%%EndProlog
%%BeginSetup
xpdf begin
%%EndSetup
pdfStartPage
%%EndPageSetup
[] 0 d
1 i
0 j
0 J
10 M
1 w
/DeviceGray {} cs
[0] sc
/DeviceGray {} CS
[0] SC
false op
false OP
{} settransfer
0 0 2409 909 re
W
q
[1 0 0 -1 0 909] cm
q
0 0 2409 908.7038 re
W*
q
[0.747904 0 0 0.747904 0 -908.7038] cm
/DeviceRGB {} CS
[1 1 1] SC
/DeviceRGB {} cs
[1 1 1] sc
0 0 3221 2436 re
f
Q
Q
q
5.983235 0 2403.0168 907.20795 re
W*
q
[0.822879 0 0 0.822978 95.330643 145.991699] cm
/DeviceRGB {} CS
[0.09 0.329 0.122] SC
/DeviceRGB {} cs
[0.09 0.329 0.122] sc
231 798 m
227 779 219 741 218 741 c
49 640 69 465 125 365 c
137 491 360 578 230 732 c
229 734 236 758 242 780 c
268 736 307 683 305 678 c
145 288 645 258 749 16 c
796 250 725 612 323 704 c
321 705 250 830 247 831 c
247 829 217 830 221 820 c
223 814 227 806 231 798 c
h
330 625 m
267 476 452 312 544 271 c
356 439 324 564 330 625 c
h
226 704 m
277 645 217 544 181 511 c
242 616 238 677 226 704 c
h
f
Q
q
[26.582434 0 0 26.585632 866.89978 103.116905] cm
/DeviceRGB {} CS
[0.145 0.22 0.059] SC
/DeviceRGB {} cs
[0.145 0.22 0.059] sc
6.5 8.62 m
6.513333 8.940001 6.52 9.303333 6.52 9.71 c
6.52 10.116667 6.513333 10.473333 6.5 10.78 c
6.5 11.139999 6.436667 11.496666 6.31 11.849999 c
6.183333 12.203333 5.993333 12.52 5.74 12.799999 c
5.486666 13.079999 5.17 13.299999 4.79 13.459999 c
4.41 13.619999 3.973334 13.699999 3.48 13.699999 c
2.46 13.699999 l
2.46 18.919998 l
2.46 19.106665 2.486667 19.243332 2.54 19.329998 c
2.593333 19.416664 2.646667 19.466663 2.7 19.479998 c
2.78 19.519997 2.866667 19.519997 2.96 19.479998 c
2.96 19.999998 l
0.54 19.999998 l
0.54 19.479998 l
0.646667 19.519997 0.726667 19.519997 0.78 19.479998 c
0.846667 19.466663 0.913333 19.416664 0.98 19.329998 c
1.046667 19.243332 1.08 19.106665 1.08 18.919998 c
1.08 6.779998 l
1.08 6.579998 1.046667 6.436665 0.98 6.349998 c
0.913333 6.263331 0.846667 6.206665 0.78 6.179998 c
0.726667 6.153331 0.646667 6.153331 0.54 6.179998 c
0.54 5.699998 l
3.48 5.699998 l
3.973334 5.699998 4.41 5.779998 4.79 5.939998 c
5.17 6.099998 5.486666 6.319997 5.74 6.599998 c
5.993333 6.879998 6.183333 7.193331 6.31 7.539998 c
6.436667 7.886664 6.5 8.246664 6.5 8.619998 c
6.5 8.62 l
h
5.08 8.18 m
5.08 7.833334 5.023333 7.533334 4.91 7.28 c
4.796667 7.026667 4.663333 6.826667 4.51 6.68 c
4.356667 6.533334 4.196666 6.416667 4.03 6.33 c
3.863333 6.243334 3.72 6.2 3.6 6.2 c
2.46 6.2 l
2.46 13.16 l
3.599999 13.16 l
3.72 13.16 3.863333 13.126666 4.029999 13.06 c
4.196666 12.993333 4.356666 12.876666 4.509999 12.709999 c
4.663333 12.543332 4.796666 12.339999 4.909999 12.099999 c
5.023333 11.86 5.079999 11.56 5.079999 11.2 c
5.079999 8.18 l
5.08 8.18 l
h
14.58 19.139999 m
14.58 19.246666 14.553333 19.356667 14.5 19.469999 c
14.446667 19.583332 14.37 19.689999 14.270001 19.789999 c
14.170001 19.889999 14.043334 19.98 13.89 20.059999 c
13.736667 20.139999 13.56 20.18 13.360001 20.18 c
13.040001 20.18 12.753334 20.106667 12.500001 19.960001 c
12.246668 19.813335 12.073335 19.620001 11.980001 19.380001 c
11.900002 19.246668 11.840001 19.093334 11.800001 18.920002 c
11.626668 19.280003 11.363335 19.580002 11.010001 19.820002 c
10.656668 20.060001 10.266668 20.18 9.840001 20.180002 c
9.306667 20.180002 8.840001 19.98667 8.440002 19.600002 c
8.213335 19.400003 8.026669 19.140003 7.880002 18.820002 c
7.733335 18.5 7.646668 18.133333 7.620002 17.720001 c
7.606669 17.680002 7.600002 17.600002 7.600002 17.480001 c
7.600002 17.460001 l
7.613335 17.233334 7.653335 16.996668 7.720002 16.750002 c
7.786668 16.503336 7.890002 16.25667 8.030002 16.010002 c
8.170002 15.763335 8.336668 15.530002 8.530002 15.310002 c
8.723335 15.090003 8.933335 14.900003 9.160002 14.740003 c
9.213335 14.713336 9.256668 14.683336 9.290002 14.650003 c
9.323336 14.616669 9.373335 14.580003 9.440002 14.540003 c
9.500002 14.510003 l
9.560002 14.480003 l
9.800002 14.333337 10.010002 14.200004 10.190002 14.080004 c
10.370003 13.960004 10.533336 13.846671 10.680002 13.740004 c
10.760002 13.68667 10.840002 13.62667 10.920002 13.560003 c
11.000002 13.493337 11.086669 13.42667 11.180002 13.360004 c
11.300002 13.280004 11.413336 13.18667 11.520002 13.080004 c
11.626669 12.973337 11.706669 12.84667 11.760002 12.700004 c
11.840002 12.56667 11.880002 12.406671 11.880002 12.220003 c
11.880002 12.033336 11.860002 11.86667 11.820002 11.720003 c
11.766668 11.42667 11.630002 11.173337 11.410002 10.960003 c
11.190002 10.746669 10.946669 10.606669 10.680002 10.540003 c
10.560002 10.500003 10.446669 10.480002 10.340002 10.480002 c
9.966668 10.480002 9.646668 10.546669 9.380002 10.680002 c
9.206669 10.760002 9.080002 10.850002 9.000002 10.950003 c
8.920002 11.050003 8.876669 11.143336 8.870002 11.230002 c
8.863335 11.316669 8.880002 11.386668 8.920002 11.440002 c
8.960002 11.493337 9.013335 11.533337 9.080002 11.560002 c
9.106669 11.573336 9.123335 11.580003 9.130002 11.580003 c
9.136669 11.580003 9.153336 11.58667 9.180002 11.600003 c
9.353335 11.66667 9.496669 11.783337 9.610003 11.950004 c
9.723336 12.116671 9.780003 12.300004 9.780003 12.500004 c
9.780003 12.753337 9.690002 12.970004 9.510002 13.150003 c
9.330002 13.330003 9.113336 13.420003 8.860003 13.420004 c
8.593336 13.420004 8.36667 13.330004 8.180002 13.150003 c
7.993335 12.970003 7.900002 12.753337 7.900002 12.500004 c
7.900002 12.273337 7.930002 12.036671 7.990002 11.790004 c
8.050002 11.543337 8.140002 11.320004 8.260002 11.120004 c
8.513335 10.74667 8.816669 10.47667 9.170002 10.310003 c
9.523336 10.143336 9.913335 10.060003 10.340002 10.060003 c
10.660002 10.060003 10.976668 10.120004 11.290002 10.240004 c
11.603335 10.360004 11.883335 10.530004 12.130002 10.750004 c
12.376669 10.970004 12.583335 11.233337 12.750002 11.540004 c
12.916669 11.84667 13.000002 12.200004 13.000002 12.600004 c
13.000002 18.480003 l
13.000002 18.720003 13.003335 18.876671 13.010002 18.950003 c
13.016669 19.023335 13.020002 19.073336 13.020002 19.100002 c
13.020002 19.273336 13.073336 19.413336 13.180002 19.520002 c
13.286669 19.626669 13.413336 19.680002 13.560002 19.680002 c
13.733335 19.680002 13.873336 19.630003 13.980002 19.530003 c
14.086669 19.430002 14.146669 19.300003 14.160003 19.140003 c
14.580003 19.140003 l
14.58 19.139999 l
h
11.44 18.639999 m
11.546666 18.453333 11.61 18.253332 11.629999 18.039999 c
11.649999 17.826666 11.659999 17.626665 11.659999 17.439999 c
11.659999 13.739999 l
11.606666 13.793332 11.549999 13.843332 11.489999 13.889998 c
11.429998 13.936665 11.366666 13.993332 11.299999 14.059999 c
10.913333 14.339998 10.583333 14.596665 10.31 14.829998 c
10.036666 15.063331 9.806666 15.303331 9.619999 15.549998 c
9.433332 15.796665 9.286665 16.06 9.179999 16.339998 c
9.073334 16.619999 9.006667 16.939999 8.98 17.299999 c
8.98 17.599998 l
8.993333 18.106665 9.13 18.503332 9.389999 18.789999 c
9.649999 19.076666 10.019999 19.219999 10.499999 19.219999 c
10.713332 19.219999 10.906666 19.166666 11.079999 19.059999 c
11.253332 18.939999 11.373332 18.799999 11.439999 18.639999 c
11.44 18.639999 l
h
18.32 10.08 m
19.08 10.093333 19.686666 10.323334 20.139999 10.770001 c
20.593332 11.216667 20.82 11.926667 20.82 12.900001 c
20.82 17.380001 l
20.82 18.353334 20.593332 19.059999 20.139999 19.5 c
19.686666 19.940001 19.08 20.173334 18.32 20.200001 c
18.26 20.200001 l
18.033333 20.200001 17.826666 20.173334 17.639999 20.120001 c
17.619999 20.120001 l
17.606665 20.120001 17.599998 20.113335 17.599998 20.1 c
17.293333 19.993334 17.026667 19.846666 16.799999 19.66 c
16.609999 19.469999 l
16.609999 19.469999 16.56 19.4 16.459999 19.26 c
16.459999 22.58 l
16.459999 22.766666 16.486666 22.903334 16.539999 22.99 c
16.593332 23.076666 16.646666 23.133333 16.699999 23.16 c
16.779999 23.199999 16.866667 23.199999 16.959999 23.16 c
16.959999 23.66 l
14.539999 23.66 l
14.539999 23.16 l
14.646666 23.199999 14.726666 23.199999 14.779999 23.16 c
14.846665 23.133333 14.909999 23.076666 14.969998 22.99 c
15.029998 22.903334 15.059998 22.766666 15.059999 22.58 c
15.059999 11.3 l
15.059999 11.113334 15.029999 10.976667 14.969998 10.89 c
14.909998 10.803333 14.846665 10.746667 14.779999 10.72 c
14.726666 10.693334 14.646666 10.693334 14.539999 10.72 c
14.539999 10.24 l
16.459999 10.24 l
16.459999 11.02 l
16.553331 10.873333 16.666666 10.74 16.799999 10.62 c
17.026667 10.42 17.293333 10.273334 17.599998 10.18 c
17.599998 10.166667 17.606665 10.16 17.619999 10.16 c
17.639999 10.16 l
17.853333 10.106667 18.059999 10.08 18.26 10.08 c
18.32 10.08 l
h
19.32 12.46 m
19.32 12.44 l
19.306665 11.76 19.223333 11.28 19.07 11 c
18.916666 10.72 18.666666 10.566667 18.32 10.54 c
18.08 10.526667 17.853333 10.556666 17.639999 10.63 c
17.426666 10.703334 17.24 10.826667 17.08 11 c
16.92 11.173333 16.786667 11.406666 16.68 11.7 c
16.573334 11.993334 16.5 12.34 16.460001 12.74 c
16.460001 17.540001 l
16.5 17.953335 16.573334 18.303335 16.68 18.59 c
16.786667 18.876665 16.92 19.103333 17.08 19.27 c
17.24 19.436668 17.426666 19.560001 17.639999 19.640001 c
17.853333 19.720001 18.08 19.753334 18.32 19.740002 c
18.666666 19.713335 18.916666 19.560001 19.07 19.280003 c
19.223333 19.000004 19.306665 18.520004 19.32 17.840002 c
19.32 17.820002 l
19.32 12.460001 l
19.32 12.46 l
h
24.379999 10.08 m
24.686665 10.08 24.999998 10.133333 25.32 10.24 c
25.640001 10.346666 25.926668 10.513333 26.18 10.74 c
26.433332 10.966666 26.636667 11.253333 26.790001 11.599999 c
26.943335 11.946666 27.020002 12.359999 27.02 12.839999 c
27.02 14.859999 l
27.02 14.919999 l
27.02 15.379999 l
23.040001 15.379999 l
23.040001 17.439999 l
23.040001 18.266665 23.183334 18.856665 23.470001 19.209999 c
23.756668 19.563334 24.113335 19.733334 24.540001 19.719999 c
24.793333 19.706665 25.043333 19.663332 25.290001 19.59 c
25.536669 19.516668 25.750002 19.393333 25.93 19.219999 c
26.109999 19.046665 26.26 18.799997 26.380001 18.48 c
26.500002 18.160002 26.560003 17.740002 26.560001 17.219999 c
27.02 17.219999 l
27.02 17.5 l
27.02 17.860001 26.98 18.203335 26.9 18.530001 c
26.82 18.856667 26.679998 19.140001 26.48 19.380001 c
26.280001 19.620001 26.023333 19.810001 25.709999 19.950001 c
25.396666 20.09 25.013332 20.166666 24.559999 20.18 c
24.24 20.18 23.913334 20.120001 23.58 20 c
23.246666 19.879999 22.946667 19.699999 22.68 19.459999 c
22.413334 19.219999 22.193335 18.936665 22.02 18.609999 c
21.846666 18.283333 21.76 17.913332 21.76 17.499998 c
21.76 17.029999 l
21.76 17.029999 21.756666 16.773333 21.75 16.259998 c
21.743334 15.746665 21.74 15.429998 21.74 15.309999 c
21.74 15.189999 21.736666 14.859999 21.73 14.319999 c
21.723333 13.779999 21.719999 13.489999 21.719999 13.449999 c
21.719999 12.859999 l
21.719999 12.379998 21.803333 11.963332 21.969999 11.609999 c
22.136665 11.256665 22.343332 10.969999 22.59 10.749999 c
22.836668 10.529999 23.116667 10.363333 23.43 10.249999 c
23.743334 10.136665 24.059999 10.079999 24.380001 10.079999 c
24.379999 10.08 l
h
25.68 14.86 m
25.68 13.28 l
25.68 12.773333 25.646667 12.349999 25.58 12.01 c
25.513332 11.670001 25.423332 11.396668 25.309999 11.190001 c
25.196667 10.983334 25.059999 10.84 24.9 10.76 c
24.74 10.68 24.559999 10.64 24.359999 10.64 c
23.933332 10.64 23.609999 10.826667 23.389999 11.200001 c
23.17 11.573335 23.059999 12.266667 23.059999 13.280001 c
23.059999 14.860001 l
25.68 14.860001 l
25.68 14.86 l
h
33.540001 11.66 m
33.540001 11.686667 33.546669 11.74 33.560001 11.82 c
33.573334 11.9 33.573334 11.966666 33.560001 12.02 c
33.546669 12.299999 33.450001 12.53 33.27 12.709999 c
33.09 12.889998 32.873333 12.979999 32.619999 12.98 c
32.353333 12.98 32.129997 12.886666 31.949999 12.7 c
31.77 12.513333 31.68 12.286667 31.679998 12.02 c
31.679998 11.686667 31.806665 11.433332 32.059998 11.259999 c
32.086666 11.246666 32.103333 11.236666 32.109997 11.23 c
32.139996 11.2 l
32.23333 11.133333 32.293327 11.036666 32.319996 10.91 c
32.346664 10.783334 32.266663 10.673333 32.079994 10.58 c
31.959993 10.513333 31.839994 10.48 31.719994 10.48 c
31.319994 10.453333 30.96666 10.573333 30.659994 10.839999 c
30.353329 11.106666 30.146662 11.573332 30.039993 12.239999 c
30.039993 18.919998 l
30.039993 19.106665 30.06666 19.243332 30.119993 19.329998 c
30.173326 19.416664 30.22666 19.466663 30.279993 19.479998 c
30.359993 19.519997 30.446661 19.519997 30.539993 19.479998 c
30.539993 19.999998 l
28.119993 19.999998 l
28.119993 19.479998 l
28.22666 19.519997 28.30666 19.519997 28.359993 19.479998 c
28.426661 19.466663 28.493326 19.416664 28.559994 19.329998 c
28.626661 19.243332 28.659994 19.106665 28.659994 18.919998 c
28.659994 11.299998 l
28.659994 11.113332 28.626661 10.976666 28.559994 10.889998 c
28.493326 10.803331 28.426661 10.746665 28.359993 10.719998 c
28.30666 10.693332 28.22666 10.693332 28.119993 10.719998 c
28.119993 10.219998 l
30.039993 10.219998 l
30.039993 11.039998 l
30.133326 10.893332 30.22666 10.756664 30.319994 10.629998 c
30.413328 10.503332 30.523329 10.396666 30.649994 10.309999 c
30.776659 10.223331 30.923326 10.153332 31.089994 10.099999 c
31.256662 10.046665 31.466661 10.019999 31.719994 10.019999 c
32.106663 10.019999 32.466663 10.159999 32.799995 10.439999 c
33.133327 10.719998 33.379997 11.126665 33.539997 11.659999 c
33.540001 11.66 l
h
36.240002 18.92 m
36.240002 19.106667 36.27 19.243334 36.330002 19.33 c
36.390003 19.416666 36.446667 19.466665 36.5 19.48 c
36.566666 19.519999 36.653332 19.519999 36.759998 19.48 c
36.759998 20 l
34.339996 20 l
34.339996 19.48 l
34.446663 19.519999 34.539997 19.519999 34.619995 19.48 c
34.659996 19.466665 34.713329 19.416666 34.779995 19.33 c
34.846661 19.243334 34.879993 19.106667 34.879993 18.92 c
34.879993 6.8 l
34.879993 6.6 34.846661 6.456667 34.779995 6.37 c
34.713329 6.283334 34.659996 6.226667 34.619995 6.2 c
34.539997 6.16 34.446663 6.16 34.339996 6.2 c
34.339996 5.72 l
36.239998 5.72 l
36.239998 6.8 l
36.239998 18.92 l
36.240002 18.92 l
h
40.580002 10.08 m
40.886669 10.08 41.200001 10.133333 41.52 10.24 c
41.84 10.346666 42.126667 10.513333 42.380001 10.74 c
42.633335 10.966666 42.83667 11.253333 42.990002 11.599999 c
43.143333 11.946666 43.220001 12.359999 43.220001 12.839999 c
43.220001 14.859999 l
43.220001 14.919999 l
43.220001 15.379999 l
39.240002 15.379999 l
39.240002 17.439999 l
39.240002 18.266665 39.383335 18.856665 39.670002 19.209999 c
39.956669 19.563334 40.313335 19.733334 40.740002 19.719999 c
40.993336 19.706665 41.243336 19.663332 41.490002 19.59 c
41.736668 19.516668 41.950001 19.393333 42.130001 19.219999 c
42.310001 19.046665 42.460003 18.799997 42.580002 18.48 c
42.700001 18.160002 42.760002 17.740002 42.760002 17.219999 c
43.220001 17.219999 l
43.220001 17.5 l
43.220001 17.860001 43.18 18.203335 43.100002 18.530001 c
43.020004 18.856667 42.880005 19.140001 42.680004 19.380001 c
42.480003 19.620001 42.223339 19.810001 41.910004 19.950001 c
41.596668 20.09 41.213337 20.166666 40.760002 20.18 c
40.440002 20.18 40.113335 20.120001 39.780003 20 c
39.446671 19.879999 39.146667 19.699999 38.880001 19.459999 c
38.613335 19.219999 38.393333 18.936665 38.220001 18.609999 c
38.046669 18.283333 37.960003 17.913332 37.960003 17.499998 c
37.960003 17.029999 l
37.960003 17.029999 37.956669 16.773333 37.950005 16.259998 c
37.94334 15.746665 37.940006 15.429998 37.940006 15.309999 c
37.940006 15.189999 37.936672 14.859999 37.930008 14.319999 c
37.923344 13.779999 37.92001 13.489999 37.92001 13.449999 c
37.92001 12.859999 l
37.92001 12.379998 38.003342 11.963332 38.17001 11.609999 c
38.336678 11.256665 38.543343 10.969999 38.790009 10.749999 c
39.036674 10.529999 39.316673 10.363333 39.630009 10.249999 c
39.943344 10.136665 40.26001 10.079999 40.580009 10.079999 c
40.580002 10.08 l
h
41.880001 14.86 m
41.880001 13.28 l
41.880001 12.773333 41.846668 12.349999 41.780003 12.01 c
41.713337 11.670001 41.623337 11.396668 41.510002 11.190001 c
41.396667 10.983334 41.260002 10.84 41.100002 10.76 c
40.940002 10.68 40.760002 10.64 40.560001 10.64 c
40.133335 10.64 39.810001 10.826667 39.59 11.200001 c
39.369999 11.573335 39.259998 12.266667 39.259998 13.280001 c
39.259998 14.860001 l
41.879997 14.860001 l
41.880001 14.86 l
h
47.880001 19.879999 m
47.866669 19.893333 47.856667 19.9 47.850002 19.9 c
47.843338 19.9 47.833336 19.906666 47.820004 19.92 c
47.800003 19.940001 l
47.773335 19.953335 47.743336 19.966667 47.710003 19.980001 c
47.67667 19.993336 47.646671 20.006668 47.620003 20.020002 c
47.340004 20.140003 47.013336 20.200003 46.640003 20.200003 c
46.42667 20.200003 46.220005 20.150003 46.020004 20.050003 c
45.820004 19.950003 45.646671 19.866671 45.500004 19.800003 c
45.353336 19.733335 45.230003 19.703337 45.130005 19.710003 c
45.030006 19.716669 44.980007 19.813337 44.980003 20.000004 c
44.600002 20.000004 l
44.600002 17.600004 l
45.080002 17.600004 l
45.053333 18.02667 45.100002 18.393337 45.220001 18.700005 c
45.299999 18.966671 45.456669 19.216671 45.690002 19.450005 c
45.923336 19.683338 46.273338 19.800005 46.740002 19.800005 c
47.046669 19.800005 47.299999 19.746672 47.5 19.640005 c
47.846668 19.466671 48.083332 19.260004 48.209999 19.020004 c
48.336666 18.780005 48.386665 18.530005 48.360001 18.270004 c
48.333336 18.010004 48.236668 17.753338 48.07 17.500004 c
47.903332 17.24667 47.699997 17.013336 47.459999 16.800003 c
47.299999 16.680002 47.133331 16.553337 46.959999 16.420004 c
46.786667 16.286671 46.613335 16.146671 46.439999 16.000004 c
45.973331 15.62667 45.603333 15.290004 45.329998 14.990004 c
45.056664 14.690003 44.846664 14.410004 44.699997 14.150003 c
44.553329 13.890003 44.453331 13.643336 44.399998 13.410004 c
44.346664 13.176671 44.32 12.926671 44.319996 12.660004 c
44.319996 12.500004 44.329994 12.353337 44.349995 12.220004 c
44.369995 12.086671 44.393326 11.966671 44.419994 11.860004 c
44.459995 11.633338 44.526661 11.413338 44.619995 11.200005 c
44.713329 10.986671 44.846661 10.796672 45.019997 10.630005 c
45.193333 10.463338 45.406666 10.326672 45.659996 10.220005 c
45.913326 10.113339 46.219994 10.060005 46.579994 10.060005 c
46.779995 10.060005 46.979992 10.103338 47.179993 10.190005 c
47.379993 10.276672 47.559994 10.356672 47.719994 10.430005 c
47.879993 10.503338 48.00666 10.536672 48.099995 10.530006 c
48.193329 10.523339 48.239994 10.420006 48.239994 10.220005 c
48.619995 10.220005 l
48.619995 12.700005 l
48.099995 12.700005 l
48.113327 12.300004 48.066662 11.940004 47.959995 11.620005 c
47.866661 11.340005 47.703327 11.086671 47.469994 10.860004 c
47.23666 10.633338 46.893326 10.520004 46.439995 10.520004 c
46.079994 10.520004 45.786659 10.600004 45.559994 10.760004 c
45.439995 10.853337 45.336658 10.990005 45.249992 11.170004 c
45.163326 11.350003 45.113323 11.54667 45.099991 11.760004 c
45.086658 11.973338 45.119991 12.203338 45.199989 12.450005 c
45.279987 12.696671 45.439987 12.926671 45.679989 13.140005 c
45.706657 13.180005 45.743324 13.220005 45.789989 13.260005 c
45.836655 13.300005 45.886658 13.333338 45.939991 13.360005 c
45.979992 13.386672 46.013325 13.413339 46.039989 13.440005 c
46.119991 13.520005 l
46.266659 13.640005 46.41666 13.760005 46.569992 13.880005 c
46.723324 14.000005 46.879993 14.120005 47.039993 14.240005 c
47.413326 14.560005 47.723328 14.850005 47.969994 15.110004 c
48.21666 15.370004 48.423325 15.613338 48.589993 15.840004 c
48.75666 16.066671 48.879993 16.280004 48.959991 16.480003 c
49.039989 16.680002 49.086658 16.873337 49.099991 17.060003 c
49.233326 17.673336 49.209991 18.190002 49.029991 18.610003 c
48.849991 19.030003 48.619991 19.360003 48.339993 19.600002 c
48.319992 19.600002 l
48.319992 19.626669 48.30666 19.640003 48.279991 19.640003 c
48.253323 19.66667 48.226658 19.68667 48.199989 19.700003 c
48.07999 19.780003 l
48.07999 19.780003 48.039989 19.800003 47.959991 19.840002 c
47.959991 19.860003 l
47.946659 19.860003 47.933323 19.863337 47.919991 19.870003 c
47.906658 19.876669 47.893322 19.880003 47.87999 19.880003 c
47.880001 19.879999 l
h
53.82 19.879999 m
53.806667 19.893333 53.796665 19.9 53.790001 19.9 c
53.783337 19.9 53.773335 19.906666 53.760002 19.92 c
53.740002 19.940001 l
53.713333 19.953335 53.683334 19.966667 53.650002 19.980001 c
53.616669 19.993336 53.58667 20.006668 53.560001 20.020002 c
53.280003 20.140003 52.953335 20.200003 52.580002 20.200003 c
52.366669 20.200003 52.160004 20.150003 51.960003 20.050003 c
51.760002 19.950003 51.58667 19.866671 51.440002 19.800003 c
51.293335 19.733335 51.170002 19.703337 51.070004 19.710003 c
50.970005 19.716669 50.920006 19.813337 50.920002 20.000004 c
50.540001 20.000004 l
50.540001 17.600004 l
51.02 17.600004 l
50.993332 18.02667 51.040001 18.393337 51.16 18.700005 c
51.239998 18.966671 51.396667 19.216671 51.630001 19.450005 c
51.863335 19.683338 52.213337 19.800005 52.68 19.800005 c
52.986668 19.800005 53.239998 19.746672 53.439999 19.640005 c
53.786667 19.466671 54.023331 19.260004 54.149998 19.020004 c
54.276665 18.780005 54.326664 18.530005 54.299999 18.270004 c
54.273335 18.010004 54.176666 17.753338 54.009998 17.500004 c
53.84333 17.24667 53.639996 17.013336 53.399998 16.800003 c
53.239998 16.680002 53.07333 16.553337 52.899998 16.420004 c
52.726665 16.286671 52.553333 16.146671 52.379997 16.000004 c
51.91333 15.62667 51.543331 15.290004 51.269997 14.990004 c
50.996662 14.690003 50.786663 14.410004 50.639996 14.150003 c
50.493328 13.890003 50.39333 13.643336 50.339996 13.410004 c
50.286663 13.176671 50.259998 12.926671 50.259995 12.660004 c
50.259995 12.500004 50.269993 12.353337 50.289993 12.220004 c
50.309994 12.086671 50.333324 11.966671 50.359993 11.860004 c
50.399994 11.633338 50.46666 11.413338 50.559994 11.200005 c
50.653328 10.986671 50.786659 10.796672 50.959995 10.630005 c
51.133331 10.463338 51.346664 10.326672 51.599995 10.220005 c
51.853325 10.113339 52.159992 10.060005 52.519993 10.060005 c
52.719994 10.060005 52.919991 10.103338 53.119991 10.190005 c
53.319992 10.276672 53.499992 10.356672 53.659992 10.430005 c
53.819992 10.503338 53.946659 10.536672 54.039993 10.530006 c
54.133327 10.523339 54.179993 10.420006 54.179993 10.220005 c
54.559994 10.220005 l
54.559994 12.700005 l
54.039993 12.700005 l
54.053326 12.300004 54.00666 11.940004 53.899994 11.620005 c
53.80666 11.340005 53.643326 11.086671 53.409992 10.860004 c
53.176659 10.633338 52.833324 10.520004 52.379993 10.520004 c
52.019993 10.520004 51.726658 10.600004 51.499992 10.760004 c
51.379993 10.853337 51.276657 10.990005 51.189991 11.170004 c
51.103325 11.350003 51.053322 11.54667 51.039989 11.760004 c
51.026657 11.973338 51.05999 12.203338 51.139988 12.450005 c
51.219986 12.696671 51.379986 12.926671 51.619987 13.140005 c
51.646656 13.180005 51.683323 13.220005 51.729988 13.260005 c
51.776653 13.300005 51.826656 13.333338 51.87999 13.360005 c
51.919991 13.386672 51.953323 13.413339 51.979988 13.440005 c
52.05999 13.520005 l
52.206657 13.640005 52.356659 13.760005 52.509991 13.880005 c
52.663322 14.000005 52.819992 14.120005 52.979992 14.240005 c
53.353325 14.560005 53.663326 14.850005 53.909992 15.110004 c
54.156658 15.370004 54.363323 15.613338 54.529991 15.840004 c
54.696659 16.066671 54.819992 16.280004 54.89999 16.480003 c
54.979988 16.680002 55.026657 16.873337 55.039989 17.060003 c
55.173325 17.673336 55.14999 18.190002 54.96999 18.610003 c
54.789989 19.030003 54.55999 19.360003 54.279991 19.600002 c
54.259991 19.600002 l
54.259991 19.626669 54.246658 19.640003 54.21999 19.640003 c
54.193321 19.66667 54.166656 19.68667 54.139988 19.700003 c
54.019989 19.780003 l
54.019989 19.780003 53.979988 19.800003 53.89999 19.840002 c
53.89999 19.860003 l
53.886658 19.860003 53.873322 19.863337 53.859989 19.870003 c
53.846657 19.876669 53.833321 19.880003 53.819988 19.880003 c
53.82 19.879999 l
h
f
Q
Q
Q
showpage
%%PageTrailer
pdfEndPage
%%Trailer
end
%%DocumentSuppliedResources:
%%EOF
================================================
FILE: resources/logo/print/eps/Color logo with background.eps
================================================
%!PS-Adobe-3.0 EPSF-3.0
%Produced by poppler pdftops version: 0.59.0 (http://poppler.freedesktop.org)
%%Creator: Chromium
%%LanguageLevel: 3
%%DocumentSuppliedResources: (atend)
%%BoundingBox: 0 0 2409 909
%%HiResBoundingBox: 0 0 2409 909
%%DocumentSuppliedResources: (atend)
%%EndComments
%%BeginProlog
%%BeginResource: procset xpdf 3.00 0
%%Copyright: Copyright 1996-2011 Glyph & Cog, LLC
/xpdf 75 dict def xpdf begin
% PDF special state
/pdfDictSize 15 def
/pdfSetup {
/setpagedevice where {
pop 2 dict begin
/Policies 1 dict dup begin /PageSize 6 def end def
{ /Duplex true def } if
currentdict end setpagedevice
} {
pop
} ifelse
} def
/pdfSetupPaper {
% Change paper size, but only if different from previous paper size otherwise
% duplex fails. PLRM specifies a tolerance of 5 pts when matching paper size
% so we use the same when checking if the size changes.
/setpagedevice where {
pop currentpagedevice
/PageSize known {
2 copy
currentpagedevice /PageSize get aload pop
exch 4 1 roll
sub abs 5 gt
3 1 roll
sub abs 5 gt
or
} {
true
} ifelse
{
2 array astore
2 dict begin
/PageSize exch def
/ImagingBBox null def
currentdict end
setpagedevice
} {
pop pop
} ifelse
} {
pop
} ifelse
} def
/pdfStartPage {
pdfDictSize dict begin
/pdfFillCS [] def
/pdfFillXform {} def
/pdfStrokeCS [] def
/pdfStrokeXform {} def
/pdfFill [0] def
/pdfStroke [0] def
/pdfFillOP false def
/pdfStrokeOP false def
/pdfOPM false def
/pdfLastFill false def
/pdfLastStroke false def
/pdfTextMat [1 0 0 1 0 0] def
/pdfFontSize 0 def
/pdfCharSpacing 0 def
/pdfTextRender 0 def
/pdfPatternCS false def
/pdfTextRise 0 def
/pdfWordSpacing 0 def
/pdfHorizScaling 1 def
/pdfTextClipPath [] def
} def
/pdfEndPage { end } def
% PDF color state
/opm { dup /pdfOPM exch def
/setoverprintmode where{pop setoverprintmode}{pop}ifelse } def
/cs { /pdfFillXform exch def dup /pdfFillCS exch def
setcolorspace } def
/CS { /pdfStrokeXform exch def dup /pdfStrokeCS exch def
setcolorspace } def
/sc { pdfLastFill not { pdfFillCS setcolorspace } if
dup /pdfFill exch def aload pop pdfFillXform setcolor
/pdfLastFill true def /pdfLastStroke false def } def
/SC { pdfLastStroke not { pdfStrokeCS setcolorspace } if
dup /pdfStroke exch def aload pop pdfStrokeXform setcolor
/pdfLastStroke true def /pdfLastFill false def } def
/op { /pdfFillOP exch def
pdfLastFill { pdfFillOP setoverprint } if } def
/OP { /pdfStrokeOP exch def
pdfLastStroke { pdfStrokeOP setoverprint } if } def
/fCol {
pdfLastFill not {
pdfFillCS setcolorspace
pdfFill aload pop pdfFillXform setcolor
pdfFillOP setoverprint
/pdfLastFill true def /pdfLastStroke false def
} if
} def
/sCol {
pdfLastStroke not {
pdfStrokeCS setcolorspace
pdfStroke aload pop pdfStrokeXform setcolor
pdfStrokeOP setoverprint
/pdfLastStroke true def /pdfLastFill false def
} if
} def
% build a font
/pdfMakeFont {
4 3 roll findfont
4 2 roll matrix scale makefont
dup length dict begin
{ 1 index /FID ne { def } { pop pop } ifelse } forall
/Encoding exch def
currentdict
end
definefont pop
} def
/pdfMakeFont16 {
exch findfont
dup length dict begin
{ 1 index /FID ne { def } { pop pop } ifelse } forall
/WMode exch def
currentdict
end
definefont pop
} def
/pdfMakeFont16L3 {
1 index /CIDFont resourcestatus {
pop pop 1 index /CIDFont findresource /CIDFontType known
} {
false
} ifelse
{
0 eq { /Identity-H } { /Identity-V } ifelse
exch 1 array astore composefont pop
} {
pdfMakeFont16
} ifelse
} def
% graphics state operators
/q { gsave pdfDictSize dict begin } def
/Q {
end grestore
/pdfLastFill where {
pop
pdfLastFill {
pdfFillOP setoverprint
} {
pdfStrokeOP setoverprint
} ifelse
} if
/pdfOPM where {
pop
pdfOPM /setoverprintmode where{pop setoverprintmode}{pop}ifelse
} if
} def
/cm { concat } def
/d { setdash } def
/i { setflat } def
/j { setlinejoin } def
/J { setlinecap } def
/M { setmiterlimit } def
/w { setlinewidth } def
% path segment operators
/m { moveto } def
/l { lineto } def
/c { curveto } def
/re { 4 2 roll moveto 1 index 0 rlineto 0 exch rlineto
neg 0 rlineto closepath } def
/h { closepath } def
% path painting operators
/S { sCol stroke } def
/Sf { fCol stroke } def
/f { fCol fill } def
/f* { fCol eofill } def
% clipping operators
/W { clip newpath } def
/W* { eoclip newpath } def
/Ws { strokepath clip newpath } def
% text state operators
/Tc { /pdfCharSpacing exch def } def
/Tf { dup /pdfFontSize exch def
dup pdfHorizScaling mul exch matrix scale
pdfTextMat matrix concatmatrix dup 4 0 put dup 5 0 put
exch findfont exch makefont setfont } def
/Tr { /pdfTextRender exch def } def
/Tp { /pdfPatternCS exch def } def
/Ts { /pdfTextRise exch def } def
/Tw { /pdfWordSpacing exch def } def
/Tz { /pdfHorizScaling exch def } def
% text positioning operators
/Td { pdfTextMat transform moveto } def
/Tm { /pdfTextMat exch def } def
% text string operators
/xyshow where {
pop
/xyshow2 {
dup length array
0 2 2 index length 1 sub {
2 index 1 index 2 copy get 3 1 roll 1 add get
pdfTextMat dtransform
4 2 roll 2 copy 6 5 roll put 1 add 3 1 roll dup 4 2 roll put
} for
exch pop
xyshow
} def
}{
/xyshow2 {
currentfont /FontType get 0 eq {
0 2 3 index length 1 sub {
currentpoint 4 index 3 index 2 getinterval show moveto
2 copy get 2 index 3 2 roll 1 add get
pdfTextMat dtransform rmoveto
} for
} {
0 1 3 index length 1 sub {
currentpoint 4 index 3 index 1 getinterval show moveto
2 copy 2 mul get 2 index 3 2 roll 2 mul 1 add get
pdfTextMat dtransform rmoveto
} for
} ifelse
pop pop
} def
} ifelse
/cshow where {
pop
/xycp {
0 3 2 roll
{
pop pop currentpoint 3 2 roll
1 string dup 0 4 3 roll put false charpath moveto
2 copy get 2 index 2 index 1 add get
pdfTextMat dtransform rmoveto
2 add
} exch cshow
pop pop
} def
}{
/xycp {
currentfont /FontType get 0 eq {
0 2 3 index length 1 sub {
currentpoint 4 index 3 index 2 getinterval false charpath moveto
2 copy get 2 index 3 2 roll 1 add get
pdfTextMat dtransform rmoveto
} for
} {
0 1 3 index length 1 sub {
currentpoint 4 index 3 index 1 getinterval false charpath moveto
2 copy 2 mul get 2 index 3 2 roll 2 mul 1 add get
pdfTextMat dtransform rmoveto
} for
} ifelse
pop pop
} def
} ifelse
/Tj {
fCol
0 pdfTextRise pdfTextMat dtransform rmoveto
currentpoint 4 2 roll
pdfTextRender 1 and 0 eq {
2 copy xyshow2
} if
pdfTextRender 3 and dup 1 eq exch 2 eq or {
3 index 3 index moveto
2 copy
currentfont /FontType get 3 eq { fCol } { sCol } ifelse
xycp currentpoint stroke moveto
} if
pdfTextRender 4 and 0 ne {
4 2 roll moveto xycp
/pdfTextClipPath [ pdfTextClipPath aload pop
{/moveto cvx}
{/lineto cvx}
{/curveto cvx}
{/closepath cvx}
pathforall ] def
currentpoint newpath moveto
} {
pop pop pop pop
} ifelse
0 pdfTextRise neg pdfTextMat dtransform rmoveto
} def
/TJm { 0.001 mul pdfFontSize mul pdfHorizScaling mul neg 0
pdfTextMat dtransform rmoveto } def
/TJmV { 0.001 mul pdfFontSize mul neg 0 exch
pdfTextMat dtransform rmoveto } def
/Tclip { pdfTextClipPath cvx exec clip newpath
/pdfTextClipPath [] def } def
/Tclip* { pdfTextClipPath cvx exec eoclip newpath
/pdfTextClipPath [] def } def
% Level 2/3 image operators
/pdfImBuf 100 string def
/pdfImStr {
2 copy exch length lt {
2 copy get exch 1 add exch
} {
()
} ifelse
} def
/skipEOD {
{ currentfile pdfImBuf readline
not { pop exit } if
(%-EOD-) eq { exit } if } loop
} def
/pdfIm { image skipEOD } def
/pdfMask {
/ReusableStreamDecode filter
skipEOD
/maskStream exch def
} def
/pdfMaskEnd { maskStream closefile } def
/pdfMaskInit {
/maskArray exch def
/maskIdx 0 def
} def
/pdfMaskSrc {
maskIdx maskArray length lt {
maskArray maskIdx get
/maskIdx maskIdx 1 add def
} {
()
} ifelse
} def
/pdfImM { fCol imagemask skipEOD } def
/pr { 2 index 2 index 3 2 roll putinterval 4 add } def
/pdfImClip {
gsave
0 2 4 index length 1 sub {
dup 4 index exch 2 copy
get 5 index div put
1 add 3 index exch 2 copy
get 3 index div put
} for
pop pop rectclip
} def
/pdfImClipEnd { grestore } def
% shading operators
/colordelta {
false 0 1 3 index length 1 sub {
dup 4 index exch get 3 index 3 2 roll get sub abs 0.004 gt {
pop true
} if
} for
exch pop exch pop
} def
/funcCol { func n array astore } def
/funcSH {
dup 0 eq {
true
} {
dup 6 eq {
false
} {
4 index 4 index funcCol dup
6 index 4 index funcCol dup
3 1 roll colordelta 3 1 roll
5 index 5 index funcCol dup
3 1 roll colordelta 3 1 roll
6 index 8 index funcCol dup
3 1 roll colordelta 3 1 roll
colordelta or or or
} ifelse
} ifelse
{
1 add
4 index 3 index add 0.5 mul exch 4 index 3 index add 0.5 mul exch
6 index 6 index 4 index 4 index 4 index funcSH
2 index 6 index 6 index 4 index 4 index funcSH
6 index 2 index 4 index 6 index 4 index funcSH
5 3 roll 3 2 roll funcSH pop pop
} {
pop 3 index 2 index add 0.5 mul 3 index 2 index add 0.5 mul
funcCol sc
dup 4 index exch mat transform m
3 index 3 index mat transform l
1 index 3 index mat transform l
mat transform l pop pop h f*
} ifelse
} def
/axialCol {
dup 0 lt {
pop t0
} {
dup 1 gt {
pop t1
} {
dt mul t0 add
} ifelse
} ifelse
func n array astore
} def
/axialSH {
dup 0 eq {
true
} {
dup 8 eq {
false
} {
2 index axialCol 2 index axialCol colordelta
} ifelse
} ifelse
{
1 add 3 1 roll 2 copy add 0.5 mul
dup 4 3 roll exch 4 index axialSH
exch 3 2 roll axialSH
} {
pop 2 copy add 0.5 mul
axialCol sc
exch dup dx mul x0 add exch dy mul y0 add
3 2 roll dup dx mul x0 add exch dy mul y0 add
dx abs dy abs ge {
2 copy yMin sub dy mul dx div add yMin m
yMax sub dy mul dx div add yMax l
2 copy yMax sub dy mul dx div add yMax l
yMin sub dy mul dx div add yMin l
h f*
} {
exch 2 copy xMin sub dx mul dy div add xMin exch m
xMax sub dx mul dy div add xMax exch l
exch 2 copy xMax sub dx mul dy div add xMax exch l
xMin sub dx mul dy div add xMin exch l
h f*
} ifelse
} ifelse
} def
/radialCol {
dup t0 lt {
pop t0
} {
dup t1 gt {
pop t1
} if
} ifelse
func n array astore
} def
/radialSH {
dup 0 eq {
true
} {
dup 8 eq {
false
} {
2 index dt mul t0 add radialCol
2 index dt mul t0 add radialCol colordelta
} ifelse
} ifelse
{
1 add 3 1 roll 2 copy add 0.5 mul
dup 4 3 roll exch 4 index radialSH
exch 3 2 roll radialSH
} {
pop 2 copy add 0.5 mul dt mul t0 add
radialCol sc
encl {
exch dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
0 360 arc h
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
360 0 arcn h f
} {
2 copy
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
a1 a2 arcn
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
a2 a1 arcn h
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
a1 a2 arc
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
a2 a1 arc h f
} ifelse
} ifelse
} def
end
%%EndResource
/CIDInit /ProcSet findresource begin
10 dict begin
begincmap
/CMapType 1 def
/CMapName /Identity-H def
/CIDSystemInfo 3 dict dup begin
/Registry (Adobe) def
/Ordering (Identity) def
/Supplement 0 def
end def
1 begincodespacerange
<0000>
endcodespacerange
0 usefont
1 begincidrange
<0000> 0
endcidrange
endcmap
currentdict CMapName exch /CMap defineresource pop
end
10 dict begin
begincmap
/CMapType 1 def
/CMapName /Identity-V def
/CIDSystemInfo 3 dict dup begin
/Registry (Adobe) def
/Ordering (Identity) def
/Supplement 0 def
end def
/WMode 1 def
1 begincodespacerange
<0000>
endcodespacerange
0 usefont
1 begincidrange
<0000> 0
endcidrange
endcmap
currentdict CMapName exch /CMap defineresource pop
end
end
%%EndProlog
%%BeginSetup
xpdf begin
%%EndSetup
pdfStartPage
%%EndPageSetup
[] 0 d
1 i
0 j
0 J
10 M
1 w
/DeviceGray {} cs
[0] sc
/DeviceGray {} CS
[0] SC
false op
false OP
{} settransfer
0 0 2409 909 re
W
q
[1 0 0 -1 0 909] cm
q
0 0 2409 908.7038 re
W*
q
[0.747904 0 0 0.747904 0 -908.7038] cm
/DeviceRGB {} CS
[1 1 1] SC
/DeviceRGB {} cs
[1 1 1] sc
0 0 3221 2436 re
f
Q
Q
q
5.983235 0 2403.0168 907.20795 re
W*
q
[0.747959 0 0 0.748049 5.983235 0] cm
/DeviceRGB {} CS
[1 1 1] SC
/DeviceRGB {} cs
[1 1 1] sc
0 0 3212.7659 1212.76599 re
f
Q
q
[0.822879 0 0 0.822978 95.330643 145.991699] cm
/DeviceRGB {} CS
[0.09 0.329 0.122] SC
/DeviceRGB {} cs
[0.09 0.329 0.122] sc
231 798 m
227 779 219 741 218 741 c
49 640 69 465 125 365 c
137 491 360 578 230 732 c
229 734 236 758 242 780 c
268 736 307 683 305 678 c
145 288 645 258 749 16 c
796 250 725 612 323 704 c
321 705 250 830 247 831 c
247 829 217 830 221 820 c
223 814 227 806 231 798 c
h
330 625 m
267 476 452 312 544 271 c
356 439 324 564 330 625 c
h
226 704 m
277 645 217 544 181 511 c
242 616 238 677 226 704 c
h
f
Q
q
[26.582434 0 0 26.585632 866.89978 103.116905] cm
/DeviceRGB {} CS
[0.145 0.22 0.059] SC
/DeviceRGB {} cs
[0.145 0.22 0.059] sc
6.5 8.62 m
6.513333 8.940001 6.52 9.303333 6.52 9.71 c
6.52 10.116667 6.513333 10.473333 6.5 10.78 c
6.5 11.139999 6.436667 11.496666 6.31 11.849999 c
6.183333 12.203333 5.993333 12.52 5.74 12.799999 c
5.486666 13.079999 5.17 13.299999 4.79 13.459999 c
4.41 13.619999 3.973334 13.699999 3.48 13.699999 c
2.46 13.699999 l
2.46 18.919998 l
2.46 19.106665 2.486667 19.243332 2.54 19.329998 c
2.593333 19.416664 2.646667 19.466663 2.7 19.479998 c
2.78 19.519997 2.866667 19.519997 2.96 19.479998 c
2.96 19.999998 l
0.54 19.999998 l
0.54 19.479998 l
0.646667 19.519997 0.726667 19.519997 0.78 19.479998 c
0.846667 19.466663 0.913333 19.416664 0.98 19.329998 c
1.046667 19.243332 1.08 19.106665 1.08 18.919998 c
1.08 6.779998 l
1.08 6.579998 1.046667 6.436665 0.98 6.349998 c
0.913333 6.263331 0.846667 6.206665 0.78 6.179998 c
0.726667 6.153331 0.646667 6.153331 0.54 6.179998 c
0.54 5.699998 l
3.48 5.699998 l
3.973334 5.699998 4.41 5.779998 4.79 5.939998 c
5.17 6.099998 5.486666 6.319997 5.74 6.599998 c
5.993333 6.879998 6.183333 7.193331 6.31 7.539998 c
6.436667 7.886664 6.5 8.246664 6.5 8.619998 c
6.5 8.62 l
h
5.08 8.18 m
5.08 7.833334 5.023333 7.533334 4.91 7.28 c
4.796667 7.026667 4.663333 6.826667 4.51 6.68 c
4.356667 6.533334 4.196666 6.416667 4.03 6.33 c
3.863333 6.243334 3.72 6.2 3.6 6.2 c
2.46 6.2 l
2.46 13.16 l
3.599999 13.16 l
3.72 13.16 3.863333 13.126666 4.029999 13.06 c
4.196666 12.993333 4.356666 12.876666 4.509999 12.709999 c
4.663333 12.543332 4.796666 12.339999 4.909999 12.099999 c
5.023333 11.86 5.079999 11.56 5.079999 11.2 c
5.079999 8.18 l
5.08 8.18 l
h
14.58 19.139999 m
14.58 19.246666 14.553333 19.356667 14.5 19.469999 c
14.446667 19.583332 14.37 19.689999 14.270001 19.789999 c
14.170001 19.889999 14.043334 19.98 13.89 20.059999 c
13.736667 20.139999 13.56 20.18 13.360001 20.18 c
13.040001 20.18 12.753334 20.106667 12.500001 19.960001 c
12.246668 19.813335 12.073335 19.620001 11.980001 19.380001 c
11.900002 19.246668 11.840001 19.093334 11.800001 18.920002 c
11.626668 19.280003 11.363335 19.580002 11.010001 19.820002 c
10.656668 20.060001 10.266668 20.18 9.840001 20.180002 c
9.306667 20.180002 8.840001 19.98667 8.440002 19.600002 c
8.213335 19.400003 8.026669 19.140003 7.880002 18.820002 c
7.733335 18.5 7.646668 18.133333 7.620002 17.720001 c
7.606669 17.680002 7.600002 17.600002 7.600002 17.480001 c
7.600002 17.460001 l
7.613335 17.233334 7.653335 16.996668 7.720002 16.750002 c
7.786668 16.503336 7.890002 16.25667 8.030002 16.010002 c
8.170002 15.763335 8.336668 15.530002 8.530002 15.310002 c
8.723335 15.090003 8.933335 14.900003 9.160002 14.740003 c
9.213335 14.713336 9.256668 14.683336 9.290002 14.650003 c
9.323336 14.616669 9.373335 14.580003 9.440002 14.540003 c
9.500002 14.510003 l
9.560002 14.480003 l
9.800002 14.333337 10.010002 14.200004 10.190002 14.080004 c
10.370003 13.960004 10.533336 13.846671 10.680002 13.740004 c
10.760002 13.68667 10.840002 13.62667 10.920002 13.560003 c
11.000002 13.493337 11.086669 13.42667 11.180002 13.360004 c
11.300002 13.280004 11.413336 13.18667 11.520002 13.080004 c
11.626669 12.973337 11.706669 12.84667 11.760002 12.700004 c
11.840002 12.56667 11.880002 12.406671 11.880002 12.220003 c
11.880002 12.033336 11.860002 11.86667 11.820002 11.720003 c
11.766668 11.42667 11.630002 11.173337 11.410002 10.960003 c
11.190002 10.746669 10.946669 10.606669 10.680002 10.540003 c
10.560002 10.500003 10.446669 10.480002 10.340002 10.480002 c
9.966668 10.480002 9.646668 10.546669 9.380002 10.680002 c
9.206669 10.760002 9.080002 10.850002 9.000002 10.950003 c
8.920002 11.050003 8.876669 11.143336 8.870002 11.230002 c
8.863335 11.316669 8.880002 11.386668 8.920002 11.440002 c
8.960002 11.493337 9.013335 11.533337 9.080002 11.560002 c
9.106669 11.573336 9.123335 11.580003 9.130002 11.580003 c
9.136669 11.580003 9.153336 11.58667 9.180002 11.600003 c
9.353335 11.66667 9.496669 11.783337 9.610003 11.950004 c
9.723336 12.116671 9.780003 12.300004 9.780003 12.500004 c
9.780003 12.753337 9.690002 12.970004 9.510002 13.150003 c
9.330002 13.330003 9.113336 13.420003 8.860003 13.420004 c
8.593336 13.420004 8.36667 13.330004 8.180002 13.150003 c
7.993335 12.970003 7.900002 12.753337 7.900002 12.500004 c
7.900002 12.273337 7.930002 12.036671 7.990002 11.790004 c
8.050002 11.543337 8.140002 11.320004 8.260002 11.120004 c
8.513335 10.74667 8.816669 10.47667 9.170002 10.310003 c
9.523336 10.143336 9.913335 10.060003 10.340002 10.060003 c
10.660002 10.060003 10.976668 10.120004 11.290002 10.240004 c
11.603335 10.360004 11.883335 10.530004 12.130002 10.750004 c
12.376669 10.970004 12.583335 11.233337 12.750002 11.540004 c
12.916669 11.84667 13.000002 12.200004 13.000002 12.600004 c
13.000002 18.480003 l
13.000002 18.720003 13.003335 18.876671 13.010002 18.950003 c
13.016669 19.023335 13.020002 19.073336 13.020002 19.100002 c
13.020002 19.273336 13.073336 19.413336 13.180002 19.520002 c
13.286669 19.626669 13.413336 19.680002 13.560002 19.680002 c
13.733335 19.680002 13.873336 19.630003 13.980002 19.530003 c
14.086669 19.430002 14.146669 19.300003 14.160003 19.140003 c
14.580003 19.140003 l
14.58 19.139999 l
h
11.44 18.639999 m
11.546666 18.453333 11.61 18.253332 11.629999 18.039999 c
11.649999 17.826666 11.659999 17.626665 11.659999 17.439999 c
11.659999 13.739999 l
11.606666 13.793332 11.549999 13.843332 11.489999 13.889998 c
11.429998 13.936665 11.366666 13.993332 11.299999 14.059999 c
10.913333 14.339998 10.583333 14.596665 10.31 14.829998 c
10.036666 15.063331 9.806666 15.303331 9.619999 15.549998 c
9.433332 15.796665 9.286665 16.06 9.179999 16.339998 c
9.073334 16.619999 9.006667 16.939999 8.98 17.299999 c
8.98 17.599998 l
8.993333 18.106665 9.13 18.503332 9.389999 18.789999 c
9.649999 19.076666 10.019999 19.219999 10.499999 19.219999 c
10.713332 19.219999 10.906666 19.166666 11.079999 19.059999 c
11.253332 18.939999 11.373332 18.799999 11.439999 18.639999 c
11.44 18.639999 l
h
18.32 10.08 m
19.08 10.093333 19.686666 10.323334 20.139999 10.770001 c
20.593332 11.216667 20.82 11.926667 20.82 12.900001 c
20.82 17.380001 l
20.82 18.353334 20.593332 19.059999 20.139999 19.5 c
19.686666 19.940001 19.08 20.173334 18.32 20.200001 c
18.26 20.200001 l
18.033333 20.200001 17.826666 20.173334 17.639999 20.120001 c
17.619999 20.120001 l
17.606665 20.120001 17.599998 20.113335 17.599998 20.1 c
17.293333 19.993334 17.026667 19.846666 16.799999 19.66 c
16.609999 19.469999 l
16.609999 19.469999 16.56 19.4 16.459999 19.26 c
16.459999 22.58 l
16.459999 22.766666 16.486666 22.903334 16.539999 22.99 c
16.593332 23.076666 16.646666 23.133333 16.699999 23.16 c
16.779999 23.199999 16.866667 23.199999 16.959999 23.16 c
16.959999 23.66 l
14.539999 23.66 l
14.539999 23.16 l
14.646666 23.199999 14.726666 23.199999 14.779999 23.16 c
14.846665 23.133333 14.909999 23.076666 14.969998 22.99 c
15.029998 22.903334 15.059998 22.766666 15.059999 22.58 c
15.059999 11.3 l
15.059999 11.113334 15.029999 10.976667 14.969998 10.89 c
14.909998 10.803333 14.846665 10.746667 14.779999 10.72 c
14.726666 10.693334 14.646666 10.693334 14.539999 10.72 c
14.539999 10.24 l
16.459999 10.24 l
16.459999 11.02 l
16.553331 10.873333 16.666666 10.74 16.799999 10.62 c
17.026667 10.42 17.293333 10.273334 17.599998 10.18 c
17.599998 10.166667 17.606665 10.16 17.619999 10.16 c
17.639999 10.16 l
17.853333 10.106667 18.059999 10.08 18.26 10.08 c
18.32 10.08 l
h
19.32 12.46 m
19.32 12.44 l
19.306665 11.76 19.223333 11.28 19.07 11 c
18.916666 10.72 18.666666 10.566667 18.32 10.54 c
18.08 10.526667 17.853333 10.556666 17.639999 10.63 c
17.426666 10.703334 17.24 10.826667 17.08 11 c
16.92 11.173333 16.786667 11.406666 16.68 11.7 c
16.573334 11.993334 16.5 12.34 16.460001 12.74 c
16.460001 17.540001 l
16.5 17.953335 16.573334 18.303335 16.68 18.59 c
16.786667 18.876665 16.92 19.103333 17.08 19.27 c
17.24 19.436668 17.426666 19.560001 17.639999 19.640001 c
17.853333 19.720001 18.08 19.753334 18.32 19.740002 c
18.666666 19.713335 18.916666 19.560001 19.07 19.280003 c
19.223333 19.000004 19.306665 18.520004 19.32 17.840002 c
19.32 17.820002 l
19.32 12.460001 l
19.32 12.46 l
h
24.379999 10.08 m
24.686665 10.08 24.999998 10.133333 25.32 10.24 c
25.640001 10.346666 25.926668 10.513333 26.18 10.74 c
26.433332 10.966666 26.636667 11.253333 26.790001 11.599999 c
26.943335 11.946666 27.020002 12.359999 27.02 12.839999 c
27.02 14.859999 l
27.02 14.919999 l
27.02 15.379999 l
23.040001 15.379999 l
23.040001 17.439999 l
23.040001 18.266665 23.183334 18.856665 23.470001 19.209999 c
23.756668 19.563334 24.113335 19.733334 24.540001 19.719999 c
24.793333 19.706665 25.043333 19.663332 25.290001 19.59 c
25.536669 19.516668 25.750002 19.393333 25.93 19.219999 c
26.109999 19.046665 26.26 18.799997 26.380001 18.48 c
26.500002 18.160002 26.560003 17.740002 26.560001 17.219999 c
27.02 17.219999 l
27.02 17.5 l
27.02 17.860001 26.98 18.203335 26.9 18.530001 c
26.82 18.856667 26.679998 19.140001 26.48 19.380001 c
26.280001 19.620001 26.023333 19.810001 25.709999 19.950001 c
25.396666 20.09 25.013332 20.166666 24.559999 20.18 c
24.24 20.18 23.913334 20.120001 23.58 20 c
23.246666 19.879999 22.946667 19.699999 22.68 19.459999 c
22.413334 19.219999 22.193335 18.936665 22.02 18.609999 c
21.846666 18.283333 21.76 17.913332 21.76 17.499998 c
21.76 17.029999 l
21.76 17.029999 21.756666 16.773333 21.75 16.259998 c
21.743334 15.746665 21.74 15.429998 21.74 15.309999 c
21.74 15.189999 21.736666 14.859999 21.73 14.319999 c
21.723333 13.779999 21.719999 13.489999 21.719999 13.449999 c
21.719999 12.859999 l
21.719999 12.379998 21.803333 11.963332 21.969999 11.609999 c
22.136665 11.256665 22.343332 10.969999 22.59 10.749999 c
22.836668 10.529999 23.116667 10.363333 23.43 10.249999 c
23.743334 10.136665 24.059999 10.079999 24.380001 10.079999 c
24.379999 10.08 l
h
25.68 14.86 m
25.68 13.28 l
25.68 12.773333 25.646667 12.349999 25.58 12.01 c
25.513332 11.670001 25.423332 11.396668 25.309999 11.190001 c
25.196667 10.983334 25.059999 10.84 24.9 10.76 c
24.74 10.68 24.559999 10.64 24.359999 10.64 c
23.933332 10.64 23.609999 10.826667 23.389999 11.200001 c
23.17 11.573335 23.059999 12.266667 23.059999 13.280001 c
23.059999 14.860001 l
25.68 14.860001 l
25.68 14.86 l
h
33.540001 11.66 m
33.540001 11.686667 33.546669 11.74 33.560001 11.82 c
33.573334 11.9 33.573334 11.966666 33.560001 12.02 c
33.546669 12.299999 33.450001 12.53 33.27 12.709999 c
33.09 12.889998 32.873333 12.979999 32.619999 12.98 c
32.353333 12.98 32.129997 12.886666 31.949999 12.7 c
31.77 12.513333 31.68 12.286667 31.679998 12.02 c
31.679998 11.686667 31.806665 11.433332 32.059998 11.259999 c
32.086666 11.246666 32.103333 11.236666 32.109997 11.23 c
32.139996 11.2 l
32.23333 11.133333 32.293327 11.036666 32.319996 10.91 c
32.346664 10.783334 32.266663 10.673333 32.079994 10.58 c
31.959993 10.513333 31.839994 10.48 31.719994 10.48 c
31.319994 10.453333 30.96666 10.573333 30.659994 10.839999 c
30.353329 11.106666 30.146662 11.573332 30.039993 12.239999 c
30.039993 18.919998 l
30.039993 19.106665 30.06666 19.243332 30.119993 19.329998 c
30.173326 19.416664 30.22666 19.466663 30.279993 19.479998 c
30.359993 19.519997 30.446661 19.519997 30.539993 19.479998 c
30.539993 19.999998 l
28.119993 19.999998 l
28.119993 19.479998 l
28.22666 19.519997 28.30666 19.519997 28.359993 19.479998 c
28.426661 19.466663 28.493326 19.416664 28.559994 19.329998 c
28.626661 19.243332 28.659994 19.106665 28.659994 18.919998 c
28.659994 11.299998 l
28.659994 11.113332 28.626661 10.976666 28.559994 10.889998 c
28.493326 10.803331 28.426661 10.746665 28.359993 10.719998 c
28.30666 10.693332 28.22666 10.693332 28.119993 10.719998 c
28.119993 10.219998 l
30.039993 10.219998 l
30.039993 11.039998 l
30.133326 10.893332 30.22666 10.756664 30.319994 10.629998 c
30.413328 10.503332 30.523329 10.396666 30.649994 10.309999 c
30.776659 10.223331 30.923326 10.153332 31.089994 10.099999 c
31.256662 10.046665 31.466661 10.019999 31.719994 10.019999 c
32.106663 10.019999 32.466663 10.159999 32.799995 10.439999 c
33.133327 10.719998 33.379997 11.126665 33.539997 11.659999 c
33.540001 11.66 l
h
36.240002 18.92 m
36.240002 19.106667 36.27 19.243334 36.330002 19.33 c
36.390003 19.416666 36.446667 19.466665 36.5 19.48 c
36.566666 19.519999 36.653332 19.519999 36.759998 19.48 c
36.759998 20 l
34.339996 20 l
34.339996 19.48 l
34.446663 19.519999 34.539997 19.519999 34.619995 19.48 c
34.659996 19.466665 34.713329 19.416666 34.779995 19.33 c
34.846661 19.243334 34.879993 19.106667 34.879993 18.92 c
34.879993 6.8 l
34.879993 6.6 34.846661 6.456667 34.779995 6.37 c
34.713329 6.283334 34.659996 6.226667 34.619995 6.2 c
34.539997 6.16 34.446663 6.16 34.339996 6.2 c
34.339996 5.72 l
36.239998 5.72 l
36.239998 6.8 l
36.239998 18.92 l
36.240002 18.92 l
h
40.580002 10.08 m
40.886669 10.08 41.200001 10.133333 41.52 10.24 c
41.84 10.346666 42.126667 10.513333 42.380001 10.74 c
42.633335 10.966666 42.83667 11.253333 42.990002 11.599999 c
43.143333 11.946666 43.220001 12.359999 43.220001 12.839999 c
43.220001 14.859999 l
43.220001 14.919999 l
43.220001 15.379999 l
39.240002 15.379999 l
39.240002 17.439999 l
39.240002 18.266665 39.383335 18.856665 39.670002 19.209999 c
39.956669 19.563334 40.313335 19.733334 40.740002 19.719999 c
40.993336 19.706665 41.243336 19.663332 41.490002 19.59 c
41.736668 19.516668 41.950001 19.393333 42.130001 19.219999 c
42.310001 19.046665 42.460003 18.799997 42.580002 18.48 c
42.700001 18.160002 42.760002 17.740002 42.760002 17.219999 c
43.220001 17.219999 l
43.220001 17.5 l
43.220001 17.860001 43.18 18.203335 43.100002 18.530001 c
43.020004 18.856667 42.880005 19.140001 42.680004 19.380001 c
42.480003 19.620001 42.223339 19.810001 41.910004 19.950001 c
41.596668 20.09 41.213337 20.166666 40.760002 20.18 c
40.440002 20.18 40.113335 20.120001 39.780003 20 c
39.446671 19.879999 39.146667 19.699999 38.880001 19.459999 c
38.613335 19.219999 38.393333 18.936665 38.220001 18.609999 c
38.046669 18.283333 37.960003 17.913332 37.960003 17.499998 c
37.960003 17.029999 l
37.960003 17.029999 37.956669 16.773333 37.950005 16.259998 c
37.94334 15.746665 37.940006 15.429998 37.940006 15.309999 c
37.940006 15.189999 37.936672 14.859999 37.930008 14.319999 c
37.923344 13.779999 37.92001 13.489999 37.92001 13.449999 c
37.92001 12.859999 l
37.92001 12.379998 38.003342 11.963332 38.17001 11.609999 c
38.336678 11.256665 38.543343 10.969999 38.790009 10.749999 c
39.036674 10.529999 39.316673 10.363333 39.630009 10.249999 c
39.943344 10.136665 40.26001 10.079999 40.580009 10.079999 c
40.580002 10.08 l
h
41.880001 14.86 m
41.880001 13.28 l
41.880001 12.773333 41.846668 12.349999 41.780003 12.01 c
41.713337 11.670001 41.623337 11.396668 41.510002 11.190001 c
41.396667 10.983334 41.260002 10.84 41.100002 10.76 c
40.940002 10.68 40.760002 10.64 40.560001 10.64 c
40.133335 10.64 39.810001 10.826667 39.59 11.200001 c
39.369999 11.573335 39.259998 12.266667 39.259998 13.280001 c
39.259998 14.860001 l
41.879997 14.860001 l
41.880001 14.86 l
h
47.880001 19.879999 m
47.866669 19.893333 47.856667 19.9 47.850002 19.9 c
47.843338 19.9 47.833336 19.906666 47.820004 19.92 c
47.800003 19.940001 l
47.773335 19.953335 47.743336 19.966667 47.710003 19.980001 c
47.67667 19.993336 47.646671 20.006668 47.620003 20.020002 c
47.340004 20.140003 47.013336 20.200003 46.640003 20.200003 c
46.42667 20.200003 46.220005 20.150003 46.020004 20.050003 c
45.820004 19.950003 45.646671 19.866671 45.500004 19.800003 c
45.353336 19.733335 45.230003 19.703337 45.130005 19.710003 c
45.030006 19.716669 44.980007 19.813337 44.980003 20.000004 c
44.600002 20.000004 l
44.600002 17.600004 l
45.080002 17.600004 l
45.053333 18.02667 45.100002 18.393337 45.220001 18.700005 c
45.299999 18.966671 45.456669 19.216671 45.690002 19.450005 c
45.923336 19.683338 46.273338 19.800005 46.740002 19.800005 c
47.046669 19.800005 47.299999 19.746672 47.5 19.640005 c
47.846668 19.466671 48.083332 19.260004 48.209999 19.020004 c
48.336666 18.780005 48.386665 18.530005 48.360001 18.270004 c
48.333336 18.010004 48.236668 17.753338 48.07 17.500004 c
47.903332 17.24667 47.699997 17.013336 47.459999 16.800003 c
47.299999 16.680002 47.133331 16.553337 46.959999 16.420004 c
46.786667 16.286671 46.613335 16.146671 46.439999 16.000004 c
45.973331 15.62667 45.603333 15.290004 45.329998 14.990004 c
45.056664 14.690003 44.846664 14.410004 44.699997 14.150003 c
44.553329 13.890003 44.453331 13.643336 44.399998 13.410004 c
44.346664 13.176671 44.32 12.926671 44.319996 12.660004 c
44.319996 12.500004 44.329994 12.353337 44.349995 12.220004 c
44.369995 12.086671 44.393326 11.966671 44.419994 11.860004 c
44.459995 11.633338 44.526661 11.413338 44.619995 11.200005 c
44.713329 10.986671 44.846661 10.796672 45.019997 10.630005 c
45.193333 10.463338 45.406666 10.326672 45.659996 10.220005 c
45.913326 10.113339 46.219994 10.060005 46.579994 10.060005 c
46.779995 10.060005 46.979992 10.103338 47.179993 10.190005 c
47.379993 10.276672 47.559994 10.356672 47.719994 10.430005 c
47.879993 10.503338 48.00666 10.536672 48.099995 10.530006 c
48.193329 10.523339 48.239994 10.420006 48.239994 10.220005 c
48.619995 10.220005 l
48.619995 12.700005 l
48.099995 12.700005 l
48.113327 12.300004 48.066662 11.940004 47.959995 11.620005 c
47.866661 11.340005 47.703327 11.086671 47.469994 10.860004 c
47.23666 10.633338 46.893326 10.520004 46.439995 10.520004 c
46.079994 10.520004 45.786659 10.600004 45.559994 10.760004 c
45.439995 10.853337 45.336658 10.990005 45.249992 11.170004 c
45.163326 11.350003 45.113323 11.54667 45.099991 11.760004 c
45.086658 11.973338 45.119991 12.203338 45.199989 12.450005 c
45.279987 12.696671 45.439987 12.926671 45.679989 13.140005 c
45.706657 13.180005 45.743324 13.220005 45.789989 13.260005 c
45.836655 13.300005 45.886658 13.333338 45.939991 13.360005 c
45.979992 13.386672 46.013325 13.413339 46.039989 13.440005 c
46.119991 13.520005 l
46.266659 13.640005 46.41666 13.760005 46.569992 13.880005 c
46.723324 14.000005 46.879993 14.120005 47.039993 14.240005 c
47.413326 14.560005 47.723328 14.850005 47.969994 15.110004 c
48.21666 15.370004 48.423325 15.613338 48.589993 15.840004 c
48.75666 16.066671 48.879993 16.280004 48.959991 16.480003 c
49.039989 16.680002 49.086658 16.873337 49.099991 17.060003 c
49.233326 17.673336 49.209991 18.190002 49.029991 18.610003 c
48.849991 19.030003 48.619991 19.360003 48.339993 19.600002 c
48.319992 19.600002 l
48.319992 19.626669 48.30666 19.640003 48.279991 19.640003 c
48.253323 19.66667 48.226658 19.68667 48.199989 19.700003 c
48.07999 19.780003 l
48.07999 19.780003 48.039989 19.800003 47.959991 19.840002 c
47.959991 19.860003 l
47.946659 19.860003 47.933323 19.863337 47.919991 19.870003 c
47.906658 19.876669 47.893322 19.880003 47.87999 19.880003 c
47.880001 19.879999 l
h
53.82 19.879999 m
53.806667 19.893333 53.796665 19.9 53.790001 19.9 c
53.783337 19.9 53.773335 19.906666 53.760002 19.92 c
53.740002 19.940001 l
53.713333 19.953335 53.683334 19.966667 53.650002 19.980001 c
53.616669 19.993336 53.58667 20.006668 53.560001 20.020002 c
53.280003 20.140003 52.953335 20.200003 52.580002 20.200003 c
52.366669 20.200003 52.160004 20.150003 51.960003 20.050003 c
51.760002 19.950003 51.58667 19.866671 51.440002 19.800003 c
51.293335 19.733335 51.170002 19.703337 51.070004 19.710003 c
50.970005 19.716669 50.920006 19.813337 50.920002 20.000004 c
50.540001 20.000004 l
50.540001 17.600004 l
51.02 17.600004 l
50.993332 18.02667 51.040001 18.393337 51.16 18.700005 c
51.239998 18.966671 51.396667 19.216671 51.630001 19.450005 c
51.863335 19.683338 52.213337 19.800005 52.68 19.800005 c
52.986668 19.800005 53.239998 19.746672 53.439999 19.640005 c
53.786667 19.466671 54.023331 19.260004 54.149998 19.020004 c
54.276665 18.780005 54.326664 18.530005 54.299999 18.270004 c
54.273335 18.010004 54.176666 17.753338 54.009998 17.500004 c
53.84333 17.24667 53.639996 17.013336 53.399998 16.800003 c
53.239998 16.680002 53.07333 16.553337 52.899998 16.420004 c
52.726665 16.286671 52.553333 16.146671 52.379997 16.000004 c
51.91333 15.62667 51.543331 15.290004 51.269997 14.990004 c
50.996662 14.690003 50.786663 14.410004 50.639996 14.150003 c
50.493328 13.890003 50.39333 13.643336 50.339996 13.410004 c
50.286663 13.176671 50.259998 12.926671 50.259995 12.660004 c
50.259995 12.500004 50.269993 12.353337 50.289993 12.220004 c
50.309994 12.086671 50.333324 11.966671 50.359993 11.860004 c
50.399994 11.633338 50.46666 11.413338 50.559994 11.200005 c
50.653328 10.986671 50.786659 10.796672 50.959995 10.630005 c
51.133331 10.463338 51.346664 10.326672 51.599995 10.220005 c
51.853325 10.113339 52.159992 10.060005 52.519993 10.060005 c
52.719994 10.060005 52.919991 10.103338 53.119991 10.190005 c
53.319992 10.276672 53.499992 10.356672 53.659992 10.430005 c
53.819992 10.503338 53.946659 10.536672 54.039993 10.530006 c
54.133327 10.523339 54.179993 10.420006 54.179993 10.220005 c
54.559994 10.220005 l
54.559994 12.700005 l
54.039993 12.700005 l
54.053326 12.300004 54.00666 11.940004 53.899994 11.620005 c
53.80666 11.340005 53.643326 11.086671 53.409992 10.860004 c
53.176659 10.633338 52.833324 10.520004 52.379993 10.520004 c
52.019993 10.520004 51.726658 10.600004 51.499992 10.760004 c
51.379993 10.853337 51.276657 10.990005 51.189991 11.170004 c
51.103325 11.350003 51.053322 11.54667 51.039989 11.760004 c
51.026657 11.973338 51.05999 12.203338 51.139988 12.450005 c
51.219986 12.696671 51.379986 12.926671 51.619987 13.140005 c
51.646656 13.180005 51.683323 13.220005 51.729988 13.260005 c
51.776653 13.300005 51.826656 13.333338 51.87999 13.360005 c
51.919991 13.386672 51.953323 13.413339 51.979988 13.440005 c
52.05999 13.520005 l
52.206657 13.640005 52.356659 13.760005 52.509991 13.880005 c
52.663322 14.000005 52.819992 14.120005 52.979992 14.240005 c
53.353325 14.560005 53.663326 14.850005 53.909992 15.110004 c
54.156658 15.370004 54.363323 15.613338 54.529991 15.840004 c
54.696659 16.066671 54.819992 16.280004 54.89999 16.480003 c
54.979988 16.680002 55.026657 16.873337 55.039989 17.060003 c
55.173325 17.673336 55.14999 18.190002 54.96999 18.610003 c
54.789989 19.030003 54.55999 19.360003 54.279991 19.600002 c
54.259991 19.600002 l
54.259991 19.626669 54.246658 19.640003 54.21999 19.640003 c
54.193321 19.66667 54.166656 19.68667 54.139988 19.700003 c
54.019989 19.780003 l
54.019989 19.780003 53.979988 19.800003 53.89999 19.840002 c
53.89999 19.860003 l
53.886658 19.860003 53.873322 19.863337 53.859989 19.870003 c
53.846657 19.876669 53.833321 19.880003 53.819988 19.880003 c
53.82 19.879999 l
h
f
Q
Q
Q
showpage
%%PageTrailer
pdfEndPage
%%Trailer
end
%%DocumentSuppliedResources:
%%EOF
================================================
FILE: resources/logo/print/eps/White logo - no background.eps
================================================
%!PS-Adobe-3.0 EPSF-3.0
%Produced by poppler pdftops version: 0.59.0 (http://poppler.freedesktop.org)
%%Creator: Chromium
%%LanguageLevel: 3
%%DocumentSuppliedResources: (atend)
%%BoundingBox: 0 0 2409 909
%%HiResBoundingBox: 0 0 2409 909
%%DocumentSuppliedResources: (atend)
%%EndComments
%%BeginProlog
%%BeginResource: procset xpdf 3.00 0
%%Copyright: Copyright 1996-2011 Glyph & Cog, LLC
/xpdf 75 dict def xpdf begin
% PDF special state
/pdfDictSize 15 def
/pdfSetup {
/setpagedevice where {
pop 2 dict begin
/Policies 1 dict dup begin /PageSize 6 def end def
{ /Duplex true def } if
currentdict end setpagedevice
} {
pop
} ifelse
} def
/pdfSetupPaper {
% Change paper size, but only if different from previous paper size otherwise
% duplex fails. PLRM specifies a tolerance of 5 pts when matching paper size
% so we use the same when checking if the size changes.
/setpagedevice where {
pop currentpagedevice
/PageSize known {
2 copy
currentpagedevice /PageSize get aload pop
exch 4 1 roll
sub abs 5 gt
3 1 roll
sub abs 5 gt
or
} {
true
} ifelse
{
2 array astore
2 dict begin
/PageSize exch def
/ImagingBBox null def
currentdict end
setpagedevice
} {
pop pop
} ifelse
} {
pop
} ifelse
} def
/pdfStartPage {
pdfDictSize dict begin
/pdfFillCS [] def
/pdfFillXform {} def
/pdfStrokeCS [] def
/pdfStrokeXform {} def
/pdfFill [0] def
/pdfStroke [0] def
/pdfFillOP false def
/pdfStrokeOP false def
/pdfOPM false def
/pdfLastFill false def
/pdfLastStroke false def
/pdfTextMat [1 0 0 1 0 0] def
/pdfFontSize 0 def
/pdfCharSpacing 0 def
/pdfTextRender 0 def
/pdfPatternCS false def
/pdfTextRise 0 def
/pdfWordSpacing 0 def
/pdfHorizScaling 1 def
/pdfTextClipPath [] def
} def
/pdfEndPage { end } def
% PDF color state
/opm { dup /pdfOPM exch def
/setoverprintmode where{pop setoverprintmode}{pop}ifelse } def
/cs { /pdfFillXform exch def dup /pdfFillCS exch def
setcolorspace } def
/CS { /pdfStrokeXform exch def dup /pdfStrokeCS exch def
setcolorspace } def
/sc { pdfLastFill not { pdfFillCS setcolorspace } if
dup /pdfFill exch def aload pop pdfFillXform setcolor
/pdfLastFill true def /pdfLastStroke false def } def
/SC { pdfLastStroke not { pdfStrokeCS setcolorspace } if
dup /pdfStroke exch def aload pop pdfStrokeXform setcolor
/pdfLastStroke true def /pdfLastFill false def } def
/op { /pdfFillOP exch def
pdfLastFill { pdfFillOP setoverprint } if } def
/OP { /pdfStrokeOP exch def
pdfLastStroke { pdfStrokeOP setoverprint } if } def
/fCol {
pdfLastFill not {
pdfFillCS setcolorspace
pdfFill aload pop pdfFillXform setcolor
pdfFillOP setoverprint
/pdfLastFill true def /pdfLastStroke false def
} if
} def
/sCol {
pdfLastStroke not {
pdfStrokeCS setcolorspace
pdfStroke aload pop pdfStrokeXform setcolor
pdfStrokeOP setoverprint
/pdfLastStroke true def /pdfLastFill false def
} if
} def
% build a font
/pdfMakeFont {
4 3 roll findfont
4 2 roll matrix scale makefont
dup length dict begin
{ 1 index /FID ne { def } { pop pop } ifelse } forall
/Encoding exch def
currentdict
end
definefont pop
} def
/pdfMakeFont16 {
exch findfont
dup length dict begin
{ 1 index /FID ne { def } { pop pop } ifelse } forall
/WMode exch def
currentdict
end
definefont pop
} def
/pdfMakeFont16L3 {
1 index /CIDFont resourcestatus {
pop pop 1 index /CIDFont findresource /CIDFontType known
} {
false
} ifelse
{
0 eq { /Identity-H } { /Identity-V } ifelse
exch 1 array astore composefont pop
} {
pdfMakeFont16
} ifelse
} def
% graphics state operators
/q { gsave pdfDictSize dict begin } def
/Q {
end grestore
/pdfLastFill where {
pop
pdfLastFill {
pdfFillOP setoverprint
} {
pdfStrokeOP setoverprint
} ifelse
} if
/pdfOPM where {
pop
pdfOPM /setoverprintmode where{pop setoverprintmode}{pop}ifelse
} if
} def
/cm { concat } def
/d { setdash } def
/i { setflat } def
/j { setlinejoin } def
/J { setlinecap } def
/M { setmiterlimit } def
/w { setlinewidth } def
% path segment operators
/m { moveto } def
/l { lineto } def
/c { curveto } def
/re { 4 2 roll moveto 1 index 0 rlineto 0 exch rlineto
neg 0 rlineto closepath } def
/h { closepath } def
% path painting operators
/S { sCol stroke } def
/Sf { fCol stroke } def
/f { fCol fill } def
/f* { fCol eofill } def
% clipping operators
/W { clip newpath } def
/W* { eoclip newpath } def
/Ws { strokepath clip newpath } def
% text state operators
/Tc { /pdfCharSpacing exch def } def
/Tf { dup /pdfFontSize exch def
dup pdfHorizScaling mul exch matrix scale
pdfTextMat matrix concatmatrix dup 4 0 put dup 5 0 put
exch findfont exch makefont setfont } def
/Tr { /pdfTextRender exch def } def
/Tp { /pdfPatternCS exch def } def
/Ts { /pdfTextRise exch def } def
/Tw { /pdfWordSpacing exch def } def
/Tz { /pdfHorizScaling exch def } def
% text positioning operators
/Td { pdfTextMat transform moveto } def
/Tm { /pdfTextMat exch def } def
% text string operators
/xyshow where {
pop
/xyshow2 {
dup length array
0 2 2 index length 1 sub {
2 index 1 index 2 copy get 3 1 roll 1 add get
pdfTextMat dtransform
4 2 roll 2 copy 6 5 roll put 1 add 3 1 roll dup 4 2 roll put
} for
exch pop
xyshow
} def
}{
/xyshow2 {
currentfont /FontType get 0 eq {
0 2 3 index length 1 sub {
currentpoint 4 index 3 index 2 getinterval show moveto
2 copy get 2 index 3 2 roll 1 add get
pdfTextMat dtransform rmoveto
} for
} {
0 1 3 index length 1 sub {
currentpoint 4 index 3 index 1 getinterval show moveto
2 copy 2 mul get 2 index 3 2 roll 2 mul 1 add get
pdfTextMat dtransform rmoveto
} for
} ifelse
pop pop
} def
} ifelse
/cshow where {
pop
/xycp {
0 3 2 roll
{
pop pop currentpoint 3 2 roll
1 string dup 0 4 3 roll put false charpath moveto
2 copy get 2 index 2 index 1 add get
pdfTextMat dtransform rmoveto
2 add
} exch cshow
pop pop
} def
}{
/xycp {
currentfont /FontType get 0 eq {
0 2 3 index length 1 sub {
currentpoint 4 index 3 index 2 getinterval false charpath moveto
2 copy get 2 index 3 2 roll 1 add get
pdfTextMat dtransform rmoveto
} for
} {
0 1 3 index length 1 sub {
currentpoint 4 index 3 index 1 getinterval false charpath moveto
2 copy 2 mul get 2 index 3 2 roll 2 mul 1 add get
pdfTextMat dtransform rmoveto
} for
} ifelse
pop pop
} def
} ifelse
/Tj {
fCol
0 pdfTextRise pdfTextMat dtransform rmoveto
currentpoint 4 2 roll
pdfTextRender 1 and 0 eq {
2 copy xyshow2
} if
pdfTextRender 3 and dup 1 eq exch 2 eq or {
3 index 3 index moveto
2 copy
currentfont /FontType get 3 eq { fCol } { sCol } ifelse
xycp currentpoint stroke moveto
} if
pdfTextRender 4 and 0 ne {
4 2 roll moveto xycp
/pdfTextClipPath [ pdfTextClipPath aload pop
{/moveto cvx}
{/lineto cvx}
{/curveto cvx}
{/closepath cvx}
pathforall ] def
currentpoint newpath moveto
} {
pop pop pop pop
} ifelse
0 pdfTextRise neg pdfTextMat dtransform rmoveto
} def
/TJm { 0.001 mul pdfFontSize mul pdfHorizScaling mul neg 0
pdfTextMat dtransform rmoveto } def
/TJmV { 0.001 mul pdfFontSize mul neg 0 exch
pdfTextMat dtransform rmoveto } def
/Tclip { pdfTextClipPath cvx exec clip newpath
/pdfTextClipPath [] def } def
/Tclip* { pdfTextClipPath cvx exec eoclip newpath
/pdfTextClipPath [] def } def
% Level 2/3 image operators
/pdfImBuf 100 string def
/pdfImStr {
2 copy exch length lt {
2 copy get exch 1 add exch
} {
()
} ifelse
} def
/skipEOD {
{ currentfile pdfImBuf readline
not { pop exit } if
(%-EOD-) eq { exit } if } loop
} def
/pdfIm { image skipEOD } def
/pdfMask {
/ReusableStreamDecode filter
skipEOD
/maskStream exch def
} def
/pdfMaskEnd { maskStream closefile } def
/pdfMaskInit {
/maskArray exch def
/maskIdx 0 def
} def
/pdfMaskSrc {
maskIdx maskArray length lt {
maskArray maskIdx get
/maskIdx maskIdx 1 add def
} {
()
} ifelse
} def
/pdfImM { fCol imagemask skipEOD } def
/pr { 2 index 2 index 3 2 roll putinterval 4 add } def
/pdfImClip {
gsave
0 2 4 index length 1 sub {
dup 4 index exch 2 copy
get 5 index div put
1 add 3 index exch 2 copy
get 3 index div put
} for
pop pop rectclip
} def
/pdfImClipEnd { grestore } def
% shading operators
/colordelta {
false 0 1 3 index length 1 sub {
dup 4 index exch get 3 index 3 2 roll get sub abs 0.004 gt {
pop true
} if
} for
exch pop exch pop
} def
/funcCol { func n array astore } def
/funcSH {
dup 0 eq {
true
} {
dup 6 eq {
false
} {
4 index 4 index funcCol dup
6 index 4 index funcCol dup
3 1 roll colordelta 3 1 roll
5 index 5 index funcCol dup
3 1 roll colordelta 3 1 roll
6 index 8 index funcCol dup
3 1 roll colordelta 3 1 roll
colordelta or or or
} ifelse
} ifelse
{
1 add
4 index 3 index add 0.5 mul exch 4 index 3 index add 0.5 mul exch
6 index 6 index 4 index 4 index 4 index funcSH
2 index 6 index 6 index 4 index 4 index funcSH
6 index 2 index 4 index 6 index 4 index funcSH
5 3 roll 3 2 roll funcSH pop pop
} {
pop 3 index 2 index add 0.5 mul 3 index 2 index add 0.5 mul
funcCol sc
dup 4 index exch mat transform m
3 index 3 index mat transform l
1 index 3 index mat transform l
mat transform l pop pop h f*
} ifelse
} def
/axialCol {
dup 0 lt {
pop t0
} {
dup 1 gt {
pop t1
} {
dt mul t0 add
} ifelse
} ifelse
func n array astore
} def
/axialSH {
dup 0 eq {
true
} {
dup 8 eq {
false
} {
2 index axialCol 2 index axialCol colordelta
} ifelse
} ifelse
{
1 add 3 1 roll 2 copy add 0.5 mul
dup 4 3 roll exch 4 index axialSH
exch 3 2 roll axialSH
} {
pop 2 copy add 0.5 mul
axialCol sc
exch dup dx mul x0 add exch dy mul y0 add
3 2 roll dup dx mul x0 add exch dy mul y0 add
dx abs dy abs ge {
2 copy yMin sub dy mul dx div add yMin m
yMax sub dy mul dx div add yMax l
2 copy yMax sub dy mul dx div add yMax l
yMin sub dy mul dx div add yMin l
h f*
} {
exch 2 copy xMin sub dx mul dy div add xMin exch m
xMax sub dx mul dy div add xMax exch l
exch 2 copy xMax sub dx mul dy div add xMax exch l
xMin sub dx mul dy div add xMin exch l
h f*
} ifelse
} ifelse
} def
/radialCol {
dup t0 lt {
pop t0
} {
dup t1 gt {
pop t1
} if
} ifelse
func n array astore
} def
/radialSH {
dup 0 eq {
true
} {
dup 8 eq {
false
} {
2 index dt mul t0 add radialCol
2 index dt mul t0 add radialCol colordelta
} ifelse
} ifelse
{
1 add 3 1 roll 2 copy add 0.5 mul
dup 4 3 roll exch 4 index radialSH
exch 3 2 roll radialSH
} {
pop 2 copy add 0.5 mul dt mul t0 add
radialCol sc
encl {
exch dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
0 360 arc h
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
360 0 arcn h f
} {
2 copy
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
a1 a2 arcn
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
a2 a1 arcn h
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
a1 a2 arc
dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add
a2 a1 arc h f
} ifelse
} ifelse
} def
end
%%EndResource
/CIDInit /ProcSet findresource begin
10 dict begin
begincmap
/CMapType 1 def
/CMapName /Identity-H def
/CIDSystemInfo 3 dict dup begin
/Registry (Adobe) def
/Ordering (Identity) def
/Supplement 0 def
end def
1 begincodespacerange
<0000>
endcodespacerange
0 usefont
1 begincidrange
<0000> 0
endcidrange
endcmap
currentdict CMapName exch /CMap defineresource pop
end
10 dict begin
begincmap
/CMapType 1 def
/CMapName /Identity-V def
/CIDSystemInfo 3 dict dup begin
/Registry (Adobe) def
/Ordering (Identity) def
/Supplement 0 def
end def
/WMode 1 def
1 begincodespacerange
<0000>
endcodespacerange
0 usefont
1 begincidrange
<0000> 0
endcidrange
endcmap
currentdict CMapName exch /CMap defineresource pop
end
end
%%EndProlog
%%BeginSetup
xpdf begin
%%EndSetup
pdfStartPage
%%EndPageSetup
[] 0 d
1 i
0 j
0 J
10 M
1 w
/DeviceGray {} cs
[0] sc
/DeviceGray {} CS
[0] SC
false op
false OP
{} settransfer
0 0 2409 909 re
W
q
[1 0 0 -1 0 909] cm
q
0 0 2409 908.7038 re
W*
q
[0.747904 0 0 0.747904 0 -908.7038] cm
/DeviceRGB {} CS
[1 1 1] SC
/DeviceRGB {} cs
[1 1 1] sc
0 0 3221 2436 re
f
Q
Q
q
5.983235 0 2403.0168 907.20795 re
W*
q
[0.822879 0 0 0.822978 95.330643 145.991699] cm
/DeviceRGB {} CS
[0.09 0.329 0.122] SC
/DeviceRGB {} cs
[0.09 0.329 0.122] sc
231 798 m
227 779 219 741 218 741 c
49 640 69 465 125 365 c
137 491 360 578 230 732 c
229 734 236 758 242 780 c
268 736 307 683 305 678 c
145 288 645 258 749 16 c
796 250 725 612 323 704 c
321 705 250 830 247 831 c
247 829 217 830 221 820 c
223 814 227 806 231 798 c
h
330 625 m
267 476 452 312 544 271 c
356 439 324 564 330 625 c
h
226 704 m
277 645 217 544 181 511 c
242 616 238 677 226 704 c
h
f
Q
q
[26.582434 0 0 26.585632 866.89978 103.116905] cm
/DeviceRGB {} CS
[1 1 1] SC
/DeviceRGB {} cs
[1 1 1] sc
6.5 8.62 m
6.513333 8.940001 6.52 9.303333 6.52 9.71 c
6.52 10.116667 6.513333 10.473333 6.5 10.78 c
6.5 11.139999 6.436667 11.496666 6.31 11.849999 c
6.183333 12.203333 5.993333 12.52 5.74 12.799999 c
5.486666 13.079999 5.17 13.299999 4.79 13.459999 c
4.41 13.619999 3.973334 13.699999 3.48 13.699999 c
2.46 13.699999 l
2.46 18.919998 l
2.46 19.106665 2.486667 19.243332 2.54 19.329998 c
2.593333 19.416664 2.646667 19.466663 2.7 19.479998 c
2.78 19.519997 2.866667 19.519997 2.96 19.479998 c
2.96 19.999998 l
0.54 19.999998 l
0.54 19.479998 l
0.646667 19.519997 0.726667 19.519997 0.78 19.479998 c
0.846667 19.466663 0.913333 19.416664 0.98 19.329998 c
1.046667 19.243332 1.08 19.106665 1.08 18.919998 c
1.08 6.779998 l
1.08 6.579998 1.046667 6.436665 0.98 6.349998 c
0.913333 6.263331 0.846667 6.206665 0.78 6.179998 c
0.726667 6.153331 0.646667 6.153331 0.54 6.179998 c
0.54 5.699998 l
3.48 5.699998 l
3.973334 5.699998 4.41 5.779998 4.79 5.939998 c
5.17 6.099998 5.486666 6.319997 5.74 6.599998 c
5.993333 6.879998 6.183333 7.193331 6.31 7.539998 c
6.436667 7.886664 6.5 8.246664 6.5 8.619998 c
6.5 8.62 l
h
5.08 8.18 m
5.08 7.833334 5.023333 7.533334 4.91 7.28 c
4.796667 7.026667 4.663333 6.826667 4.51 6.68 c
4.356667 6.533334 4.196666 6.416667 4.03 6.33 c
3.863333 6.243334 3.72 6.2 3.6 6.2 c
2.46 6.2 l
2.46 13.16 l
3.599999 13.16 l
3.72 13.16 3.863333 13.126666 4.029999 13.06 c
4.196666 12.993333 4.356666 12.876666 4.509999 12.709999 c
4.663333 12.543332 4.796666 12.339999 4.909999 12.099999 c
5.023333 11.86 5.079999 11.56 5.079999 11.2 c
5.079999 8.18 l
5.08 8.18 l
h
14.58 19.139999 m
14.58 19.246666 14.553333 19.356667 14.5 19.469999 c
14.446667 19.583332 14.37 19.689999 14.270001 19.789999 c
14.170001 19.889999 14.043334 19.98 13.89 20.059999 c
13.736667 20.139999 13.56 20.18 13.360001 20.18 c
13.040001 20.18 12.753334 20.106667 12.500001 19.960001 c
12.246668 19.813335 12.073335 19.620001 11.980001 19.380001 c
11.900002 19.246668 11.840001 19.093334 11.800001 18.920002 c
11.626668 19.280003 11.363335 19.580002 11.010001 19.820002 c
10.656668 20.060001 10.266668 20.18 9.840001 20.180002 c
9.306667 20.180002 8.840001 19.98667 8.440002 19.600002 c
8.213335 19.400003 8.026669 19.140003 7.880002 18.820002 c
7.733335 18.5 7.646668 18.133333 7.620002 17.720001 c
7.606669 17.680002 7.600002 17.600002 7.600002 17.480001 c
7.600002 17.460001 l
7.613335 17.233334 7.653335 16.996668 7.720002 16.750002 c
7.786668 16.503336 7.890002 16.25667 8.030002 16.010002 c
8.170002 15.763335 8.336668 15.530002 8.530002 15.310002 c
8.723335 15.090003 8.933335 14.900003 9.160002 14.740003 c
9.213335 14.713336 9.256668 14.683336 9.290002 14.650003 c
9.323336 14.616669 9.373335 14.580003 9.440002 14.540003 c
9.500002 14.510003 l
9.560002 14.480003 l
9.800002 14.333337 10.010002 14.200004 10.190002 14.080004 c
10.370003 13.960004 10.533336 13.846671 10.680002 13.740004 c
10.760002 13.68667 10.840002 13.62667 10.920002 13.560003 c
11.000002 13.493337 11.086669 13.42667 11.180002 13.360004 c
11.300002 13.280004 11.413336 13.18667 11.520002 13.080004 c
11.626669 12.973337 11.706669 12.84667 11.760002 12.700004 c
11.840002 12.56667 11.880002 12.406671 11.880002 12.220003 c
11.880002 12.033336 11.860002 11.86667 11.820002 11.720003 c
11.766668 11.42667 11.630002 11.173337 11.410002 10.960003 c
11.190002 10.746669 10.946669 10.606669 10.680002 10.540003 c
10.560002 10.500003 10.446669 10.480002 10.340002 10.480002 c
9.966668 10.480002 9.646668 10.546669 9.380002 10.680002 c
9.206669 10.760002 9.080002 10.850002 9.000002 10.950003 c
8.920002 11.050003 8.876669 11.143336 8.870002 11.230002 c
8.863335 11.316669 8.880002 11.386668 8.920002 11.440002 c
8.960002 11.493337 9.013335 11.533337 9.080002 11.560002 c
9.106669 11.573336 9.123335 11.580003 9.130002 11.580003 c
9.136669 11.580003 9.153336 11.58667 9.180002 11.600003 c
9.353335 11.66667 9.496669 11.783337 9.610003 11.950004 c
9.723336 12.116671 9.780003 12.300004 9.780003 12.500004 c
9.780003 12.753337 9.690002 12.970004 9.510002 13.150003 c
9.330002 13.330003 9.113336 13.420003 8.860003 13.420004 c
8.593336 13.420004 8.36667 13.330004 8.180002 13.150003 c
7.993335 12.970003 7.900002 12.753337 7.900002 12.500004 c
7.900002 12.273337 7.930002 12.036671 7.990002 11.790004 c
8.050002 11.543337 8.140002 11.320004 8.260002 11.120004 c
8.513335 10.74667 8.816669 10.47667 9.170002 10.310003 c
9.523336 10.143336 9.913335 10.060003 10.340002 10.060003 c
10.660002 10.060003 10.976668 10.120004 11.290002 10.240004 c
11.603335 10.360004 11.883335 10.530004 12.130002 10.750004 c
12.376669 10.970004 12.583335 11.233337 12.750002 11.540004 c
12.916669 11.84667 13.000002 12.200004 13.000002 12.600004 c
13.000002 18.480003 l
13.000002 18.720003 13.003335 18.876671 13.010002 18.950003 c
13.016669 19.023335 13.020002 19.073336 13.020002 19.100002 c
13.020002 19.273336 13.073336 19.413336 13.180002 19.520002 c
13.286669 19.626669 13.413336 19.680002 13.560002 19.680002 c
13.733335 19.680002 13.873336 19.630003 13.980002 19.530003 c
14.086669 19.430002 14.146669 19.300003 14.160003 19.140003 c
14.580003 19.140003 l
14.58 19.139999 l
h
11.44 18.639999 m
11.546666 18.453333 11.61 18.253332 11.629999 18.039999 c
11.649999 17.826666 11.659999 17.626665 11.659999 17.439999 c
11.659999 13.739999 l
11.606666 13.793332 11.549999 13.843332 11.489999 13.889998 c
11.429998 13.936665 11.366666 13.993332 11.299999 14.059999 c
10.913333 14.339998 10.583333 14.596665 10.31 14.829998 c
10.036666 15.063331 9.806666 15.303331 9.619999 15.549998 c
9.433332 15.796665 9.286665 16.06 9.179999 16.339998 c
9.073334 16.619999 9.006667 16.939999 8.98 17.299999 c
8.98 17.599998 l
8.993333 18.106665 9.13 18.503332 9.389999 18.789999 c
9.649999 19.076666 10.019999 19.219999 10.499999 19.219999 c
10.713332 19.219999 10.906666 19.166666 11.079999 19.059999 c
11.253332 18.939999 11.373332 18.799999 11.439999 18.639999 c
11.44 18.639999 l
h
18.32 10.08 m
19.08 10.093333 19.686666 10.323334 20.139999 10.770001 c
20.593332 11.216667 20.82 11.926667 20.82 12.900001 c
20.82 17.380001 l
20.82 18.353334 20.593332 19.059999 20.139999 19.5 c
19.686666 19.940001 19.08 20.173334 18.32 20.200001 c
18.26 20.200001 l
18.033333 20.200001 17.826666 20.173334 17.639999 20.120001 c
17.619999 20.120001 l
17.606665 20.120001 17.599998 20.113335 17.599998 20.1 c
17.293333 19.993334 17.026667 19.846666 16.799999 19.66 c
16.609999 19.469999 l
16.609999 19.469999 16.56 19.4 16.459999 19.26 c
16.459999 22.58 l
16.459999 22.766666 16.486666 22.903334 16.539999 22.99 c
16.593332 23.076666 16.646666 23.133333 16.699999 23.16 c
16.779999 23.199999 16.866667 23.199999 16.959999 23.16 c
16.959999 23.66 l
14.539999 23.66 l
14.539999 23.16 l
14.646666 23.199999 14.726666 23.199999 14.779999 23.16 c
14.846665 23.133333 14.909999 23.076666 14.969998 22.99 c
15.029998 22.903334 15.059998 22.766666 15.059999 22.58 c
15.059999 11.3 l
15.059999 11.113334 15.029999 10.976667 14.969998 10.89 c
14.909998 10.803333 14.846665 10.746667 14.779999 10.72 c
14.726666 10.693334 14.646666 10.693334 14.539999 10.72 c
14.539999 10.24 l
16.459999 10.24 l
16.459999 11.02 l
16.553331 10.873333 16.666666 10.74 16.799999 10.62 c
17.026667 10.42 17.293333 10.273334 17.599998 10.18 c
17.599998 10.166667 17.606665 10.16 17.619999 10.16 c
17.639999 10.16 l
17.853333 10.106667 18.059999 10.08 18.26 10.08 c
18.32 10.08 l
h
19.32 12.46 m
19.32 12.44 l
19.306665 11.76 19.223333 11.28 19.07 11 c
18.916666 10.72 18.666666 10.566667 18.32 10.54 c
18.08 10.526667 17.853333 10.556666 17.639999 10.63 c
17.426666 10.703334 17.24 10.826667 17.08 11 c
16.92 11.173333 16.786667 11.406666 16.68 11.7 c
16.573334 11.993334 16.5 12.34 16.460001 12.74 c
16.460001 17.540001 l
16.5 17.953335 16.573334 18.303335 16.68 18.59 c
16.786667 18.876665 16.92 19.103333 17.08 19.27 c
17.24 19.436668 17.426666 19.560001 17.639999 19.640001 c
17.853333 19.720001 18.08 19.753334 18.32 19.740002 c
18.666666 19.713335 18.916666 19.560001 19.07 19.280003 c
19.223333 19.000004 19.306665 18.520004 19.32 17.840002 c
19.32 17.820002 l
19.32 12.460001 l
19.32 12.46 l
h
24.379999 10.08 m
24.686665 10.08 24.999998 10.133333 25.32 10.24 c
25.640001 10.346666 25.926668 10.513333 26.18 10.74 c
26.433332 10.966666 26.636667 11.253333 26.790001 11.599999 c
26.943335 11.946666 27.020002 12.359999 27.02 12.839999 c
27.02 14.859999 l
27.02 14.919999 l
27.02 15.379999 l
23.040001 15.379999 l
23.040001 17.439999 l
23.040001 18.266665 23.183334 18.856665 23.470001 19.209999 c
23.756668 19.563334 24.113335 19.733334 24.540001 19.719999 c
24.793333 19.706665 25.043333 19.663332 25.290001 19.59 c
25.536669 19.516668 25.750002 19.393333 25.93 19.219999 c
26.109999 19.046665 26.26 18.799997 26.380001 18.48 c
26.500002 18.160002 26.560003 17.740002 26.560001 17.219999 c
27.02 17.219999 l
27.02 17.5 l
27.02 17.860001 26.98 18.203335 26.9 18.530001 c
26.82 18.856667 26.679998 19.140001 26.48 19.380001 c
26.280001 19.620001 26.023333 19.810001 25.709999 19.950001 c
25.396666 20.09 25.013332 20.166666 24.559999 20.18 c
24.24 20.18 23.913334 20.120001 23.58 20 c
23.246666 19.879999 22.946667 19.699999 22.68 19.459999 c
22.413334 19.219999 22.193335 18.936665 22.02 18.609999 c
21.846666 18.283333 21.76 17.913332 21.76 17.499998 c
21.76 17.029999 l
21.76 17.029999 21.756666 16.773333 21.75 16.259998 c
21.743334 15.746665 21.74 15.429998 21.74 15.309999 c
21.74 15.189999 21.736666 14.859999 21.73 14.319999 c
21.723333 13.779999 21.719999 13.489999 21.719999 13.449999 c
21.719999 12.859999 l
21.719999 12.379998 21.803333 11.963332 21.969999 11.609999 c
22.136665 11.256665 22.343332 10.969999 22.59 10.749999 c
22.836668 10.529999 23.116667 10.363333 23.43 10.249999 c
23.743334 10.136665 24.059999 10.079999 24.380001 10.079999 c
24.379999 10.08 l
h
25.68 14.86 m
25.68 13.28 l
25.68 12.773333 25.646667 12.349999 25.58 12.01 c
25.513332 11.670001 25.423332 11.396668 25.309999 11.190001 c
25.196667 10.983334 25.059999 10.84 24.9 10.76 c
24.74 10.68 24.559999 10.64 24.359999 10.64 c
23.933332 10.64 23.609999 10.826667 23.389999 11.200001 c
23.17 11.573335 23.059999 12.266667 23.059999 13.280001 c
23.059999 14.860001 l
25.68 14.860001 l
25.68 14.86 l
h
33.540001 11.66 m
33.540001 11.686667 33.546669 11.74 33.560001 11.82 c
33.573334 11.9 33.573334 11.966666 33.560001 12.02 c
33.546669 12.299999 33.450001 12.53 33.27 12.709999 c
33.09 12.889998 32.873333 12.979999 32.619999 12.98 c
32.353333 12.98 32.129997 12.886666 31.949999 12.7 c
31.77 12.513333 31.68 12.286667 31.679998 12.02 c
31.679998 11.686667 31.806665 11.433332 32.059998 11.259999 c
32.086666 11.246666 32.103333 11.236666 32.109997 11.23 c
32.139996 11.2 l
32.23333 11.133333 32.293327 11.036666 32.319996 10.91 c
32.346664 10.783334 32.266663 10.673333 32.079994 10.58 c
31.959993 10.513333 31.839994 10.48 31.719994 10.48 c
31.319994 10.453333 30.96666 10.573333 30.659994 10.839999 c
30.353329 11.106666 30.146662 11.573332 30.039993 12.239999 c
30.039993 18.919998 l
30.039993 19.106665 30.06666 19.243332 30.119993 19.329998 c
30.173326 19.416664 30.22666 19.466663 30.279993 19.479998 c
30.359993 19.519997 30.446661 19.519997 30.539993 19.479998 c
30.539993 19.999998 l
28.119993 19.999998 l
28.119993 19.479998 l
28.22666 19.519997 28.30666 19.519997 28.359993 19.479998 c
28.426661 19.466663 28.493326 19.416664 28.559994 19.329998 c
28.626661 19.243332 28.659994 19.106665 28.659994 18.919998 c
28.659994 11.299998 l
28.659994 11.113332 28.626661 10.976666 28.559994 10.889998 c
28.493326 10.803331 28.426661 10.746665 28.359993 10.719998 c
28.30666 10.693332 28.22666 10.693332 28.119993 10.719998 c
28.119993 10.219998 l
30.039993 10.219998 l
30.039993 11.039998 l
30.133326 10.893332 30.22666 10.756664 30.319994 10.629998 c
30.413328 10.503332 30.523329 10.396666 30.649994 10.309999 c
30.776659 10.223331 30.923326 10.153332 31.089994 10.099999 c
31.256662 10.046665 31.466661 10.019999 31.719994 10.019999 c
32.106663 10.019999 32.466663 10.159999 32.799995 10.439999 c
33.133327 10.719998 33.379997 11.126665 33.539997 11.659999 c
33.540001 11.66 l
h
36.240002 18.92 m
36.240002 19.106667 36.27 19.243334 36.330002 19.33 c
36.390003 19.416666 36.446667 19.466665 36.5 19.48 c
36.566666 19.519999 36.653332 19.519999 36.759998 19.48 c
36.759998 20 l
34.339996 20 l
34.339996 19.48 l
34.446663 19.519999 34.539997 19.519999 34.619995 19.48 c
34.659996 19.466665 34.713329 19.416666 34.779995 19.33 c
34.846661 19.243334 34.879993 19.106667 34.879993 18.92 c
34.879993 6.8 l
34.879993 6.6 34.846661 6.456667 34.779995 6.37 c
34.713329 6.283334 34.659996 6.226667 34.619995 6.2 c
34.539997 6.16 34.446663 6.16 34.339996 6.2 c
34.339996 5.72 l
36.239998 5.72 l
36.239998 6.8 l
36.239998 18.92 l
36.240002 18.92 l
h
40.580002 10.08 m
40.886669 10.08 41.200001 10.133333 41.52 10.24 c
41.84 10.346666 42.126667 10.513333 42.380001 10.74 c
42.633335 10.966666 42.83667 11.253333 42.990002 11.599999 c
43.143333 11.946666 43.220001 12.359999 43.220001 12.839999 c
43.220001 14.859999 l
43.220001 14.919999 l
43.220001 15.379999 l
39.240002 15.379999 l
39.240002 17.439999 l
39.240002 18.266665 39.383335 18.856665 39.670002 19.209999 c
39.956669 19.563334 40.313335 19.733334 40.740002 19.719999 c
40.993336 19.706665 41.243336 19.663332 41.490002 19.59 c
41.736668 19.516668 41.950001 19.393333 42.130001 19.219999 c
42.310001 19.046665 42.460003 18.799997 42.580002 18.48 c
42.700001 18.160002 42.760002 17.740002 42.760002 17.219999 c
43.220001 17.219999 l
43.220001 17.5 l
43.220001 17.860001 43.18 18.203335 43.100002 18.530001 c
43.020004 18.856667 42.880005 19.140001 42.680004 19.380001 c
42.480003 19.620001 42.223339 19.810001 41.910004 19.950001 c
41.596668 20.09 41.213337 20.166666 40.760002 20.18 c
40.440002 20.18 40.113335 20.120001 39.780003 20 c
39.446671 19.879999 39.146667 19.699999 38.880001 19.459999 c
38.613335 19.219999 38.393333 18.936665 38.220001 18.609999 c
38.046669 18.283333 37.960003 17.913332 37.960003 17.499998 c
37.960003 17.029999 l
37.960003 17.029999 37.956669 16.773333 37.950005 16.259998 c
37.94334 15.746665 37.940006 15.429998 37.940006 15.309999 c
37.940006 15.189999 37.936672 14.859999 37.930008 14.319999 c
37.923344 13.779999 37.92001 13.489999 37.92001 13.449999 c
37.92001 12.859999 l
37.92001 12.379998 38.003342 11.963332 38.17001 11.609999 c
38.336678 11.256665 38.543343 10.969999 38.790009 10.749999 c
39.036674 10.529999 39.316673 10.363333 39.630009 10.249999 c
39.943344 10.136665 40.26001 10.079999 40.580009 10.079999 c
40.580002 10.08 l
h
41.880001 14.86 m
41.880001 13.28 l
41.880001 12.773333 41.846668 12.349999 41.780003 12.01 c
41.713337 11.670001 41.623337 11.396668 41.510002 11.190001 c
41.396667 10.983334 41.260002 10.84 41.100002 10.76 c
40.940002 10.68 40.760002 10.64 40.560001 10.64 c
40.133335 10.64 39.810001 10.826667 39.59 11.200001 c
39.369999 11.573335 39.259998 12.266667 39.259998 13.280001 c
39.259998 14.860001 l
41.879997 14.860001 l
41.880001 14.86 l
h
47.880001 19.879999 m
47.866669 19.893333 47.856667 19.9 47.850002 19.9 c
47.843338 19.9 47.833336 19.906666 47.820004 19.92 c
47.800003 19.940001 l
47.773335 19.953335 47.743336 19.966667 47.710003 19.980001 c
47.67667 19.993336 47.646671 20.006668 47.620003 20.020002 c
47.340004 20.140003 47.013336 20.200003 46.640003 20.200003 c
46.42667 20.200003 46.220005 20.150003 46.020004 20.050003 c
45.820004 19.950003 45.646671 19.866671 45.500004 19.800003 c
45.353336 19.733335 45.230003 19.703337 45.130005 19.710003 c
45.030006 19.716669 44.980007 19.813337 44.980003 20.000004 c
44.600002 20.000004 l
44.600002 17.600004 l
45.080002 17.600004 l
45.053333 18.02667 45.100002 18.393337 45.220001 18.700005 c
45.299999 18.966671 45.456669 19.216671 45.690002 19.450005 c
45.923336 19.683338 46.273338 19.800005 46.740002 19.800005 c
47.046669 19.800005 47.299999 19.746672 47.5 19.640005 c
47.846668 19.466671 48.083332 19.260004 48.209999 19.020004 c
48.336666 18.780005 48.386665 18.530005 48.360001 18.270004 c
48.333336 18.010004 48.236668 17.753338 48.07 17.500004 c
47.903332 17.24667 47.699997 17.013336 47.459999 16.800003 c
47.299999 16.680002 47.133331 16.553337 46.959999 16.420004 c
46.786667 16.286671 46.613335 16.146671 46.439999 16.000004 c
45.973331 15.62667 45.603333 15.290004 45.329998 14.990004 c
45.056664 14.690003 44.846664 14.410004 44.699997 14.150003 c
44.553329 13.890003 44.453331 13.643336 44.399998 13.410004 c
44.346664 13.176671 44.32 12.926671 44.319996 12.660004 c
44.319996 12.500004 44.329994 12.353337 44.349995 12.220004 c
44.369995 12.086671 44.393326 11.966671 44.419994 11.860004 c
44.459995 11.633338 44.526661 11.413338 44.619995 11.200005 c
44.713329 10.986671 44.846661 10.796672 45.019997 10.630005 c
45.193333 10.463338 45.406666 10.326672 45.659996 10.220005 c
45.913326 10.113339 46.219994 10.060005 46.579994 10.060005 c
46.779995 10.060005 46.979992 10.103338 47.179993 10.190005 c
47.379993 10.276672 47.559994 10.356672 47.719994 10.430005 c
47.879993 10.503338 48.00666 10.536672 48.099995 10.530006 c
48.193329 10.523339 48.239994 10.420006 48.239994 10.220005 c
48.619995 10.220005 l
48.619995 12.700005 l
48.099995 12.700005 l
48.113327 12.300004 48.066662 11.940004 47.959995 11.620005 c
47.866661 11.340005 47.703327 11.086671 47.469994 10.860004 c
47.23666 10.633338 46.893326 10.520004 46.439995 10.520004 c
46.079994 10.520004 45.786659 10.600004 45.559994 10.760004 c
45.439995 10.853337 45.336658 10.990005 45.249992 11.170004 c
45.163326 11.350003 45.113323 11.54667 45.099991 11.760004 c
45.086658 11.973338 45.119991 12.203338 45.199989 12.450005 c
45.279987 12.696671 45.439987 12.926671 45.679989 13.140005 c
45.706657 13.180005 45.743324 13.220005 45.789989 13.260005 c
45.836655 13.300005 45.886658 13.333338 45.939991 13.360005 c
45.979992 13.386672 46.013325 13.413339 46.039989 13.440005 c
46.119991 13.520005 l
46.266659 13.640005 46.41666 13.760005 46.569992 13.880005 c
46.723324 14.000005 46.879993 14.120005 47.039993 14.240005 c
47.413326 14.560005 47.723328 14.850005 47.969994 15.110004 c
48.21666 15.370004 48.423325 15.613338 48.589993 15.840004 c
48.75666 16.066671 48.879993 16.280004 48.959991 16.480003 c
49.039989 16.680002 49.086658 16.873337 49.099991 17.060003 c
49.233326 17.673336 49.209991 18.190002 49.029991 18.610003 c
48.849991 19.030003 48.619991 19.360003 48.339993 19.600002 c
48.319992 19.600002 l
48.319992 19.626669 48.30666 19.640003 48.279991 19.640003 c
48.253323 19.66667 48.226658 19.68667 48.199989 19.700003 c
48.07999 19.780003 l
48.07999 19.780003 48.039989 19.800003 47.959991 19.840002 c
47.959991 19.860003 l
47.946659 19.860003 47.933323 19.863337 47.919991 19.870003 c
47.906658 19.876669 47.893322 19.880003 47.87999 19.880003 c
47.880001 19.879999 l
h
53.82 19.879999 m
53.806667 19.893333 53.796665 19.9 53.790001 19.9 c
53.783337 19.9 53.773335 19.906666 53.760002 19.92 c
53.740002 19.940001 l
53.713333 19.953335 53.683334 19.966667 53.650002 19.980001 c
53.616669 19.993336 53.58667 20.006668 53.560001 20.020002 c
53.280003 20.140003 52.953335 20.200003 52.580002 20.200003 c
52.366669 20.200003 52.160004 20.150003 51.960003 20.050003 c
51.760002 19.950003 51.58667 19.866671 51.440002 19.800003 c
51.293335 19.733335 51.170002 19.703337 51.070004 19.710003 c
50.970005 19.716669 50.920006 19.813337 50.920002 20.000004 c
50.540001 20.000004 l
50.540001 17.600004 l
51.02 17.600004 l
50.993332 18.02667 51.040001 18.393337 51.16 18.700005 c
51.239998 18.966671 51.396667 19.216671 51.630001 19.450005 c
51.863335 19.683338 52.213337 19.800005 52.68 19.800005 c
52.986668 19.800005 53.239998 19.746672 53.439999 19.640005 c
53.786667 19.466671 54.023331 19.260004 54.149998 19.020004 c
54.276665 18.780005 54.326664 18.530005 54.299999 18.270004 c
54.273335 18.010004 54.176666 17.753338 54.009998 17.500004 c
53.84333 17.24667 53.639996 17.013336 53.399998 16.800003 c
53.239998 16.680002 53.07333 16.553337 52.899998 16.420004 c
52.726665 16.286671 52.553333 16.146671 52.379997 16.000004 c
51.91333 15.62667 51.543331 15.290004 51.269997 14.990004 c
50.996662 14.690003 50.786663 14.410004 50.639996 14.150003 c
50.493328 13.890003 50.39333 13.643336 50.339996 13.410004 c
50.286663 13.176671 50.259998 12.926671 50.259995 12.660004 c
50.259995 12.500004 50.269993 12.353337 50.289993 12.220004 c
50.309994 12.086671 50.333324 11.966671 50.359993 11.860004 c
50.399994 11.633338 50.46666 11.413338 50.559994 11.200005 c
50.653328 10.986671 50.786659 10.796672 50.959995 10.630005 c
51.133331 10.463338 51.346664 10.326672 51.599995 10.220005 c
51.853325 10.113339 52.159992 10.060005 52.519993 10.060005 c
52.719994 10.060005 52.919991 10.103338 53.119991 10.190005 c
53.319992 10.276672 53.499992 10.356672 53.659992 10.430005 c
53.819992 10.503338 53.946659 10.536672 54.039993 10.530006 c
54.133327 10.523339 54.179993 10.420006 54.179993 10.220005 c
54.559994 10.220005 l
54.559994 12.700005 l
54.039993 12.700005 l
54.053326 12.300004 54.00666 11.940004 53.899994 11.620005 c
53.80666 11.340005 53.643326 11.086671 53.409992 10.860004 c
53.176659 10.633338 52.833324 10.520004 52.379993 10.520004 c
52.019993 10.520004 51.726658 10.600004 51.499992 10.760004 c
51.379993 10.853337 51.276657 10.990005 51.189991 11.170004 c
51.103325 11.350003 51.053322 11.54667 51.039989 11.760004 c
51.026657 11.973338 51.05999 12.203338 51.139988 12.450005 c
51.219986 12.696671 51.379986 12.926671 51.619987 13.140005 c
51.646656 13.180005 51.683323 13.220005 51.729988 13.260005 c
51.776653 13.300005 51.826656 13.333338 51.87999 13.360005 c
51.919991 13.386672 51.953323 13.413339 51.979988 13.440005 c
52.05999 13.520005 l
52.206657 13.640005 52.356659 13.760005 52.509991 13.880005 c
52.663322 14.000005 52.819992 14.120005 52.979992 14.240005 c
53.353325 14.560005 53.663326 14.850005 53.909992 15.110004 c
54.156658 15.370004 54.363323 15.613338 54.529991 15.840004 c
54.696659 16.066671 54.819992 16.280004 54.89999 16.480003 c
54.979988 16.680002 55.026657 16.873337 55.039989 17.060003 c
55.173325 17.673336 55.14999 18.190002 54.96999 18.610003 c
54.789989 19.030003 54.55999 19.360003 54.279991 19.600002 c
54.259991 19.600002 l
54.259991 19.626669 54.246658 19.640003 54.21999 19.640003 c
54.193321 19.66667 54.166656 19.68667 54.139988 19.700003 c
54.019989 19.780003 l
54.019989 19.780003 53.979988 19.800003 53.89999 19.840002 c
53.89999 19.860003 l
53.886658 19.860003 53.873322 19.863337 53.859989 19.870003 c
53.846657 19.876669 53.833321 19.880003 53.819988 19.880003 c
53.82 19.879999 l
h
f
Q
Q
Q
showpage
%%PageTrailer
pdfEndPage
%%Trailer
end
%%DocumentSuppliedResources:
%%EOF
================================================
FILE: scripts/docker-entrypoint.sh
================================================
#!/bin/bash
set -e
# Source: https://github.com/sameersbn/docker-gitlab/
map_uidgid() {
USERMAP_ORIG_UID=$(id -u paperless)
USERMAP_ORIG_GID=$(id -g paperless)
USERMAP_NEW_UID=${USERMAP_UID:-$USERMAP_ORIG_UID}
USERMAP_NEW_GID=${USERMAP_GID:-${USERMAP_ORIG_GID:-$USERMAP_NEW_UID}}
if [[ ${USERMAP_NEW_UID} != "${USERMAP_ORIG_UID}" || ${USERMAP_NEW_GID} != "${USERMAP_ORIG_GID}" ]]; then
echo "Mapping UID and GID for paperless:paperless to $USERMAP_NEW_UID:$USERMAP_NEW_GID"
usermod -u "${USERMAP_NEW_UID}" paperless
groupmod -o -g "${USERMAP_NEW_GID}" paperless
fi
}
set_permissions() {
# Set permissions for consumption and export directory
for dir in PAPERLESS_CONSUMPTION_DIR PAPERLESS_EXPORT_DIR; do
# Extract the name of the current directory from $dir for the error message
cur_dir_name=$(echo "$dir" | awk -F'_' '{ print tolower($2); }')
chgrp paperless "${!dir}" || {
echo "Changing group of ${cur_dir_name} directory:"
echo " ${!dir}"
echo "failed."
echo ""
echo "Either try to set it on your host-mounted directory"
echo "directly, or make sure that the directory has \`g+wx\`"
echo "permissions and the files in it at least \`o+r\`."
} >&2
chmod g+wx "${!dir}" || {
echo "Changing group permissions of ${cur_dir_name} directory:"
echo " ${!dir}"
echo "failed."
echo ""
echo "Either try to set it on your host-mounted directory"
echo "directly, or make sure that the directory has \`g+wx\`"
echo "permissions and the files in it at least \`o+r\`."
} >&2
done
# Set permissions for application directory
chown -Rh paperless:paperless /usr/src/paperless
}
migrations() {
# A simple lock file in case other containers use this startup
LOCKFILE="/usr/src/paperless/data/db.sqlite3.migration"
# check for and create lock file in one command
if (set -o noclobber; echo "$$" > "${LOCKFILE}") 2> /dev/null
then
trap 'rm -f "${LOCKFILE}"; exit $?' INT TERM EXIT
sudo -HEu paperless "/usr/src/paperless/src/manage.py" "migrate"
rm ${LOCKFILE}
fi
}
initialize() {
map_uidgid
set_permissions
migrations
}
install_languages() {
local langs="$1"
read -ra langs <<<"$langs"
# Check that it is not empty
if [ ${#langs[@]} -eq 0 ]; then
return
fi
# Loop over languages to be installed
for lang in "${langs[@]}"; do
pkg="tesseract-ocr-data-$lang"
# English is installed by default
if [[ "$lang" == "eng" ]]; then
continue
fi
if apk info -e "$pkg" > /dev/null 2>&1; then
continue
fi
if ! apk --no-cache info "$pkg" > /dev/null 2>&1; then
continue
fi
apk --no-cache --update add "$pkg"
done
}
if [[ "$1" != "/"* ]]; then
initialize
# Install additional languages if specified
if [[ ! -z "$PAPERLESS_OCR_LANGUAGES" ]]; then
install_languages "$PAPERLESS_OCR_LANGUAGES"
fi
if [[ "$1" = "gunicorn" ]]; then
shift
EXTRA_PARAMS=""
SSL_KEY_PATH="/usr/src/paperless/data/ssl.key"
SSL_CERT_PATH="/usr/src/paperless/data/ssl.cert"
if [ "${PAPERLESS_USE_SSL}" = "true" ]; then
if [ -f "${SSL_KEY_PATH}" ] && [ -f "${SSL_CERT_PATH}" ]; then
EXTRA_PARAMS="--certfile=${SSL_CERT_PATH} --keyfile=${SSL_KEY_PATH}"
else
echo "Error: Could not find certfile in ${SSL_CERT_PATH} or keyfile in ${SSL_KEY_PATH}, but \$PAPERLESS_USE_SSL is true. Starting without SSL enabled."
fi
fi
cd /usr/src/paperless/src/ && \
exec sudo -HEu paperless /usr/bin/gunicorn -c /usr/src/paperless/gunicorn.conf ${EXTRA_PARAMS} "$@" paperless.wsgi
else
exec sudo -HEu paperless "/usr/src/paperless/src/manage.py" "$@"
fi
fi
exec "$@"
================================================
FILE: scripts/gunicorn.conf
================================================
bind = '127.0.0.1:8000'
backlog = 2048
workers = 3
worker_class = 'sync'
worker_connections = 1000
timeout = 20
keepalive = 2
spew = False
daemon = False
pidfile = None
umask = 0
user = None
group = None
tmp_upload_dir = None
loglevel = 'info'
errorlog = '-'
accesslog = '-'
proc_name = None
def pre_fork(server, worker):
pass
def pre_exec(server):
server.log.info("Forked child, re-executing.")
def when_ready(server):
server.log.info("Server is ready. Spawning workers")
def worker_int(worker):
worker.log.info("worker received INT or QUIT signal")
## get traceback info
import threading, sys, traceback
id2name = dict([(th.ident, th.name) for th in threading.enumerate()])
code = []
for threadId, stack in sys._current_frames().items():
code.append("\n# Thread: %s(%d)" % (id2name.get(threadId,""),
threadId))
for filename, lineno, name, line in traceback.extract_stack(stack):
code.append('File: "%s", line %d, in %s' % (filename,
lineno, name))
if line:
code.append(" %s" % (line.strip()))
worker.log.debug("\n".join(code))
def worker_abort(worker):
worker.log.info("worker received SIGABRT signal")
================================================
FILE: scripts/paperless-consumer.service
================================================
[Unit]
Description=Paperless consumer
[Service]
User=paperless
Group=paperless
ExecStart=/home/paperless/project/virtualenv/bin/python /home/paperless/project/src/manage.py document_consumer
[Install]
WantedBy=multi-user.target
================================================
FILE: scripts/paperless-webserver.service
================================================
[Unit]
Description=Paperless webserver
After=network.target
Wants=network.target
[Service]
User=paperless
Group=paperless
ExecStart=/home/paperless/project/virtualenv/bin/gunicorn --pythonpath=/home/paperless/project/src paperless.wsgi -w 2
[Install]
WantedBy=multi-user.target
================================================
FILE: scripts/post-consumption-example.sh
================================================
#!/usr/bin/env bash
DOCUMENT_ID=${1}
DOCUMENT_FILE_NAME=${2}
DOCUMENT_SOURCE_PATH=${3}
DOCUMENT_THUMBNAIL_PATH=${4}
DOCUMENT_DOWNLOAD_URL=${5}
DOCUMENT_THUMBNAIL_URL=${6}
DOCUMENT_CORRESPONDENT=${7}
DOCUMENT_TAGS=${8}
echo "
A document with an id of ${DOCUMENT_ID} was just consumed. I know the
following additional information about it:
* Generated File Name: ${DOCUMENT_FILE_NAME}
* Source Path: ${DOCUMENT_SOURCE_PATH}
* Thumbnail Path: ${DOCUMENT_THUMBNAIL_PATH}
* Download URL: ${DOCUMENT_DOWNLOAD_URL}
* Thumbnail URL: ${DOCUMENT_THUMBNAIL_URL}
* Correspondent: ${DOCUMENT_CORRESPONDENT}
* Tags: ${DOCUMENT_TAGS}
It was consumed with the passphrase ${PASSPHRASE}
"
================================================
FILE: src/documents/__init__.py
================================================
from .checks import changed_password_check
================================================
FILE: src/documents/actions.py
================================================
from django.contrib import messages
from django.contrib.admin import helpers
from django.contrib.admin.utils import model_ngettext
from django.core.exceptions import PermissionDenied
from django.template.response import TemplateResponse
from documents.models import Correspondent, Tag
def select_action(
modeladmin, request, queryset, title, action, modelclass,
success_message="", document_action=None, queryset_action=None):
opts = modeladmin.model._meta
app_label = opts.app_label
if not modeladmin.has_change_permission(request):
raise PermissionDenied
if request.POST.get('post'):
n = queryset.count()
selected_object = modelclass.objects.get(id=request.POST.get('obj_id'))
if n:
for document in queryset:
if document_action:
document_action(document, selected_object)
document_display = str(document)
modeladmin.log_change(request, document, document_display)
if queryset_action:
queryset_action(queryset, selected_object)
modeladmin.message_user(request, success_message % {
"selected_object": selected_object.name,
"count": n,
"items": model_ngettext(modeladmin.opts, n)
}, messages.SUCCESS)
# Return None to display the change list page again.
return None
context = dict(
modeladmin.admin_site.each_context(request),
title=title,
queryset=queryset,
opts=opts,
action_checkbox_name=helpers.ACTION_CHECKBOX_NAME,
media=modeladmin.media,
action=action,
objects=modelclass.objects.all(),
itemname=model_ngettext(modelclass, 1)
)
request.current_app = modeladmin.admin_site.name
return TemplateResponse(
request,
"admin/{}/{}/select_object.html".format(app_label, opts.model_name),
context
)
def simple_action(
modeladmin, request, queryset, success_message="",
document_action=None, queryset_action=None):
if not modeladmin.has_change_permission(request):
raise PermissionDenied
n = queryset.count()
if n:
for document in queryset:
if document_action:
document_action(document)
document_display = str(document)
modeladmin.log_change(request, document, document_display)
if queryset_action:
queryset_action(queryset)
modeladmin.message_user(request, success_message % {
"count": n, "items": model_ngettext(modeladmin.opts, n)
}, messages.SUCCESS)
# Return None to display the change list page again.
return None
def add_tag_to_selected(modeladmin, request, queryset):
return select_action(
modeladmin=modeladmin,
request=request,
queryset=queryset,
title="Add tag to multiple documents",
action="add_tag_to_selected",
modelclass=Tag,
success_message="Successfully added tag %(selected_object)s to "
"%(count)d %(items)s.",
document_action=lambda doc, tag: doc.tags.add(tag)
)
def remove_tag_from_selected(modeladmin, request, queryset):
return select_action(
modeladmin=modeladmin,
request=request,
queryset=queryset,
title="Remove tag from multiple documents",
action="remove_tag_from_selected",
modelclass=Tag,
success_message="Successfully removed tag %(selected_object)s from "
"%(count)d %(items)s.",
document_action=lambda doc, tag: doc.tags.remove(tag)
)
def set_correspondent_on_selected(modeladmin, request, queryset):
return select_action(
modeladmin=modeladmin,
request=request,
queryset=queryset,
title="Set correspondent on multiple documents",
action="set_correspondent_on_selected",
modelclass=Correspondent,
success_message="Successfully set correspondent %(selected_object)s "
"on %(count)d %(items)s.",
queryset_action=lambda qs, corr: qs.update(correspondent=corr)
)
def remove_correspondent_from_selected(modeladmin, request, queryset):
return simple_action(
modeladmin=modeladmin,
request=request,
queryset=queryset,
success_message="Successfully removed correspondent from %(count)d "
"%(items)s.",
queryset_action=lambda qs: qs.update(correspondent=None)
)
add_tag_to_selected.short_description = "Add tag to selected documents"
remove_tag_from_selected.short_description = \
"Remove tag from selected documents"
set_correspondent_on_selected.short_description = \
"Set correspondent on selected documents"
remove_correspondent_from_selected.short_description = \
"Remove correspondent from selected documents"
================================================
FILE: src/documents/admin.py
================================================
from datetime import datetime, timedelta
from django.conf import settings
from django.contrib import admin, messages
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
from django.contrib.auth.models import Group, User
from django.db import models
from django.http import HttpResponseRedirect
from django.templatetags.static import static
from django.urls import reverse
from django.utils.html import format_html, format_html_join
from django.utils.http import urlquote
from django.utils.safestring import mark_safe
from djangoql.admin import DjangoQLSearchMixin
from documents.actions import (
add_tag_to_selected,
remove_correspondent_from_selected,
remove_tag_from_selected,
set_correspondent_on_selected
)
from .models import Correspondent, Document, Log, Tag
class FinancialYearFilter(admin.SimpleListFilter):
title = "Financial Year"
parameter_name = "fy"
_fy_wraps = None
def _fy_start(self, year):
"""Return date of the start of financial year for the given year."""
fy_start = "{}-{}".format(str(year), settings.FY_START)
return datetime.strptime(fy_start, "%Y-%m-%d").date()
def _fy_end(self, year):
"""Return date of the end of financial year for the given year."""
fy_end = "{}-{}".format(str(year), settings.FY_END)
return datetime.strptime(fy_end, "%Y-%m-%d").date()
def _fy_does_wrap(self):
"""Return whether the financial year spans across two years."""
if self._fy_wraps is None:
start = "{}".format(settings.FY_START)
start = datetime.strptime(start, "%m-%d").date()
end = "{}".format(settings.FY_END)
end = datetime.strptime(end, "%m-%d").date()
self._fy_wraps = end < start
return self._fy_wraps
def _determine_fy(self, date):
"""Return a (query, display) financial year tuple of the given date."""
if self._fy_does_wrap():
fy_start = self._fy_start(date.year)
if date.date() >= fy_start:
query = "{}-{}".format(date.year, date.year + 1)
else:
query = "{}-{}".format(date.year - 1, date.year)
# To keep it simple we use the same string for both
# query parameter and the display.
return query, query
else:
query = "{0}-{0}".format(date.year)
display = "{}".format(date.year)
return query, display
def lookups(self, request, model_admin):
if not settings.FY_START or not settings.FY_END:
return None
r = []
for document in Document.objects.all():
r.append(self._determine_fy(document.created))
return sorted(set(r), key=lambda x: x[0], reverse=True)
def queryset(self, request, queryset):
if not self.value() or not settings.FY_START or not settings.FY_END:
return None
start, end = self.value().split("-")
return queryset.filter(created__gte=self._fy_start(start),
created__lte=self._fy_end(end))
class RecentCorrespondentFilter(admin.RelatedFieldListFilter):
"""
If PAPERLESS_RECENT_CORRESPONDENT_YEARS is set, we limit the available
correspondents to documents sent our way over the past ``n`` years.
"""
def field_choices(self, field, request, model_admin):
years = settings.PAPERLESS_RECENT_CORRESPONDENT_YEARS
correspondents = Correspondent.objects.all()
if years and years > 0:
self.title = "Correspondent (Recent)"
days = 365 * years
correspondents = correspondents.filter(
documents__created__gte=datetime.now() - timedelta(days=days)
).distinct()
return [(c.id, c.name) for c in correspondents]
class CommonAdmin(admin.ModelAdmin):
list_per_page = settings.PAPERLESS_LIST_PER_PAGE
class CorrespondentAdmin(CommonAdmin):
list_display = (
"name",
"match",
"matching_algorithm",
"document_count",
"last_correspondence"
)
list_filter = ("matching_algorithm",)
list_editable = ("match", "matching_algorithm")
readonly_fields = ("slug",)
def get_queryset(self, request):
qs = super(CorrespondentAdmin, self).get_queryset(request)
qs = qs.annotate(
document_count=models.Count("documents"),
last_correspondence=models.Max("documents__created")
)
return qs
def document_count(self, obj):
return obj.document_count
document_count.admin_order_field = "document_count"
def last_correspondence(self, obj):
return obj.last_correspondence
last_correspondence.admin_order_field = "last_correspondence"
class TagAdmin(CommonAdmin):
list_display = (
"name", "colour", "match", "matching_algorithm", "document_count")
list_filter = ("colour", "matching_algorithm")
list_editable = ("colour", "match", "matching_algorithm")
readonly_fields = ("slug",)
class Media:
js = ("js/colours.js",)
def get_queryset(self, request):
qs = super(TagAdmin, self).get_queryset(request)
qs = qs.annotate(document_count=models.Count("documents"))
return qs
def document_count(self, obj):
return obj.document_count
document_count.admin_order_field = "document_count"
class DocumentAdmin(DjangoQLSearchMixin, CommonAdmin):
class Media:
css = {
"all": ("paperless.css",)
}
search_fields = ("correspondent__name", "title", "content", "tags__name")
readonly_fields = ("added", "file_type", "storage_type",)
list_display = ("title", "created", "added", "thumbnail", "correspondent",
"tags_")
list_filter = (
"tags",
("correspondent", RecentCorrespondentFilter),
FinancialYearFilter
)
filter_horizontal = ("tags",)
ordering = ["-created", "correspondent"]
actions = [
add_tag_to_selected,
remove_tag_from_selected,
set_correspondent_on_selected,
remove_correspondent_from_selected
]
date_hierarchy = "created"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.document_queue = []
def has_add_permission(self, request):
return False
def created_(self, obj):
return obj.created.date().strftime("%Y-%m-%d")
created_.short_description = "Created"
def changelist_view(self, request, extra_context=None):
response = super().changelist_view(
request,
extra_context=extra_context
)
if request.method == "GET":
cl = self.get_changelist_instance(request)
self.document_queue = [doc.id for doc in cl.queryset]
return response
def change_view(self, request, object_id=None, form_url='',
extra_context=None):
extra_context = extra_context or {}
if self.document_queue and object_id:
if int(object_id) in self.document_queue:
# There is a queue of documents
current_index = self.document_queue.index(int(object_id))
if current_index < len(self.document_queue) - 1:
# ... and there are still documents in the queue
extra_context["next_object"] = self.document_queue[
current_index + 1
]
return super(DocumentAdmin, self).change_view(
request,
object_id,
form_url,
extra_context=extra_context,
)
def response_change(self, request, obj):
# This is mostly copied from ModelAdmin.response_change()
opts = self.model._meta
preserved_filters = self.get_preserved_filters(request)
msg_dict = {
"name": opts.verbose_name,
"obj": format_html(
'{}',
urlquote(request.path),
obj
),
}
if "_saveandeditnext" in request.POST:
msg = format_html(
'The {name} "{obj}" was changed successfully. '
'Editing next object.',
**msg_dict
)
self.message_user(request, msg, messages.SUCCESS)
redirect_url = reverse(
"admin:{}_{}_change".format(opts.app_label, opts.model_name),
args=(request.POST["_next_object"],),
current_app=self.admin_site.name
)
redirect_url = add_preserved_filters(
{
"preserved_filters": preserved_filters,
"opts": opts
},
redirect_url
)
return HttpResponseRedirect(redirect_url)
return super().response_change(request, obj)
@mark_safe
def thumbnail(self, obj):
return self._html_tag(
"a",
self._html_tag(
"img",
src=reverse("fetch", kwargs={"kind": "thumb", "pk": obj.pk}),
width=180,
alt="Thumbnail of {}".format(obj.file_name),
title=obj.file_name
),
href=obj.download_url
)
@mark_safe
def tags_(self, obj):
r = ""
for tag in obj.tags.all():
colour = tag.get_colour_display()
r += self._html_tag(
"a",
tag.slug,
**{
"class": "tag",
"style": "background-color: {};".format(colour),
"href": "{}?tags__id__exact={}".format(
reverse("admin:documents_document_changelist"),
tag.pk
)
}
)
return r
@mark_safe
def document(self, obj):
# TODO: is this method even used anymore?
return self._html_tag(
"a",
self._html_tag(
"img",
src=static("documents/img/{}.png".format(obj.file_type)),
width=22,
height=22,
alt=obj.file_type,
title=obj.file_name
),
href=obj.download_url
)
@staticmethod
def _html_tag(kind, inside=None, **kwargs):
attributes = format_html_join(' ', '{}="{}"', kwargs.items())
if inside is not None:
return format_html("<{kind} {attributes}>{inside}{kind}>",
kind=kind, attributes=attributes, inside=inside)
return format_html("<{} {}/>", kind, attributes)
class LogAdmin(CommonAdmin):
list_display = ("created", "message", "level",)
list_filter = ("level", "created",)
admin.site.register(Correspondent, CorrespondentAdmin)
admin.site.register(Tag, TagAdmin)
admin.site.register(Document, DocumentAdmin)
admin.site.register(Log, LogAdmin)
# Unless we implement multi-user, these default registrations don't make sense.
admin.site.unregister(Group)
admin.site.unregister(User)
================================================
FILE: src/documents/apps.py
================================================
from django.apps import AppConfig
from django.db.models.signals import post_delete
class DocumentsConfig(AppConfig):
name = "documents"
def ready(self):
from .signals import document_consumption_started
from .signals import document_consumption_finished
from .signals.handlers import (
set_correspondent,
set_tags,
run_pre_consume_script,
run_post_consume_script,
cleanup_document_deletion,
set_log_entry
)
document_consumption_started.connect(run_pre_consume_script)
document_consumption_finished.connect(set_tags)
document_consumption_finished.connect(set_correspondent)
document_consumption_finished.connect(set_log_entry)
document_consumption_finished.connect(run_post_consume_script)
post_delete.connect(cleanup_document_deletion)
AppConfig.ready(self)
================================================
FILE: src/documents/checks.py
================================================
import textwrap
from django.conf import settings
from django.core.checks import Error, register
from django.db.utils import OperationalError, ProgrammingError
@register()
def changed_password_check(app_configs, **kwargs):
from documents.models import Document
from paperless.db import GnuPG
try:
encrypted_doc = Document.objects.filter(
storage_type=Document.STORAGE_TYPE_GPG).first()
except (OperationalError, ProgrammingError):
return [] # No documents table yet
if encrypted_doc:
if not settings.PASSPHRASE:
return [Error(
"The database contains encrypted documents but no password "
"is set."
)]
if not GnuPG.decrypted(encrypted_doc.source_file):
return [Error(textwrap.dedent(
"""
The current password doesn't match the password of the
existing documents.
If you intend to change your password, you must first export
all of the old documents, start fresh with the new password
and then re-import them."
"""))]
return []
================================================
FILE: src/documents/consumer.py
================================================
from django.db import transaction
import datetime
import hashlib
import logging
import os
import re
import time
import uuid
from operator import itemgetter
from django.conf import settings
from django.utils import timezone
from paperless.db import GnuPG
from .models import Document, FileInfo, Tag
from .parsers import ParseError
from .signals import (
document_consumer_declaration,
document_consumption_finished,
document_consumption_started
)
class ConsumerError(Exception):
pass
class Consumer:
"""
Loop over every file found in CONSUMPTION_DIR and:
1. Convert it to a greyscale pnm
2. Use tesseract on the pnm
3. Store the document in the MEDIA_ROOT with optional encryption
4. Store the OCR'd text in the database
5. Delete the document and image(s)
"""
# Files are considered ready for consumption if they have been unmodified
# for this duration
FILES_MIN_UNMODIFIED_DURATION = 0.5
def __init__(self, consume=settings.CONSUMPTION_DIR,
scratch=settings.SCRATCH_DIR):
self.logger = logging.getLogger(__name__)
self.logging_group = None
self._ignore = []
self.consume = consume
self.scratch = scratch
os.makedirs(self.scratch, exist_ok=True)
self.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
if settings.PASSPHRASE:
self.storage_type = Document.STORAGE_TYPE_GPG
if not self.consume:
raise ConsumerError(
"The CONSUMPTION_DIR settings variable does not appear to be "
"set."
)
if not os.path.exists(self.consume):
raise ConsumerError(
"Consumption directory {} does not exist".format(self.consume))
self.parsers = []
for response in document_consumer_declaration.send(self):
self.parsers.append(response[1])
if not self.parsers:
raise ConsumerError(
"No parsers could be found, not even the default. "
"This is a problem."
)
def log(self, level, message):
getattr(self.logger, level)(message, extra={
"group": self.logging_group
})
def consume_new_files(self):
"""
Find non-ignored files in consumption dir and consume them if they have
been unmodified for FILES_MIN_UNMODIFIED_DURATION.
"""
ignored_files = []
files = []
for entry in os.scandir(self.consume):
if entry.is_file():
file = (entry.path, entry.stat().st_mtime)
if file in self._ignore:
ignored_files.append(file)
else:
files.append(file)
else:
self.logger.warning(
"Skipping %s as it is not a file",
entry.path
)
if not files:
return
# Set _ignore to only include files that still exist.
# This keeps it from growing indefinitely.
self._ignore[:] = ignored_files
files_old_to_new = sorted(files, key=itemgetter(1))
time.sleep(self.FILES_MIN_UNMODIFIED_DURATION)
for file, mtime in files_old_to_new:
if mtime == os.path.getmtime(file):
# File has not been modified and can be consumed
if not self.try_consume_file(file):
self._ignore.append((file, mtime))
@transaction.atomic
def try_consume_file(self, file):
"""
Return True if file was consumed
"""
if not re.match(FileInfo.REGEXES["title"], file):
return False
doc = file
if self._is_duplicate(doc):
self.log(
"info",
"Skipping {} as it appears to be a duplicate".format(doc)
)
return False
parser_class = self._get_parser_class(doc)
if not parser_class:
self.log(
"error", "No parsers could be found for {}".format(doc))
return False
self.logging_group = uuid.uuid4()
self.log("info", "Consuming {}".format(doc))
document_consumption_started.send(
sender=self.__class__,
filename=doc,
logging_group=self.logging_group
)
parsed_document = parser_class(doc)
try:
thumbnail = parsed_document.get_optimised_thumbnail()
date = parsed_document.get_date()
document = self._store(
parsed_document.get_text(),
doc,
thumbnail,
date
)
except ParseError as e:
self.log("error", "PARSE FAILURE for {}: {}".format(doc, e))
parsed_document.cleanup()
return False
else:
parsed_document.cleanup()
self._cleanup_doc(doc)
self.log(
"info",
"Document {} consumption finished".format(document)
)
document_consumption_finished.send(
sender=self.__class__,
document=document,
logging_group=self.logging_group
)
return True
def _get_parser_class(self, doc):
"""
Determine the appropriate parser class based on the file
"""
options = []
for parser in self.parsers:
result = parser(doc)
if result:
options.append(result)
self.log(
"info",
"Parsers available: {}".format(
", ".join([str(o["parser"].__name__) for o in options])
)
)
if not options:
return None
# Return the parser with the highest weight.
return sorted(
options, key=lambda _: _["weight"], reverse=True)[0]["parser"]
def _store(self, text, doc, thumbnail, date):
file_info = FileInfo.from_path(doc)
stats = os.stat(doc)
self.log("debug", "Saving record to database")
created = file_info.created or date or timezone.make_aware(
datetime.datetime.fromtimestamp(stats.st_mtime))
with open(doc, "rb") as f:
document = Document.objects.create(
correspondent=file_info.correspondent,
title=file_info.title,
content=text,
file_type=file_info.extension,
checksum=hashlib.md5(f.read()).hexdigest(),
created=created,
modified=created,
storage_type=self.storage_type
)
relevant_tags = set(list(Tag.match_all(text)) + list(file_info.tags))
if relevant_tags:
tag_names = ", ".join([t.slug for t in relevant_tags])
self.log("debug", "Tagging with {}".format(tag_names))
document.tags.add(*relevant_tags)
# Create directory to store document in
document.create_source_directory()
# Safe document and thumbnail
self._write(document, doc, document.source_path)
self._write(document, thumbnail, document.thumbnail_path)
document.set_filename(document.source_filename)
document.save()
self.log("info", "Completed")
return document
def _write(self, document, source, target):
with open(source, "rb") as read_file:
with open(target, "wb") as write_file:
if document.storage_type == Document.STORAGE_TYPE_UNENCRYPTED:
write_file.write(read_file.read())
return
self.log("debug", "Encrypting")
write_file.write(GnuPG.encrypted(read_file))
def _cleanup_doc(self, doc):
self.log("debug", "Deleting document {}".format(doc))
os.unlink(doc)
@staticmethod
def _is_duplicate(doc):
with open(doc, "rb") as f:
checksum = hashlib.md5(f.read()).hexdigest()
return Document.objects.filter(checksum=checksum).exists()
================================================
FILE: src/documents/filters.py
================================================
from django_filters.rest_framework import BooleanFilter, FilterSet
from .models import Correspondent, Document, Tag
CHAR_KWARGS = (
"startswith", "endswith", "contains",
"istartswith", "iendswith", "icontains"
)
class CorrespondentFilterSet(FilterSet):
class Meta:
model = Correspondent
fields = {
"name": [
"startswith", "endswith", "contains",
"istartswith", "iendswith", "icontains"
],
"slug": ["istartswith", "iendswith", "icontains"]
}
class TagFilterSet(FilterSet):
class Meta:
model = Tag
fields = {
"name": [
"startswith", "endswith", "contains",
"istartswith", "iendswith", "icontains"
],
"slug": ["istartswith", "iendswith", "icontains"]
}
class DocumentFilterSet(FilterSet):
tags_empty = BooleanFilter(
label="Is tagged",
field_name="tags",
lookup_expr="isnull",
exclude=True
)
class Meta:
model = Document
fields = {
"title": CHAR_KWARGS,
"content": ("contains", "icontains"),
"correspondent__name": CHAR_KWARGS,
"correspondent__slug": CHAR_KWARGS,
"tags__name": CHAR_KWARGS,
"tags__slug": CHAR_KWARGS,
}
================================================
FILE: src/documents/forms.py
================================================
import magic
import os
from datetime import datetime
from time import mktime
from django import forms
from django.conf import settings
from .models import Document, Correspondent
class UploadForm(forms.Form):
TYPE_LOOKUP = {
"application/pdf": Document.TYPE_PDF,
"image/png": Document.TYPE_PNG,
"image/jpeg": Document.TYPE_JPG,
"image/gif": Document.TYPE_GIF,
"image/tiff": Document.TYPE_TIF,
}
correspondent = forms.CharField(
max_length=Correspondent._meta.get_field("name").max_length,
required=False
)
title = forms.CharField(
max_length=Document._meta.get_field("title").max_length,
required=False
)
document = forms.FileField()
def __init__(self, *args, **kwargs):
forms.Form.__init__(self, *args, **kwargs)
self._file_type = None
def clean_correspondent(self):
"""
I suppose it might look cleaner to use .get_or_create() here, but that
would also allow someone to fill up the db with bogus correspondents
before all validation was met.
"""
corresp = self.cleaned_data.get("correspondent")
if not corresp:
return None
if not Correspondent.SAFE_REGEX.match(corresp) or " - " in corresp:
raise forms.ValidationError(
"That correspondent name is suspicious.")
return corresp
def clean_title(self):
title = self.cleaned_data.get("title")
if not title:
return None
if not Correspondent.SAFE_REGEX.match(title) or " - " in title:
raise forms.ValidationError("That title is suspicious.")
return title
def clean_document(self):
document = self.cleaned_data.get("document").read()
with magic.Magic(flags=magic.MAGIC_MIME_TYPE) as m:
file_type = m.id_buffer(document)
if file_type not in self.TYPE_LOOKUP:
raise forms.ValidationError("The file type is invalid.")
self._file_type = self.TYPE_LOOKUP[file_type]
return document
def save(self):
"""
Since the consumer already does a lot of work, it's easier just to save
to-be-consumed files to the consumption directory rather than have the
form do that as well. Think of it as a poor-man's queue server.
"""
correspondent = self.cleaned_data.get("correspondent")
title = self.cleaned_data.get("title")
document = self.cleaned_data.get("document")
t = int(mktime(datetime.now().timetuple()))
file_name = os.path.join(
settings.CONSUMPTION_DIR,
"{} - {}.{}".format(correspondent, title, self._file_type)
)
with open(file_name, "wb") as f:
f.write(document)
os.utime(file_name, times=(t, t))
================================================
FILE: src/documents/loggers.py
================================================
import logging
class PaperlessLogger(logging.StreamHandler):
"""
A logger smart enough to know to log some kinds of messages to the database
for later retrieval in a pretty interface.
"""
def emit(self, record):
logging.StreamHandler.emit(self, record)
# We have to do the import here or Django will barf when it tries to
# load this because the apps aren't loaded at that point
from .models import Log
kwargs = {"message": record.msg, "level": record.levelno}
if hasattr(record, "group"):
kwargs["group"] = record.group
Log.objects.create(**kwargs)
================================================
FILE: src/documents/mail.py
================================================
import datetime
import imaplib
import logging
import os
import re
import time
import uuid
from base64 import b64decode
from email import policy
from email.parser import BytesParser
from dateutil import parser
from django.conf import settings
from .models import Correspondent
class MailFetcherError(Exception):
pass
class InvalidMessageError(MailFetcherError):
pass
class Loggable(object):
def __init__(self, group=None):
self.logger = logging.getLogger(__name__)
self.logging_group = group or uuid.uuid4()
def log(self, level, message):
getattr(self.logger, level)(message, extra={
"group": self.logging_group
})
class Message(Loggable):
"""
A crude, but simple email message class. We assume that there's a subject
and n attachments, and that we don't care about the message body.
"""
SECRET = os.getenv("PAPERLESS_EMAIL_SECRET")
def __init__(self, data, group=None):
"""
Cribbed heavily from
https://www.ianlewis.org/en/parsing-email-attachments-python
"""
Loggable.__init__(self, group=group)
self.subject = None
self.time = None
self.attachment = None
message = BytesParser(policy=policy.default).parsebytes(data)
self.subject = str(message["Subject"]).replace("\r\n", "")
self.body = str(message.get_body())
self.check_subject()
self.check_body()
self._set_time(message)
self.log("info", 'Importing email: "{}"'.format(self.subject))
attachments = []
for part in message.walk():
content_disposition = part.get("Content-Disposition")
if not content_disposition:
continue
dispositions = content_disposition.strip().split(";")
if len(dispositions) < 2:
continue
if not dispositions[0].lower() == "attachment" and \
"filename" not in dispositions[1].lower():
continue
file_data = part.get_payload()
attachments.append(Attachment(
b64decode(file_data), content_type=part.get_content_type()))
if len(attachments) == 0:
raise InvalidMessageError(
"There don't appear to be any attachments to this message")
if len(attachments) > 1:
raise InvalidMessageError(
"There's more than one attachment to this message. It cannot "
"be indexed automatically."
)
self.attachment = attachments[0]
def __bool__(self):
return bool(self.attachment)
def check_subject(self):
if self.subject is None:
raise InvalidMessageError("Message does not have a subject")
if not Correspondent.SAFE_REGEX.match(self.subject):
raise InvalidMessageError("Message subject is unsafe: {}".format(
self.subject))
def check_body(self):
if self.SECRET not in self.body:
raise InvalidMessageError("The secret wasn't in the body")
def _set_time(self, message):
self.time = datetime.datetime.now()
message_time = message.get("Date")
if message_time:
try:
self.time = parser.parse(message_time)
except (ValueError, AttributeError):
pass # We assume that "now" is ok
@property
def file_name(self):
return "{}.{}".format(self.subject, self.attachment.suffix)
class Attachment(object):
SAFE_SUFFIX_REGEX = re.compile(
r"^(application/(pdf))|(image/(png|jpeg|gif|tiff))$")
def __init__(self, data, content_type):
self.content_type = content_type
self.data = data
self.suffix = None
m = self.SAFE_SUFFIX_REGEX.match(self.content_type)
if not m:
raise MailFetcherError(
"Not-awesome file type: {}".format(self.content_type))
self.suffix = m.group(2) or m.group(4)
def read(self):
return self.data
class MailFetcher(Loggable):
def __init__(self, consume=settings.CONSUMPTION_DIR):
Loggable.__init__(self)
self._connection = None
self._host = os.getenv("PAPERLESS_CONSUME_MAIL_HOST")
self._port = os.getenv("PAPERLESS_CONSUME_MAIL_PORT")
self._username = os.getenv("PAPERLESS_CONSUME_MAIL_USER")
self._password = os.getenv("PAPERLESS_CONSUME_MAIL_PASS")
self._inbox = os.getenv("PAPERLESS_CONSUME_MAIL_INBOX", "INBOX")
self._enabled = bool(self._host)
if self._enabled and Message.SECRET is None:
raise MailFetcherError("No PAPERLESS_EMAIL_SECRET defined")
self.last_checked = time.time()
self.consume = consume
def pull(self):
"""
Fetch all available mail at the target address and store it locally in
the consumption directory so that the file consumer can pick it up and
do its thing.
"""
if self._enabled:
# Reset the grouping id for each fetch
self.logging_group = uuid.uuid4()
self.log("debug", "Checking mail")
for message in self._get_messages():
self.log("info", 'Storing email: "{}"'.format(message.subject))
t = int(time.mktime(message.time.timetuple()))
file_name = os.path.join(self.consume, message.file_name)
with open(file_name, "wb") as f:
f.write(message.attachment.data)
os.utime(file_name, times=(t, t))
self.last_checked = time.time()
def _get_messages(self):
r = []
try:
self._connect()
self._login()
for message in self._fetch():
if message:
r.append(message)
self._connection.expunge()
self._connection.close()
self._connection.logout()
except MailFetcherError as e:
self.log("error", str(e))
return r
def _connect(self):
try:
self._connection = imaplib.IMAP4_SSL(self._host, self._port)
except OSError as e:
msg = "Problem connecting to {}: {}".format(self._host, e.strerror)
raise MailFetcherError(msg)
def _login(self):
login = self._connection.login(self._username, self._password)
if not login[0] == "OK":
raise MailFetcherError("Can't log into mail: {}".format(login[1]))
inbox = self._connection.select(self._inbox)
if not inbox[0] == "OK":
raise MailFetcherError("Can't find the inbox: {}".format(inbox[1]))
def _fetch(self):
for num in self._connection.search(None, "ALL")[1][0].split():
__, data = self._connection.fetch(num, "(RFC822)")
message = None
try:
message = Message(data[0][1], self.logging_group)
except InvalidMessageError as e:
self.log("error", str(e))
else:
self._connection.store(num, "+FLAGS", "\\Deleted")
if message:
yield message
================================================
FILE: src/documents/management/__init__.py
================================================
================================================
FILE: src/documents/management/commands/__init__.py
================================================
================================================
FILE: src/documents/management/commands/change_storage_type.py
================================================
import os
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from termcolor import colored as coloured
from documents.models import Document
from paperless.db import GnuPG
class Command(BaseCommand):
help = (
"This is how you migrate your stored documents from an encrypted "
"state to an unencrypted one (or vice-versa)"
)
def add_arguments(self, parser):
parser.add_argument(
"from",
choices=("gpg", "unencrypted"),
help="The state you want to change your documents from"
)
parser.add_argument(
"to",
choices=("gpg", "unencrypted"),
help="The state you want to change your documents to"
)
parser.add_argument(
"--passphrase",
help="If PAPERLESS_PASSPHRASE isn't set already, you need to "
"specify it here"
)
def handle(self, *args, **options):
try:
print(coloured(
"\n\nWARNING: This script is going to work directly on your "
"document originals, so\nWARNING: you probably shouldn't run "
"this unless you've got a recent backup\nWARNING: handy. It "
"*should* work without a hitch, but be safe and backup your\n"
"WARNING: stuff first.\n\nHit Ctrl+C to exit now, or Enter to "
"continue.\n\n",
"yellow",
attrs=("bold",)
))
__ = input()
except KeyboardInterrupt:
return
if options["from"] == options["to"]:
raise CommandError(
'The "from" and "to" values can\'t be the same.'
)
passphrase = options["passphrase"] or settings.PASSPHRASE
if not passphrase:
raise CommandError(
"Passphrase not defined. Please set it with --passphrase or "
"by declaring it in your environment or your config."
)
if options["from"] == "gpg" and options["to"] == "unencrypted":
self.__gpg_to_unencrypted(passphrase)
elif options["from"] == "unencrypted" and options["to"] == "gpg":
self.__unencrypted_to_gpg(passphrase)
@staticmethod
def __gpg_to_unencrypted(passphrase):
encrypted_files = Document.objects.filter(
storage_type=Document.STORAGE_TYPE_GPG)
for document in encrypted_files:
print(coloured("Decrypting {}".format(
document).encode('utf-8'), "green"))
old_paths = [document.source_path, document.thumbnail_path]
raw_document = GnuPG.decrypted(document.source_file, passphrase)
raw_thumb = GnuPG.decrypted(document.thumbnail_file, passphrase)
document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
with open(document.source_path, "wb") as f:
f.write(raw_document)
with open(document.thumbnail_path, "wb") as f:
f.write(raw_thumb)
document.save(update_fields=("storage_type",))
for path in old_paths:
os.unlink(path)
@staticmethod
def __unencrypted_to_gpg(passphrase):
unencrypted_files = Document.objects.filter(
storage_type=Document.STORAGE_TYPE_UNENCRYPTED)
for document in unencrypted_files:
print(coloured("Encrypting {}".format(document), "green"))
old_paths = [document.source_path, document.thumbnail_path]
with open(document.source_path, "rb") as raw_document:
with open(document.thumbnail_path, "rb") as raw_thumb:
document.storage_type = Document.STORAGE_TYPE_GPG
with open(document.source_path, "wb") as f:
f.write(GnuPG.encrypted(raw_document, passphrase))
with open(document.thumbnail_path, "wb") as f:
f.write(GnuPG.encrypted(raw_thumb, passphrase))
document.save(update_fields=("storage_type",))
for path in old_paths:
os.unlink(path)
================================================
FILE: src/documents/management/commands/document_consumer.py
================================================
import logging
import os
import time
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from ...consumer import Consumer, ConsumerError
from ...mail import MailFetcher, MailFetcherError
try:
from inotify_simple import INotify, flags
except ImportError:
INotify = flags = None
class Command(BaseCommand):
"""
On every iteration of an infinite loop, consume what we can from the
consumption directory, and fetch any mail available.
"""
ORIGINAL_DOCS = os.path.join(settings.MEDIA_ROOT, "documents", "originals")
THUMB_DOCS = os.path.join(settings.MEDIA_ROOT, "documents", "thumbnails")
def __init__(self, *args, **kwargs):
self.verbosity = 0
self.logger = logging.getLogger(__name__)
self.file_consumer = None
self.mail_fetcher = None
self.first_iteration = True
BaseCommand.__init__(self, *args, **kwargs)
def add_arguments(self, parser):
parser.add_argument(
"directory",
default=settings.CONSUMPTION_DIR,
nargs="?",
help="The consumption directory."
)
parser.add_argument(
"--loop-time",
default=settings.CONSUMER_LOOP_TIME,
type=int,
help="Wait time between each loop (in seconds)."
)
parser.add_argument(
"--mail-delta",
default=10,
type=int,
help="Wait time between each mail fetch (in minutes)."
)
parser.add_argument(
"--oneshot",
action="store_true",
help="Run only once."
)
parser.add_argument(
"--no-inotify",
action="store_true",
help="Don't use inotify, even if it's available.",
default=False
)
def handle(self, *args, **options):
self.verbosity = options["verbosity"]
directory = options["directory"]
loop_time = options["loop_time"]
mail_delta = options["mail_delta"] * 60
use_inotify = INotify is not None and options["no_inotify"] is False
try:
self.file_consumer = Consumer(consume=directory)
self.mail_fetcher = MailFetcher(consume=directory)
except (ConsumerError, MailFetcherError) as e:
raise CommandError(e)
for d in (self.ORIGINAL_DOCS, self.THUMB_DOCS):
os.makedirs(d, exist_ok=True)
logging.getLogger(__name__).info(
"Starting document consumer at {}{}".format(
directory,
" with inotify" if use_inotify else ""
)
)
if options["oneshot"]:
self.loop_step(mail_delta)
else:
try:
if use_inotify:
self.loop_inotify(mail_delta)
else:
self.loop(loop_time, mail_delta)
except KeyboardInterrupt:
print("Exiting")
def loop(self, loop_time, mail_delta):
while True:
start_time = time.time()
if self.verbosity > 1:
print(".", int(start_time))
self.loop_step(mail_delta, start_time)
# Sleep until the start of the next loop step
time.sleep(max(0, start_time + loop_time - time.time()))
def loop_step(self, mail_delta, time_now=None):
# Occasionally fetch mail and store it to be consumed on the next loop
# We fetch email when we first start up so that it is not necessary to
# wait for 10 minutes after making changes to the config file.
next_mail_time = self.mail_fetcher.last_checked + mail_delta
if self.first_iteration or time_now > next_mail_time:
self.first_iteration = False
self.mail_fetcher.pull()
self.file_consumer.consume_new_files()
def loop_inotify(self, mail_delta):
directory = self.file_consumer.consume
inotify = INotify()
inotify.add_watch(directory, flags.CLOSE_WRITE | flags.MOVED_TO)
# Run initial mail fetch and consume all currently existing documents
self.loop_step(mail_delta)
next_mail_time = self.mail_fetcher.last_checked + mail_delta
while True:
# Consume documents until next_mail_time
while True:
delta = next_mail_time - time.time()
if delta > 0:
for event in inotify.read(timeout=delta):
file = os.path.join(directory, event.name)
if os.path.isfile(file):
self.file_consumer.try_consume_file(file)
else:
self.logger.warning(
"Skipping %s as it is not a file",
file
)
else:
break
self.mail_fetcher.pull()
next_mail_time = self.mail_fetcher.last_checked + mail_delta
================================================
FILE: src/documents/management/commands/document_correspondents.py
================================================
import sys
from django.core.management.base import BaseCommand
from documents.models import Correspondent, Document
from ...mixins import Renderable
class Command(Renderable, BaseCommand):
help = """
Using the current set of correspondent rules, apply said rules to all
documents in the database, effectively allowing you to back-tag all
previously indexed documents with correspondent created (or modified)
after their initial import.
""".replace(" ", "")
TOO_MANY_CONTINUE = (
"Detected {} potential correspondents for {}, so we've opted for {}")
TOO_MANY_SKIP = (
"Detected {} potential correspondents for {}, so we're skipping it")
CHANGE_MESSAGE = (
'Document {}: "{}" was given the correspondent id {}: "{}"')
def __init__(self, *args, **kwargs):
self.verbosity = 0
BaseCommand.__init__(self, *args, **kwargs)
def add_arguments(self, parser):
parser.add_argument(
"--use-first",
default=False,
action="store_true",
help="By default this command won't try to assign a correspondent "
"if more than one matches the document. Use this flag if "
"you'd rather it just pick the first one it finds."
)
def handle(self, *args, **options):
self.verbosity = options["verbosity"]
for document in Document.objects.filter(correspondent__isnull=True):
potential_correspondents = list(
Correspondent.match_all(document.content))
if not potential_correspondents:
continue
potential_count = len(potential_correspondents)
correspondent = potential_correspondents[0]
if potential_count > 1:
if not options["use_first"]:
print(
self.TOO_MANY_SKIP.format(potential_count, document),
file=sys.stderr
)
continue
print(
self.TOO_MANY_CONTINUE.format(
potential_count,
document,
correspondent
),
file=sys.stderr
)
document.correspondent = correspondent
document.save(update_fields=("correspondent",))
print(
self.CHANGE_MESSAGE.format(
document.pk,
document.title,
correspondent.pk,
correspondent.name
),
file=sys.stderr
)
================================================
FILE: src/documents/management/commands/document_exporter.py
================================================
import json
import os
import time
import shutil
from django.core.management.base import BaseCommand, CommandError
from django.core import serializers
from documents.models import Document, Correspondent, Tag
from paperless.db import GnuPG
from ...mixins import Renderable
from documents.settings import EXPORTER_FILE_NAME, EXPORTER_THUMBNAIL_NAME
class Command(Renderable, BaseCommand):
help = """
Decrypt and rename all files in our collection into a given target
directory. And include a manifest file containing document data for
easy import.
""".replace(" ", "")
def add_arguments(self, parser):
parser.add_argument("target")
parser.add_argument(
"--legacy",
action="store_true",
help="Don't try to export all of the document data, just dump the "
"original document files out in a format that makes "
"re-consuming them easy."
)
def __init__(self, *args, **kwargs):
BaseCommand.__init__(self, *args, **kwargs)
self.target = None
def handle(self, *args, **options):
self.target = options["target"]
if not os.path.exists(self.target):
raise CommandError("That path doesn't exist")
if not os.access(self.target, os.W_OK):
raise CommandError("That path doesn't appear to be writable")
if options["legacy"]:
self.dump_legacy()
else:
self.dump()
def dump(self):
documents = Document.objects.all()
document_map = {d.pk: d for d in documents}
manifest = json.loads(serializers.serialize("json", documents))
for index, document_dict in enumerate(manifest):
# Force output to unencrypted as that will be the current state.
# The importer will make the decision to encrypt or not.
manifest[index]["fields"]["storage_type"] = Document.STORAGE_TYPE_UNENCRYPTED # NOQA: E501
document = document_map[document_dict["pk"]]
file_target = os.path.join(self.target, document.file_name)
thumbnail_name = document.file_name + "-thumbnail.png"
thumbnail_target = os.path.join(self.target, thumbnail_name)
document_dict[EXPORTER_FILE_NAME] = document.file_name
document_dict[EXPORTER_THUMBNAIL_NAME] = thumbnail_name
print("Exporting: {}".format(file_target))
t = int(time.mktime(document.created.timetuple()))
if document.storage_type == Document.STORAGE_TYPE_GPG:
with open(file_target, "wb") as f:
f.write(GnuPG.decrypted(document.source_file))
os.utime(file_target, times=(t, t))
with open(thumbnail_target, "wb") as f:
f.write(GnuPG.decrypted(document.thumbnail_file))
os.utime(thumbnail_target, times=(t, t))
else:
shutil.copy(document.source_path, file_target)
shutil.copy(document.thumbnail_path, thumbnail_target)
manifest += json.loads(
serializers.serialize("json", Correspondent.objects.all()))
manifest += json.loads(serializers.serialize(
"json", Tag.objects.all()))
with open(os.path.join(self.target, "manifest.json"), "w") as f:
json.dump(manifest, f, indent=2)
def dump_legacy(self):
for document in Document.objects.all():
target = os.path.join(
self.target, self._get_legacy_file_name(document))
print("Exporting: {}".format(target))
with open(target, "wb") as f:
f.write(GnuPG.decrypted(document.source_file))
t = int(time.mktime(document.created.timetuple()))
os.utime(target, times=(t, t))
@staticmethod
def _get_legacy_file_name(doc):
if not doc.correspondent and not doc.title:
return os.path.basename(doc.source_path)
created = doc.created.strftime("%Y%m%d%H%M%SZ")
tags = ",".join([t.slug for t in doc.tags.all()])
if tags:
return "{} - {} - {} - {}.{}".format(
created, doc.correspondent, doc.title, tags, doc.file_type)
return "{} - {} - {}.{}".format(
created, doc.correspondent, doc.title, doc.file_type)
================================================
FILE: src/documents/management/commands/document_importer.py
================================================
import json
import os
import shutil
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from django.core.management import call_command
from documents.models import Document
from paperless.db import GnuPG
from ...mixins import Renderable
from documents.settings import EXPORTER_FILE_NAME, EXPORTER_THUMBNAIL_NAME
class Command(Renderable, BaseCommand):
help = """
Using a manifest.json file, load the data from there, and import the
documents it refers to.
""".replace(" ", "")
def add_arguments(self, parser):
parser.add_argument("source")
def __init__(self, *args, **kwargs):
BaseCommand.__init__(self, *args, **kwargs)
self.source = None
self.manifest = None
def handle(self, *args, **options):
self.source = options["source"]
if not os.path.exists(self.source):
raise CommandError("That path doesn't exist")
if not os.access(self.source, os.R_OK):
raise CommandError("That path doesn't appear to be readable")
manifest_path = os.path.join(self.source, "manifest.json")
self._check_manifest_exists(manifest_path)
with open(manifest_path) as f:
self.manifest = json.load(f)
self._check_manifest()
# Fill up the database with whatever is in the manifest
call_command("loaddata", manifest_path)
self._import_files_from_manifest()
@staticmethod
def _check_manifest_exists(path):
if not os.path.exists(path):
raise CommandError(
"That directory doesn't appear to contain a manifest.json "
"file."
)
def _check_manifest(self):
for record in self.manifest:
if not record["model"] == "documents.document":
continue
if EXPORTER_FILE_NAME not in record:
raise CommandError(
'The manifest file contains a record which does not '
'refer to an actual document file.'
)
doc_file = record[EXPORTER_FILE_NAME]
if not os.path.exists(os.path.join(self.source, doc_file)):
raise CommandError(
'The manifest file refers to "{}" which does not '
'appear to be in the source directory.'.format(doc_file)
)
def _import_files_from_manifest(self):
for record in self.manifest:
if not record["model"] == "documents.document":
continue
doc_file = record[EXPORTER_FILE_NAME]
thumb_file = record[EXPORTER_THUMBNAIL_NAME]
document = Document.objects.get(pk=record["pk"])
document_path = os.path.join(self.source, doc_file)
thumbnail_path = os.path.join(self.source, thumb_file)
if settings.PASSPHRASE:
with open(document_path, "rb") as unencrypted:
with open(document.source_path, "wb") as encrypted:
print("Encrypting {} and saving it to {}".format(
doc_file, document.source_path))
encrypted.write(GnuPG.encrypted(unencrypted))
with open(thumbnail_path, "rb") as unencrypted:
with open(document.thumbnail_path, "wb") as encrypted:
print("Encrypting {} and saving it to {}".format(
thumb_file, document.thumbnail_path))
encrypted.write(GnuPG.encrypted(unencrypted))
else:
shutil.copy(document_path, document.source_path)
shutil.copy(thumbnail_path, document.thumbnail_path)
# Reset the storage type to whatever we've used while importing
storage_type = Document.STORAGE_TYPE_UNENCRYPTED
if settings.PASSPHRASE:
storage_type = Document.STORAGE_TYPE_GPG
Document.objects.filter(
pk__in=[r["pk"] for r in self.manifest]
).update(
storage_type=storage_type
)
================================================
FILE: src/documents/management/commands/document_logs.py
================================================
from django.core.management.base import BaseCommand
from documents.models import Log
class Command(BaseCommand):
help = "A quick & dirty way to see what's in the logs"
def handle(self, *args, **options):
for l in Log.objects.order_by("pk"):
print(l)
================================================
FILE: src/documents/management/commands/document_renamer.py
================================================
from django.core.management.base import BaseCommand
from documents.models import Document, Tag
from ...mixins import Renderable
class Command(Renderable, BaseCommand):
help = """
This will rename all documents to match the latest filename format.
""".replace(" ", "")
def __init__(self, *args, **kwargs):
self.verbosity = 0
BaseCommand.__init__(self, *args, **kwargs)
def handle(self, *args, **options):
self.verbosity = options["verbosity"]
for document in Document.objects.all():
# Saving the document again will generate a new filename and rename
document.save()
================================================
FILE: src/documents/management/commands/document_retagger.py
================================================
from django.core.management.base import BaseCommand
from documents.models import Document, Tag
from ...mixins import Renderable
class Command(Renderable, BaseCommand):
help = """
Using the current set of tagging rules, apply said rules to all
documents in the database, effectively allowing you to back-tag all
previously indexed documents with tags created (or modified) after
their initial import.
""".replace(" ", "")
def __init__(self, *args, **kwargs):
self.verbosity = 0
BaseCommand.__init__(self, *args, **kwargs)
def handle(self, *args, **options):
self.verbosity = options["verbosity"]
for document in Document.objects.all():
tags = Tag.objects.exclude(
pk__in=document.tags.values_list("pk", flat=True))
for tag in Tag.match_all(document.content, tags):
print('Tagging {} with "{}"'.format(document, tag))
document.tags.add(tag)
================================================
FILE: src/documents/management/commands/loaddata_stdin.py
================================================
import sys
from django.core.management.commands.loaddata import Command as LoadDataCommand
class Command(LoadDataCommand):
"""
Allow the loading of data from standard in. Sourced originally from:
https://gist.github.com/bmispelon/ad5a2c333443b3a1d051 (MIT licensed)
"""
def parse_name(self, fixture_name):
self.compression_formats['stdin'] = (lambda x, y: sys.stdin, None)
if fixture_name == '-':
return '-', 'json', 'stdin'
def find_fixtures(self, fixture_label):
if fixture_label == '-':
return [('-', None, '-')]
return super(Command, self).find_fixtures(fixture_label)
================================================
FILE: src/documents/managers.py
================================================
from django.conf import settings
from django.db import models
from django.db.models.aggregates import Max
class GroupConcat(models.Aggregate):
"""
Theoretically, this should work in Sqlite, PostgreSQL, and MySQL, but I've
only ever tested it in Sqlite.
"""
ENGINE_SQLITE = 1
ENGINE_POSTGRESQL = 2
ENGINE_MYSQL = 3
ENGINES = {
"django.db.backends.sqlite3": ENGINE_SQLITE,
"django.db.backends.postgresql_psycopg2": ENGINE_POSTGRESQL,
"django.db.backends.postgresql": ENGINE_POSTGRESQL,
"django.db.backends.mysql": ENGINE_MYSQL
}
def __init__(self, expression, separator="\n", **extra):
self.engine = self._get_engine()
self.function = self._get_function()
self.template = self._get_template(separator)
models.Aggregate.__init__(
self,
expression,
output_field=models.CharField(),
**extra
)
def _get_engine(self):
engine = settings.DATABASES["default"]["ENGINE"]
try:
return self.ENGINES[engine]
except KeyError:
raise NotImplementedError(
"There's currently no support for {} when it comes to group "
"concatenation in Paperless".format(engine)
)
def _get_function(self):
if self.engine == self.ENGINE_POSTGRESQL:
return "STRING_AGG"
return "GROUP_CONCAT"
def _get_template(self, separator):
if self.engine == self.ENGINE_MYSQL:
return "%(function)s(%(expressions)s SEPARATOR '{}')".format(
separator)
return "%(function)s(%(expressions)s, '{}')".format(separator)
class LogQuerySet(models.query.QuerySet):
def by_group(self):
return self.values("group").annotate(
time=Max("modified"),
messages=GroupConcat("message"),
).order_by("-time")
class LogManager(models.Manager):
def get_queryset(self):
return LogQuerySet(self.model, using=self._db)
================================================
FILE: src/documents/migrations/0001_initial.py
================================================
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2015-12-20 19:10
from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Document',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('sender', models.CharField(blank=True, db_index=True, max_length=128)),
('title', models.CharField(blank=True, db_index=True, max_length=128)),
('content', models.TextField(db_index=("mysql" not in settings.DATABASES["default"]["ENGINE"]))),
('created', models.DateTimeField(auto_now_add=True)),
('modified', models.DateTimeField(auto_now=True)),
],
),
]
================================================
FILE: src/documents/migrations/0002_auto_20151226_1316.py
================================================
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2015-12-26 13:16
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('documents', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='document',
options={'ordering': ('sender', 'title')},
),
migrations.AlterField(
model_name='document',
name='created',
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
),
]
================================================
FILE: src/documents/migrations/0003_sender.py
================================================
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-01-11 12:21
from __future__ import unicode_literals
from django.db import migrations, models
from django.template.defaultfilters import slugify
import django.db.models.deletion
DOCUMENT_SENDER_MAP = {}
def move_sender_strings_to_sender_model(apps, schema_editor):
sender_model = apps.get_model("documents", "Sender")
document_model = apps.get_model("documents", "Document")
# Create the sender and log the relationship with the document
for document in document_model.objects.all():
if document.sender:
DOCUMENT_SENDER_MAP[document.pk], created = sender_model.objects.get_or_create(
name=document.sender,
defaults={"slug": slugify(document.sender)}
)
def realign_senders(apps, schema_editor):
document_model = apps.get_model("documents", "Document")
for pk, sender in DOCUMENT_SENDER_MAP.items():
document_model.objects.filter(pk=pk).update(sender=sender)
class Migration(migrations.Migration):
dependencies = [
('documents', '0002_auto_20151226_1316'),
]
operations = [
migrations.CreateModel(
name='Sender',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128, unique=True)),
('slug', models.SlugField()),
],
),
migrations.RunPython(move_sender_strings_to_sender_model),
migrations.RemoveField(
model_name='document',
name='sender',
),
migrations.AddField(
model_name='document',
name='sender',
field=models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, to='documents.Sender'),
),
migrations.RunPython(realign_senders),
]
================================================
FILE: src/documents/migrations/0004_auto_20160114_1844.py
================================================
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-01-14 18:44
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('documents', '0003_sender'),
]
operations = [
migrations.AlterField(
model_name='document',
name='sender',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='documents', to='documents.Sender'),
),
]
================================================
FILE: src/documents/migrations/0005_auto_20160123_0313.py
================================================
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-01-23 03:13
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('documents', '0004_auto_20160114_1844'),
]
operations = [
migrations.AlterModelOptions(
name='sender',
options={'ordering': ('name',)},
),
]
================================================
FILE: src/documents/migrations/0006_auto_20160123_0430.py
================================================
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-01-23 04:30
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('documents', '0005_auto_20160123_0313'),
]
operations = [
migrations.CreateModel(
name='Tag',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128, unique=True)),
('slug', models.SlugField(blank=True)),
('colour', models.PositiveIntegerField(choices=[(1, '#a6cee3'), (2, '#1f78b4'), (3, '#b2df8a'), (4, '#33a02c'), (5, '#fb9a99'), (6, '#e31a1c'), (7, '#fdbf6f'), (8, '#ff7f00'), (9, '#cab2d6'), (10, '#6a3d9a'), (11, '#ffff99'), (12, '#b15928'), (13, '#000000'), (14, '#cccccc')], default=1)),
],
options={
'abstract': False,
},
),
migrations.AlterField(
model_name='sender',
name='slug',
field=models.SlugField(blank=True),
),
migrations.AddField(
model_name='document',
name='tags',
field=models.ManyToManyField(related_name='documents', to='documents.Tag'),
),
]
================================================
FILE: src/documents/migrations/0007_auto_20160126_2114.py
================================================
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-01-26 21:14
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('documents', '0006_auto_20160123_0430'),
]
operations = [
migrations.AddField(
model_name='tag',
name='match',
field=models.CharField(blank=True, max_length=256),
),
migrations.AddField(
model_name='tag',
name='matching_algorithm',
field=models.PositiveIntegerField(blank=True, choices=[(1, 'Any'), (2, 'All'), (3, 'Literal'), (4, 'Regular Expression')], help_text='Which algorithm you want to use when matching text to the OCR\'d PDF. Here, "any" looks for any occurrence of any word provided in the PDF, while "all" requires that every word provided appear in the PDF, albeit not in the order provided. A "literal" match means that the text you enter must appear in the PDF exactly as you\'ve entered it, and "regular expression" uses a regex to match the PDF. If you don\'t know what a regex is, you probably don\'t want this option.', null=True),
),
migrations.AlterField(
model_name='tag',
name='colour',
field=models.PositiveIntegerField(choices=[(1, '#a6cee3'), (2, '#1f78b4'), (3, '#b2df8a'), (4, '#33a02c'), (5, '#fb9a99'), (6, '#e31a1c'), (7, '#fdbf6f'), (8, '#ff7f00'), (9, '#cab2d6'), (10, '#6a3d9a'), (11, '#b15928'), (12, '#000000'), (13, '#cccccc')], default=1),
),
]
================================================
FILE: src/documents/migrations/0008_document_file_type.py
================================================
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-01-29 22:58
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('documents', '0007_auto_20160126_2114'),
]
operations = [
migrations.AddField(
model_name='document',
name='file_type',
field=models.CharField(choices=[('pdf', 'PDF'), ('png', 'PNG'), ('jpg', 'JPG'), ('gif', 'GIF'), ('tiff', 'TIFF')], default='pdf', editable=False, max_length=4),
preserve_default=False,
),
migrations.AlterField(
model_name='document',
name='tags',
field=models.ManyToManyField(blank=True, related_name='documents', to='documents.Tag'),
),
]
================================================
FILE: src/documents/migrations/0009_auto_20160214_0040.py
================================================
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-02-14 00:40
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('documents', '0008_document_file_type'),
]
operations = [
migrations.AlterField(
model_name='tag',
name='matching_algorithm',
field=models.PositiveIntegerField(choices=[(1, 'Any'), (2, 'All'), (3, 'Literal'), (4, 'Regular Expression')], default=1, help_text='Which algorithm you want to use when matching text to the OCR\'d PDF. Here, "any" looks for any occurrence of any word provided in the PDF, while "all" requires that every word provided appear in the PDF, albeit not in the order provided. A "literal" match means that the text you enter must appear in the PDF exactly as you\'ve entered it, and "regular expression" uses a regex to match the PDF. If you don\'t know what a regex is, you probably don\'t want this option.'),
),
]
================================================
FILE: src/documents/migrations/0010_log.py
================================================
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-02-27 17:54
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('documents', '0009_auto_20160214_0040'),
]
operations = [
migrations.CreateModel(
name='Log',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('group', models.UUIDField(blank=True)),
('message', models.TextField()),
('level', models.PositiveIntegerField(choices=[(10, 'Debugging'), (20, 'Informational'), (30, 'Warning'), (40, 'Error'), (50, 'Critical')], default=20)),
('component', models.PositiveIntegerField(choices=[(1, 'Consumer'), (2, 'Mail Fetcher')])),
('created', models.DateTimeField(auto_now_add=True)),
('modified', models.DateTimeField(auto_now=True)),
],
options={
'ordering': ('-modified',),
},
),
]
================================================
FILE: src/documents/migrations/0011_auto_20160303_1929.py
================================================
# -*- coding: utf-8 -*-
# Generated by Django 1.9.2 on 2016-03-03 19:29
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
atomic = False
dependencies = [
('documents', '0010_log'),
]
operations = [
migrations.RenameModel(
old_name='Sender',
new_name='Correspondent',
),
migrations.AlterModelOptions(
name='document',
options={'ordering': ('correspondent', 'title')},
),
migrations.RenameField(
model_name='document',
old_name='sender',
new_name='correspondent',
),
]
================================================
FILE: src/documents/migrations/0012_auto_20160305_0040.py
================================================
# -*- coding: utf-8 -*-
# Generated by Django 1.9.2 on 2016-03-05 00:40
from __future__ import unicode_literals
import gnupg
import os
import re
import shutil
import subprocess
import tempfile
from django.conf import settings
from django.db import migrations
from django.utils.termcolors import colorize as colourise # Spelling hurts me
class GnuPG(object):
"""
A handy singleton to use when handling encrypted files.
"""
gpg = gnupg.GPG(gnupghome=settings.GNUPG_HOME)
@classmethod
def decrypted(cls, file_handle):
return cls.gpg.decrypt_file(
file_handle, passphrase=settings.PASSPHRASE).data
@classmethod
def encrypted(cls, file_handle):
return cls.gpg.encrypt_file(
file_handle,
recipients=None,
passphrase=settings.PASSPHRASE,
symmetric=True
).data
def move_documents_and_create_thumbnails(apps, schema_editor):
os.makedirs(os.path.join(settings.MEDIA_ROOT, "documents", "originals"), exist_ok=True)
os.makedirs(os.path.join(settings.MEDIA_ROOT, "documents", "thumbnails"), exist_ok=True)
documents = os.listdir(os.path.join(settings.MEDIA_ROOT, "documents"))
if set(documents) == {"originals", "thumbnails"}:
return
print(colourise(
"\n\n"
" This is a one-time only migration to generate thumbnails for all of your\n"
" documents so that future UIs will have something to work with. If you have\n"
" a lot of documents though, this may take a while, so a coffee break may be\n"
" in order."
"\n", opts=("bold",)
))
try:
os.makedirs(settings.SCRATCH_DIR)
except FileExistsError:
pass
for f in sorted(documents):
if not f.endswith("gpg"):
continue
print(" {} {} {}".format(
colourise("*", fg="green"),
colourise("Generating a thumbnail for", fg="white"),
colourise(f, fg="cyan")
))
thumb_temp = tempfile.mkdtemp(
prefix="paperless", dir=settings.SCRATCH_DIR)
orig_temp = tempfile.mkdtemp(
prefix="paperless", dir=settings.SCRATCH_DIR)
orig_source = os.path.join(settings.MEDIA_ROOT, "documents", f)
orig_target = os.path.join(orig_temp, f.replace(".gpg", ""))
with open(orig_source, "rb") as encrypted:
with open(orig_target, "wb") as unencrypted:
unencrypted.write(GnuPG.decrypted(encrypted))
subprocess.Popen((
settings.CONVERT_BINARY,
"-scale", "500x5000",
"-alpha", "remove",
orig_target,
os.path.join(thumb_temp, "convert-%04d.png")
)).wait()
thumb_source = os.path.join(thumb_temp, "convert-0000.png")
thumb_target = os.path.join(
settings.MEDIA_ROOT,
"documents",
"thumbnails",
re.sub(r"(\d+)\.\w+(\.gpg)", "\\1.png\\2", f)
)
with open(thumb_source, "rb") as unencrypted:
with open(thumb_target, "wb") as encrypted:
encrypted.write(GnuPG.encrypted(unencrypted))
shutil.rmtree(thumb_temp)
shutil.rmtree(orig_temp)
shutil.move(
os.path.join(settings.MEDIA_ROOT, "documents", f),
os.path.join(settings.MEDIA_ROOT, "documents", "originals", f),
)
class Migration(migrations.Migration):
dependencies = [
('documents', '0011_auto_20160303_1929'),
]
operations = [
migrations.RunPython(move_documents_and_create_thumbnails),
]
================================================
FILE: src/documents/migrations/0013_auto_20160325_2111.py
================================================
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2016-03-25 21:11
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('documents', '0012_auto_20160305_0040'),
]
operations = [
migrations.AddField(
model_name='correspondent',
name='match',
field=models.CharField(blank=True, max_length=256),
),
migrations.AddField(
model_name='correspondent',
name='matching_algorithm',
field=models.PositiveIntegerField(choices=[(1, 'Any'), (2, 'All'), (3, 'Literal'), (4, 'Regular Expression')], default=1, help_text='Which algorithm you want to use when matching text to the OCR\'d PDF. Here, "any" looks for any occurrence of any word provided in the PDF, while "all" requires that every word provided appear in the PDF, albeit not in the order provided. A "literal" match means that the text you enter must appear in the PDF exactly as you\'ve entered it, and "regular expression" uses a regex to match the PDF. If you don\'t know what a regex is, you probably don\'t want this option.'),
),
migrations.AlterField(
model_name='document',
name='created',
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.RemoveField(
model_name='log',
name='component',
),
]
================================================
FILE: src/documents/migrations/0014_document_checksum.py
================================================
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2016-03-28 19:09
from __future__ import unicode_literals
import gnupg
import hashlib
import os
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
from django.template.defaultfilters import slugify
from django.utils.termcolors import colorize as colourise # Spelling hurts me
class GnuPG(object):
"""
A handy singleton to use when handling encrypted files.
"""
gpg = gnupg.GPG(gnupghome=settings.GNUPG_HOME)
@classmethod
def decrypted(cls, file_handle):
return cls.gpg.decrypt_file(
file_handle, passphrase=settings.PASSPHRASE).data
@classmethod
def encrypted(cls, file_handle):
return cls.gpg.encrypt_file(
file_handle,
recipients=None,
passphrase=settings.PASSPHRASE,
symmetric=True
).data
class Document(object):
"""
Django's migrations restrict access to model methods, so this is a snapshot
of the methods that existed at the time this migration was written, since
we need to make use of a lot of these shortcuts here.
"""
def __init__(self, doc):
self.pk = doc.pk
self.correspondent = doc.correspondent
self.title = doc.title
self.file_type = doc.file_type
self.tags = doc.tags
self.created = doc.created
def __str__(self):
created = self.created.strftime("%Y%m%d%H%M%S")
if self.correspondent and self.title:
return "{}: {} - {}".format(
created, self.correspondent, self.title)
if self.correspondent or self.title:
return "{}: {}".format(created, self.correspondent or self.title)
return str(created)
@property
def source_path(self):
return os.path.join(
settings.MEDIA_ROOT,
"documents",
"originals",
"{:07}.{}.gpg".format(self.pk, self.file_type)
)
@property
def source_file(self):
return open(self.source_path, "rb")
@property
def file_name(self):
return slugify(str(self)) + "." + self.file_type
def set_checksums(apps, schema_editor):
document_model = apps.get_model("documents", "Document")
if not document_model.objects.all().exists():
return
print(colourise(
"\n\n"
" This is a one-time only migration to generate checksums for all\n"
" of your existing documents. If you have a lot of documents\n"
" though, this may take a while, so a coffee break may be in\n"
" order."
"\n", opts=("bold",)
))
sums = {}
for d in document_model.objects.all():
document = Document(d)
print(" {} {} {}".format(
colourise("*", fg="green"),
colourise("Generating a checksum for", fg="white"),
colourise(document.file_name, fg="cyan")
))
with document.source_file as encrypted:
checksum = hashlib.md5(GnuPG.decrypted(encrypted)).hexdigest()
if checksum in sums:
error = "\n{line}{p1}\n\n{doc1}\n{doc2}\n\n{p2}\n\n{code}\n\n{p3}{line}".format(
p1=colourise("It appears that you have two identical documents in your collection and \nPaperless no longer supports this (see issue #97). The documents in question\nare:", fg="yellow"),
p2=colourise("To fix this problem, you'll have to remove one of them from the database, a task\nmost easily done by running the following command in the same\ndirectory as manage.py:", fg="yellow"),
p3=colourise("When that's finished, re-run the migrate, and provided that there aren't any\nother duplicates, you should be good to go.", fg="yellow"),
doc1=colourise(" * {} (id: {})".format(sums[checksum][1], sums[checksum][0]), fg="red"),
doc2=colourise(" * {} (id: {})".format(document.file_name, document.pk), fg="red"),
code=colourise(" $ echo 'DELETE FROM documents_document WHERE id = {pk};' | ./manage.py dbshell".format(pk=document.pk), fg="green"),
line=colourise("\n{}\n".format("=" * 80), fg="white", opts=("bold",))
)
raise RuntimeError(error)
sums[checksum] = (document.pk, document.file_name)
document_model.objects.filter(pk=document.pk).update(checksum=checksum)
def do_nothing(apps, schema_editor):
pass
class Migration(migrations.Migration):
dependencies = [
('documents', '0013_auto_20160325_2111'),
]
operations = [
migrations.AddField(
model_name='document',
name='checksum',
field=models.CharField(
default='-',
db_index=True,
editable=False,
max_length=32,
help_text='The checksum of the original document (before it '
'was encrypted). We use this to prevent duplicate '
'document imports.',
),
preserve_default=False,
),
migrations.RunPython(set_checksums, do_nothing),
migrations.AlterField(
model_name='document',
name='created',
field=models.DateTimeField(db_index=True, default=django.utils.timezone.now),
),
migrations.AlterField(
model_name='document',
name='modified',
field=models.DateTimeField(auto_now=True, db_index=True),
),
]
================================================
FILE: src/documents/migrations/0015_add_insensitive_to_match.py
================================================
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2016-10-05 21:38
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('documents', '0014_document_checksum'),
]
operations = [
migrations.AlterField(
model_name='document',
name='checksum',
field=models.CharField(editable=False, help_text='The checksum of the original document (before it was encrypted). We use this to prevent duplicate document imports.', max_length=32, unique=True),
),
migrations.AddField(
model_name='correspondent',
name='is_insensitive',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='tag',
name='is_insensitive',
field=models.BooleanField(default=True),
),
]
================================================
FILE: src/documents/migrations/0016_auto_20170325_1558.py
================================================
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-03-25 15:58
from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('documents', '0015_add_insensitive_to_match'),
]
operations = [
migrations.AlterField(
model_name='document',
name='content',
field=models.TextField(blank=True, db_index=("mysql" not in settings.DATABASES["default"]["ENGINE"]), help_text='The raw, text-only data of the document. This field is primarily used for searching.'),
),
]
================================================
FILE: src/documents/migrations/0017_auto_20170512_0507.py
================================================
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-05-12 05:07
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('documents', '0016_auto_20170325_1558'),
]
operations = [
migrations.AlterField(
model_name='correspondent',
name='matching_algorithm',
field=models.PositiveIntegerField(choices=[(1, 'Any'), (2, 'All'), (3, 'Literal'), (4, 'Regular Expression'), (5, 'Fuzzy Match')], default=1, help_text='Which algorithm you want to use when matching text to the OCR\'d PDF. Here, "any" looks for any occurrence of any word provided in the PDF, while "all" requires that every word provided appear in the PDF, albeit not in the order provided. A "literal" match means that the text you enter must appear in the PDF exactly as you\'ve entered it, and "regular expression" uses a regex to match the PDF. (If you don\'t know what a regex is, you probably don\'t want this option.) Finally, a "fuzzy match" looks for words or phrases that are mostly—but not exactly—the same, which can be useful for matching against documents containg imperfections that foil accurate OCR.'),
),
migrations.AlterField(
model_name='tag',
name='matching_algorithm',
field=models.PositiveIntegerField(choices=[(1, 'Any'), (2, 'All'), (3, 'Literal'), (4, 'Regular Expression'), (5, 'Fuzzy Match')], default=1, help_text='Which algorithm you want to use when matching text to the OCR\'d PDF. Here, "any" looks for any occurrence of any word provided in the PDF, while "all" requires that every word provided appear in the PDF, albeit not in the order provided. A "literal" match means that the text you enter must appear in the PDF exactly as you\'ve entered it, and "regular expression" uses a regex to match the PDF. (If you don\'t know what a regex is, you probably don\'t want this option.) Finally, a "fuzzy match" looks for words or phrases that are mostly—but not exactly—the same, which can be useful for matching against documents containg imperfections that foil accurate OCR.'),
),
]
================================================
FILE: src/documents/migrations/0018_auto_20170715_1712.py
================================================
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-07-15 17:12
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('documents', '0017_auto_20170512_0507'),
]
operations = [
migrations.AlterField(
model_name='document',
name='correspondent',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='documents', to='documents.Correspondent'),
),
]
================================================
FILE: src/documents/migrations/0019_add_consumer_user.py
================================================
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-07-15 17:12
from __future__ import unicode_literals
from django.contrib.auth.models import User
from django.db import migrations
def forwards_func(apps, schema_editor):
User.objects.create(username="consumer")
def reverse_func(apps, schema_editor):
User.objects.get(username="consumer").delete()
class Migration(migrations.Migration):
dependencies = [
('documents', '0018_auto_20170715_1712'),
]
operations = [
migrations.RunPython(forwards_func, reverse_func),
]
================================================
FILE: src/documents/migrations/0020_document_added.py
================================================
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
def set_added_time_to_created_time(apps, schema_editor):
Document = apps.get_model("documents", "Document")
for doc in Document.objects.all():
doc.added = doc.created
doc.save()
class Migration(migrations.Migration):
dependencies = [
('documents', '0019_add_consumer_user'),
]
operations = [
migrations.AddField(
model_name='document',
name='added',
field=models.DateTimeField(db_index=True, default=django.utils.timezone.now, editable=False),
),
migrations.RunPython(set_added_time_to_created_time)
]
================================================
FILE: src/documents/migrations/0021_document_storage_type.py
================================================
# -*- coding: utf-8 -*-
# Generated by Django 1.11.10 on 2018-02-04 13:07
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('documents', '0020_document_added'),
]
operations = [
# Add the field with the default GPG-encrypted value
migrations.AddField(
model_name='document',
name='storage_type',
field=models.CharField(choices=[('unencrypted', 'Unencrypted'), ('gpg', 'Encrypted with GNU Privacy Guard')], default='gpg', editable=False, max_length=11),
),
# Now that the field is added, change the default to unencrypted
migrations.AlterField(
model_name='document',
name='storage_type',
field=models.CharField(choices=[('unencrypted', 'Unencrypted'), ('gpg', 'Encrypted with GNU Privacy Guard')], default='unencrypted', editable=False, max_length=11),
),
]
================================================
FILE: src/documents/migrations/0022_auto_20181007_1420.py
================================================
# Generated by Django 2.0.8 on 2018-10-07 14:20
from django.db import migrations, models
from django.utils.text import slugify
def re_slug_all_the_things(apps, schema_editor):
"""
Rewrite all slug values to make sure they're actually slugs before we brand
them as uneditable.
"""
Tag = apps.get_model("documents", "Tag")
Correspondent = apps.get_model("documents", "Correspondent")
for klass in (Tag, Correspondent):
for instance in klass.objects.all():
klass.objects.filter(
pk=instance.pk
).update(
slug=slugify(instance.slug)
)
class Migration(migrations.Migration):
dependencies = [
('documents', '0021_document_storage_type'),
]
operations = [
migrations.AlterModelOptions(
name='tag',
options={'ordering': ('name',)},
),
migrations.AlterField(
model_name='correspondent',
name='slug',
field=models.SlugField(blank=True, editable=False),
),
migrations.AlterField(
model_name='document',
name='file_type',
field=models.CharField(choices=[('pdf', 'PDF'), ('png', 'PNG'), ('jpg', 'JPG'), ('gif', 'GIF'), ('tiff', 'TIFF'), ('txt', 'TXT'), ('csv', 'CSV'), ('md', 'MD')], editable=False, max_length=4),
),
migrations.AlterField(
model_name='tag',
name='slug',
field=models.SlugField(blank=True, editable=False),
),
migrations.RunPython(re_slug_all_the_things, migrations.RunPython.noop)
]
================================================
FILE: src/documents/migrations/0023_document_current_filename.py
================================================
# Generated by Django 2.0.10 on 2019-04-26 18:57
from django.db import migrations, models
def set_filename(apps, schema_editor):
Document = apps.get_model("documents", "Document")
for doc in Document.objects.all():
file_name = "{:07}.{}".format(doc.pk, doc.file_type)
if doc.storage_type == "gpg":
file_name += ".gpg"
# Set filename
doc.filename = file_name
# Save document
doc.save()
class Migration(migrations.Migration):
dependencies = [
('documents', '0022_auto_20181007_1420'),
]
operations = [
migrations.AddField(
model_name='document',
name='filename',
field=models.FilePathField(default=None,
null=True,
editable=False,
help_text='Current filename in storage',
max_length=256),
),
migrations.RunPython(set_filename)
]
================================================
FILE: src/documents/migrations/__init__.py
================================================
================================================
FILE: src/documents/mixins.py
================================================
class Renderable:
"""
A handy mixin to make it easier/cleaner to print output based on a
verbosity value.
"""
def _render(self, text, verbosity):
if self.verbosity >= verbosity:
print(text)
================================================
FILE: src/documents/models.py
================================================
# coding=utf-8
import logging
import os
import re
import uuid
from collections import OrderedDict
import dateutil.parser
from django.dispatch import receiver
from django.conf import settings
from django.db import models
from django.template.defaultfilters import slugify
from django.utils import timezone
from django.utils.text import slugify
from fuzzywuzzy import fuzz
from collections import defaultdict
from .managers import LogManager
try:
from django.core.urlresolvers import reverse
except ImportError:
from django.urls import reverse
class MatchingModel(models.Model):
MATCH_ANY = 1
MATCH_ALL = 2
MATCH_LITERAL = 3
MATCH_REGEX = 4
MATCH_FUZZY = 5
MATCHING_ALGORITHMS = (
(MATCH_ANY, "Any"),
(MATCH_ALL, "All"),
(MATCH_LITERAL, "Literal"),
(MATCH_REGEX, "Regular Expression"),
(MATCH_FUZZY, "Fuzzy Match"),
)
name = models.CharField(max_length=128, unique=True)
slug = models.SlugField(blank=True, editable=False)
match = models.CharField(max_length=256, blank=True)
matching_algorithm = models.PositiveIntegerField(
choices=MATCHING_ALGORITHMS,
default=MATCH_ANY,
help_text=(
"Which algorithm you want to use when matching text to the OCR'd "
"PDF. Here, \"any\" looks for any occurrence of any word "
"provided in the PDF, while \"all\" requires that every word "
"provided appear in the PDF, albeit not in the order provided. A "
"\"literal\" match means that the text you enter must appear in "
"the PDF exactly as you've entered it, and \"regular expression\" "
"uses a regex to match the PDF. (If you don't know what a regex "
"is, you probably don't want this option.) Finally, a \"fuzzy "
"match\" looks for words or phrases that are mostly—but not "
"exactly—the same, which can be useful for matching against "
"documents containg imperfections that foil accurate OCR."
)
)
is_insensitive = models.BooleanField(default=True)
class Meta:
abstract = True
ordering = ("name",)
def __str__(self):
return self.name
@property
def conditions(self):
return "{}: \"{}\" ({})".format(
self.name, self.match, self.get_matching_algorithm_display())
@classmethod
def match_all(cls, text, tags=None):
if tags is None:
tags = cls.objects.all()
text = text.lower()
for tag in tags:
if tag.matches(text):
yield tag
def matches(self, text):
search_kwargs = {}
# Check that match is not empty
if self.match.strip() == "":
return False
if self.is_insensitive:
search_kwargs = {"flags": re.IGNORECASE}
if self.matching_algorithm == self.MATCH_ALL:
for word in self._split_match():
search_result = re.search(
r"\b{}\b".format(word), text, **search_kwargs)
if not search_result:
return False
return True
if self.matching_algorithm == self.MATCH_ANY:
for word in self._split_match():
if re.search(r"\b{}\b".format(word), text, **search_kwargs):
return True
return False
if self.matching_algorithm == self.MATCH_LITERAL:
return bool(re.search(
r"\b{}\b".format(self.match), text, **search_kwargs))
if self.matching_algorithm == self.MATCH_REGEX:
return bool(re.search(
re.compile(self.match, **search_kwargs), text))
if self.matching_algorithm == self.MATCH_FUZZY:
match = re.sub(r'[^\w\s]', '', self.match)
text = re.sub(r'[^\w\s]', '', text)
if self.is_insensitive:
match = match.lower()
text = text.lower()
return True if fuzz.partial_ratio(match, text) >= 90 else False
raise NotImplementedError("Unsupported matching algorithm")
def _split_match(self):
"""
Splits the match to individual keywords, getting rid of unnecessary
spaces and grouping quoted words together.
Example:
' some random words "with quotes " and spaces'
==>
["some", "random", "words", "with+quotes", "and", "spaces"]
"""
findterms = re.compile(r'"([^"]+)"|(\S+)').findall
normspace = re.compile(r"\s+").sub
return [
normspace(" ", (t[0] or t[1]).strip()).replace(" ", r"\s+")
for t in findterms(self.match)
]
def save(self, *args, **kwargs):
self.match = self.match.lower()
self.slug = slugify(self.name)
models.Model.save(self, *args, **kwargs)
class Correspondent(MatchingModel):
# This regex is probably more restrictive than it needs to be, but it's
# better safe than sorry.
SAFE_REGEX = re.compile(r"^[\w\- ,.']+$")
class Meta:
ordering = ("name",)
class Tag(MatchingModel):
COLOURS = (
(1, "#a6cee3"),
(2, "#1f78b4"),
(3, "#b2df8a"),
(4, "#33a02c"),
(5, "#fb9a99"),
(6, "#e31a1c"),
(7, "#fdbf6f"),
(8, "#ff7f00"),
(9, "#cab2d6"),
(10, "#6a3d9a"),
(11, "#b15928"),
(12, "#000000"),
(13, "#cccccc")
)
colour = models.PositiveIntegerField(choices=COLOURS, default=1)
class Document(models.Model):
TYPE_PDF = "pdf"
TYPE_PNG = "png"
TYPE_JPG = "jpg"
TYPE_GIF = "gif"
TYPE_TIF = "tiff"
TYPE_TXT = "txt"
TYPE_CSV = "csv"
TYPE_MD = "md"
TYPES = (TYPE_PDF, TYPE_PNG, TYPE_JPG, TYPE_GIF, TYPE_TIF,
TYPE_TXT, TYPE_CSV, TYPE_MD)
STORAGE_TYPE_UNENCRYPTED = "unencrypted"
STORAGE_TYPE_GPG = "gpg"
STORAGE_TYPES = (
(STORAGE_TYPE_UNENCRYPTED, "Unencrypted"),
(STORAGE_TYPE_GPG, "Encrypted with GNU Privacy Guard")
)
correspondent = models.ForeignKey(
Correspondent,
blank=True,
null=True,
related_name="documents",
on_delete=models.SET_NULL
)
title = models.CharField(max_length=128, blank=True, db_index=True)
content = models.TextField(
db_index=True,
blank=True,
help_text="The raw, text-only data of the document. This field is "
"primarily used for searching."
)
file_type = models.CharField(
max_length=4,
editable=False,
choices=tuple([(t, t.upper()) for t in TYPES])
)
tags = models.ManyToManyField(
Tag, related_name="documents", blank=True)
checksum = models.CharField(
max_length=32,
editable=False,
unique=True,
help_text="The checksum of the original document (before it was "
"encrypted). We use this to prevent duplicate document "
"imports."
)
created = models.DateTimeField(
default=timezone.now, db_index=True)
modified = models.DateTimeField(
auto_now=True, editable=False, db_index=True)
storage_type = models.CharField(
max_length=11,
choices=STORAGE_TYPES,
default=STORAGE_TYPE_UNENCRYPTED,
editable=False
)
added = models.DateTimeField(
default=timezone.now, editable=False, db_index=True)
filename = models.FilePathField(
max_length=256,
editable=False,
default=None,
null=True,
help_text="Current filename in storage"
)
class Meta:
ordering = ("correspondent", "title")
def __str__(self):
created = self.created.strftime("%Y%m%d%H%M%S")
if self.correspondent and self.title:
return "{}: {} - {}".format(
created, self.correspondent, self.title)
if self.correspondent or self.title:
return "{}: {}".format(created, self.correspondent or self.title)
return str(created)
def find_renamed_document(self, subdirectory=""):
suffix = "%07i.%s" % (self.pk, self.file_type)
# Append .gpg for encrypted files
if self.storage_type == self.STORAGE_TYPE_GPG:
suffix += ".gpg"
# Start with the (optinally) supplied subdirectory, go up in the
# directory hierarchy and try to find the file in question
root = os.path.normpath(Document.filename_to_path(subdirectory))
# Check if root really exists and return otherwise
if not os.path.isdir(root):
return None
for filename in os.listdir(root):
if filename.endswith(suffix):
return os.path.join(subdirectory, filename)
fullname = os.path.join(subdirectory, filename)
if os.path.isdir(Document.filename_to_path(fullname)):
return self.find_renamed_document(fullname)
return None
@property
def source_filename(self):
# Initial filename generation (for new documents)
if self.filename is None:
self.filename = self.generate_source_filename()
# Check if document is still available under filename
elif not os.path.isfile(Document.filename_to_path(self.filename)):
recovered_filename = self.find_renamed_document()
# If we have found the file so update the filename
if recovered_filename is not None:
logger = logging.getLogger(__name__)
logger.warning("Filename of document " + str(self.id) +
" has changed and was successfully updated")
self.filename = recovered_filename
# Remove all empty subdirectories from MEDIA_ROOT
Document.delete_all_empty_subdirectories(
Document.filename_to_path(""))
else:
logger = logging.getLogger(__name__)
logger.error("File of document " + str(self.id) + " has " +
"gone and could not be recovered")
return self.filename
@staticmethod
def many_to_dictionary(field):
# Converts ManyToManyField to dictionary by assuming, that field
# entries contain an _ or - which will be used as a delimiter
mydictionary = dict()
for index, t in enumerate(field.all()):
# Populate tag names by index
mydictionary[index] = slugify(t.name)
# Find delimiter
delimiter = t.name.find('_')
if delimiter == -1:
delimiter = t.name.find('-')
if delimiter == -1:
continue
key = t.name[:delimiter]
value = t.name[delimiter+1:]
mydictionary[slugify(key)] = slugify(value)
return mydictionary
def generate_source_filename(self):
# Create filename based on configured format
if settings.PAPERLESS_FILENAME_FORMAT is not None:
tags = defaultdict(lambda: slugify(None),
self.many_to_dictionary(self.tags))
path = settings.PAPERLESS_FILENAME_FORMAT.format(
correspondent=slugify(self.correspondent),
title=slugify(self.title),
created=slugify(self.created),
added=slugify(self.added),
tags=tags)
else:
path = ""
# Always append the primary key to guarantee uniqueness of filename
if len(path) > 0:
filename = "%s-%07i.%s" % (path, self.pk, self.file_type)
else:
filename = "%07i.%s" % (self.pk, self.file_type)
# Append .gpg for encrypted files
if self.storage_type == self.STORAGE_TYPE_GPG:
filename += ".gpg"
return filename
def create_source_directory(self):
new_filename = self.generate_source_filename()
# Determine the full "target" path
dir_new = Document.filename_to_path(os.path.dirname(new_filename))
# Create new path
os.makedirs(dir_new, exist_ok=True)
@property
def source_path(self):
return Document.filename_to_path(self.source_filename)
@staticmethod
def filename_to_path(filename):
return os.path.join(
settings.MEDIA_ROOT,
"documents",
"originals",
filename
)
@property
def source_file(self):
return open(self.source_path, "rb")
@property
def file_name(self):
return slugify(str(self)) + "." + self.file_type
@property
def download_url(self):
return reverse("fetch", kwargs={"kind": "doc", "pk": self.pk})
@property
def thumbnail_path(self):
file_name = "{:07}.png".format(self.pk)
if self.storage_type == self.STORAGE_TYPE_GPG:
file_name += ".gpg"
return os.path.join(
settings.MEDIA_ROOT,
"documents",
"thumbnails",
file_name
)
@property
def thumbnail_file(self):
return open(self.thumbnail_path, "rb")
@property
def thumbnail_url(self):
return reverse("fetch", kwargs={"kind": "thumb", "pk": self.pk})
def set_filename(self, filename):
if os.path.isfile(Document.filename_to_path(filename)):
self.filename = filename
@staticmethod
def try_delete_empty_directories(directory):
# Go up in the directory hierarchy and try to delete all directories
directory = os.path.normpath(directory)
root = os.path.normpath(Document.filename_to_path(""))
while directory != root:
# Try to delete the current directory
try:
os.rmdir(directory)
except os.error:
# Directory not empty, no need to go further up
return
# Cut off actual directory and go one level up
directory, _ = os.path.split(directory)
directory = os.path.normpath(directory)
@staticmethod
def delete_all_empty_subdirectories(directory):
# Go through all folders and try to delete all directories
root = os.path.normpath(Document.filename_to_path(directory))
for filename in os.listdir(root):
fullname = os.path.join(directory, filename)
if not os.path.isdir(Document.filename_to_path(fullname)):
continue
# Go into subdirectory to see, if there is more to delete
Document.delete_all_empty_subdirectories(
os.path.join(directory, filename))
# Try to delete the directory
try:
os.rmdir(Document.filename_to_path(fullname))
continue
except os.error:
# Directory not empty, no need to go further up
continue
@receiver(models.signals.m2m_changed, sender=Document.tags.through)
@receiver(models.signals.post_save, sender=Document)
def update_filename(sender, instance, **kwargs):
# Skip if document has not been saved yet
if instance.filename is None:
return
# Check is file exists and update filename otherwise
if not os.path.isfile(Document.filename_to_path(instance.filename)):
instance.filename = instance.source_filename
# Build the new filename
new_filename = instance.generate_source_filename()
# If the filename is the same, then nothing needs to be done
if instance.filename == new_filename:
return
# Determine the full "target" path
path_new = instance.filename_to_path(new_filename)
dir_new = instance.filename_to_path(os.path.dirname(new_filename))
# Create new path
instance.create_source_directory()
# Determine the full "current" path
path_current = instance.filename_to_path(instance.source_filename)
# Move file
try:
os.rename(path_current, path_new)
except PermissionError:
# Do not update filename in object
return
except FileNotFoundError:
logger = logging.getLogger(__name__)
logger.error("Renaming of document " + str(instance.id) + " failed " +
"as file " + instance.filename + " was no longer present")
return
# Delete empty directory
old_dir = os.path.dirname(instance.filename)
old_path = instance.filename_to_path(old_dir)
Document.try_delete_empty_directories(old_path)
instance.filename = new_filename
# Save instance
# This will not cause a cascade of post_save signals, as next time
# nothing needs to be renamed
instance.save()
@receiver(models.signals.post_delete, sender=Document)
def delete_files(sender, instance, **kwargs):
if instance.filename is None:
return
# Remove the document
old_file = instance.filename_to_path(instance.filename)
try:
os.remove(old_file)
except FileNotFoundError:
logger = logging.getLogger(__name__)
logger.warning("Deleted document " + str(instance.id) + " but file " +
old_file + " was no longer present")
# And remove the directory (if applicable)
old_dir = os.path.dirname(instance.filename)
old_path = instance.filename_to_path(old_dir)
Document.try_delete_empty_directories(old_path)
class Log(models.Model):
LEVELS = (
(logging.DEBUG, "Debugging"),
(logging.INFO, "Informational"),
(logging.WARNING, "Warning"),
(logging.ERROR, "Error"),
(logging.CRITICAL, "Critical"),
)
group = models.UUIDField(blank=True)
message = models.TextField()
level = models.PositiveIntegerField(choices=LEVELS, default=logging.INFO)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
objects = LogManager()
class Meta:
ordering = ("-modified",)
def __str__(self):
return self.message
def save(self, *args, **kwargs):
"""
To allow for the case where we don't want to group the message, we
shouldn't force the caller to specify a one-time group value. However,
allowing group=None means that the manager can't differentiate the
different un-grouped messages, so instead we set a random one here.
"""
if not self.group:
self.group = uuid.uuid4()
models.Model.save(self, *args, **kwargs)
class FileInfo:
# This epic regex *almost* worked for our needs, so I'm keeping it here for
# posterity, in the hopes that we might find a way to make it work one day.
ALMOST_REGEX = re.compile(
r"^((?P\d\d\d\d\d\d\d\d\d\d\d\d\d\dZ){separator})?"
r"((?P{non_separated_word}+){separator})??"
r"(?P{non_separated_word}+)"
r"({separator}(?P[a-z,0-9-]+))?"
r"\.(?P[a-zA-Z.-]+)$".format(
separator=r"\s+-\s+",
non_separated_word=r"([\w,. ]|([^\s]-))"
)
)
formats = "pdf|jpe?g|png|gif|tiff?|te?xt|md|csv"
REGEXES = OrderedDict([
("created-correspondent-title-tags", re.compile(
r"^(?P\d\d\d\d\d\d\d\d(\d\d\d\d\d\d)?Z) - "
r"(?P.*) - "
r"(?P.*) - "
r"(?P[a-z0-9\-,]*)"
r"\.(?P{})$".format(formats),
flags=re.IGNORECASE
)),
("created-title-tags", re.compile(
r"^(?P\d\d\d\d\d\d\d\d(\d\d\d\d\d\d)?Z) - "
r"(?P.*) - "
r"(?P[a-z0-9\-,]*)"
r"\.(?P{})$".format(formats),
flags=re.IGNORECASE
)),
("created-correspondent-title", re.compile(
r"^(?P\d\d\d\d\d\d\d\d(\d\d\d\d\d\d)?Z) - "
r"(?P.*) - "
r"(?P.*)"
r"\.(?P{})$".format(formats),
flags=re.IGNORECASE
)),
("created-title", re.compile(
r"^(?P\d\d\d\d\d\d\d\d(\d\d\d\d\d\d)?Z) - "
r"(?P.*)"
r"\.(?P{})$".format(formats),
flags=re.IGNORECASE
)),
("correspondent-title-tags", re.compile(
r"(?P.*) - "
r"(?P.*) - "
r"(?P[a-z0-9\-,]*)"
r"\.(?P{})$".format(formats),
flags=re.IGNORECASE
)),
("correspondent-title", re.compile(
r"(?P.*) - "
r"(?P.*)?"
r"\.(?P{})$".format(formats),
flags=re.IGNORECASE
)),
("title", re.compile(
r"(?P.*)"
r"\.(?P{})$".format(formats),
flags=re.IGNORECASE
))
])
def __init__(self, created=None, correspondent=None, title=None, tags=(),
extension=None):
self.created = created
self.title = title
self.extension = extension
self.correspondent = correspondent
self.tags = tags
@classmethod
def _get_created(cls, created):
try:
return dateutil.parser.parse("{:0<14}Z".format(created[:-1]))
except ValueError:
return None
@classmethod
def _get_correspondent(cls, name):
if not name:
return None
return Correspondent.objects.get_or_create(name=name, defaults={
"slug": slugify(name)
})[0]
@classmethod
def _get_title(cls, title):
return title
@classmethod
def _get_tags(cls, tags):
r = []
for t in tags.split(","):
r.append(Tag.objects.get_or_create(
slug=slugify(t),
defaults={"name": t}
)[0])
return tuple(r)
@classmethod
def _get_extension(cls, extension):
r = extension.lower()
if r == "jpeg":
return "jpg"
if r == "tif":
return "tiff"
return r
@classmethod
def _mangle_property(cls, properties, name):
if name in properties:
properties[name] = getattr(cls, "_get_{}".format(name))(
properties[name]
)
@classmethod
def from_path(cls, path):
"""
We use a crude naming convention to make handling the correspondent,
title, and tags easier:
" - - - ."
" - - ."
" - ."
"."
"""
filename = os.path.basename(path)
# Mutate filename in-place before parsing its components
# by applying at most one of the configured transformations.
for (pattern, repl) in settings.FILENAME_PARSE_TRANSFORMS:
(filename, count) = pattern.subn(repl, filename)
if count:
break
# Parse filename components.
for regex in cls.REGEXES.values():
m = regex.match(filename)
if m:
properties = m.groupdict()
cls._mangle_property(properties, "created")
cls._mangle_property(properties, "correspondent")
cls._mangle_property(properties, "title")
cls._mangle_property(properties, "tags")
cls._mangle_property(properties, "extension")
return cls(**properties)
================================================
FILE: src/documents/parsers.py
================================================
import logging
import os
import re
import shutil
import subprocess
import tempfile
import dateparser
from django.conf import settings
from django.utils import timezone
# This regular expression will try to find dates in the document at
# hand and will match the following formats:
# - XX.YY.ZZZZ with XX + YY being 1 or 2 and ZZZZ being 2 or 4 digits
# - XX/YY/ZZZZ with XX + YY being 1 or 2 and ZZZZ being 2 or 4 digits
# - XX-YY-ZZZZ with XX + YY being 1 or 2 and ZZZZ being 2 or 4 digits
# - ZZZZ.XX.YY with XX + YY being 1 or 2 and ZZZZ being 2 or 4 digits
# - ZZZZ/XX/YY with XX + YY being 1 or 2 and ZZZZ being 2 or 4 digits
# - ZZZZ-XX-YY with XX + YY being 1 or 2 and ZZZZ being 2 or 4 digits
# - XX. MONTH ZZZZ with XX being 1 or 2 and ZZZZ being 2 or 4 digits
# - MONTH ZZZZ, with ZZZZ being 4 digits
# - MONTH XX, ZZZZ with XX being 1 or 2 and ZZZZ being 4 digits
DATE_REGEX = re.compile(
r'(\b|(?!=([_-])))([0-9]{1,2})[\.\/-]([0-9]{1,2})[\.\/-]([0-9]{4}|[0-9]{2})(\b|(?=([_-])))|' + # NOQA: E501
r'(\b|(?!=([_-])))([0-9]{4}|[0-9]{2})[\.\/-]([0-9]{1,2})[\.\/-]([0-9]{1,2})(\b|(?=([_-])))|' + # NOQA: E501
r'(\b|(?!=([_-])))([0-9]{1,2}[\. ]+[^ ]{3,9} ([0-9]{4}|[0-9]{2}))(\b|(?=([_-])))|' + # NOQA: E501
r'(\b|(?!=([_-])))([^\W\d_]{3,9} [0-9]{1,2}, ([0-9]{4}))(\b|(?=([_-])))|' +
r'(\b|(?!=([_-])))([^\W\d_]{3,9} [0-9]{4})(\b|(?=([_-])))'
)
class ParseError(Exception):
pass
class DocumentParser:
"""
Subclass this to make your own parser. Have a look at
`paperless_tesseract.parsers` for inspiration.
"""
def __init__(self, path):
self.document_path = path
self.tempdir = tempfile.mkdtemp(prefix="paperless-",
dir=settings.SCRATCH_DIR)
self.logger = logging.getLogger(__name__)
self.logging_group = None
def get_thumbnail(self):
"""
Returns the path to a file we can use as a thumbnail for this document.
"""
raise NotImplementedError()
def optimise_thumbnail(self, in_path):
out_path = os.path.join(self.tempdir, "optipng.png")
args = (settings.OPTIPNG_BINARY, "-o5", in_path, "-out", out_path)
if not subprocess.Popen(args).wait() == 0:
raise ParseError("Optipng failed at {}".format(args))
return out_path
def get_optimised_thumbnail(self):
return self.optimise_thumbnail(self.get_thumbnail())
def get_text(self):
"""
Returns the text from the document and only the text.
"""
raise NotImplementedError()
def get_date(self):
"""
Returns the date of the document.
"""
def __parser(ds, date_order):
"""
Call dateparser.parse with a particular date ordering
"""
return dateparser.parse(
ds,
settings={
"DATE_ORDER": date_order,
"PREFER_DAY_OF_MONTH": "first",
"RETURN_AS_TIMEZONE_AWARE":
True
}
)
date = None
date_string = None
next_year = timezone.now().year + 5 # Arbitrary 5 year future limit
title = os.path.basename(self.document_path)
# if filename date parsing is enabled, search there first:
if settings.FILENAME_DATE_ORDER:
self.log("info", "Checking document title for date")
for m in re.finditer(DATE_REGEX, title):
date_string = m.group(0)
try:
date = __parser(date_string, settings.FILENAME_DATE_ORDER)
except (TypeError, ValueError):
# Skip all matches that do not parse to a proper date
continue
if date is not None and next_year > date.year > 1900:
self.log(
"info",
"Detected document date {} based on string {} "
"from document title"
"".format(date.isoformat(), date_string)
)
return date
try:
# getting text after checking filename will save time if only
# looking at the filename instead of the whole text
text = self.get_text()
except ParseError:
return None
# Iterate through all regex matches in text and try to parse the date
for m in re.finditer(DATE_REGEX, text):
date_string = m.group(0)
try:
date = __parser(date_string, settings.DATE_ORDER)
except (TypeError, ValueError):
# Skip all matches that do not parse to a proper date
continue
if date is not None and next_year > date.year > 1900:
break
else:
date = None
if date is not None:
self.log(
"info",
"Detected document date {} based on string {}".format(
date.isoformat(),
date_string
)
)
else:
self.log("info", "Unable to detect date for document")
return date
def log(self, level, message):
getattr(self.logger, level)(message, extra={
"group": self.logging_group
})
def cleanup(self):
self.log("debug", "Deleting directory {}".format(self.tempdir))
shutil.rmtree(self.tempdir)
================================================
FILE: src/documents/serialisers.py
================================================
from rest_framework import serializers
from .models import Correspondent, Tag, Document, Log
class CorrespondentSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Correspondent
fields = (
"id",
"slug",
"name",
"match",
"matching_algorithm",
"is_insensitive"
)
class TagSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Tag
fields = (
"id",
"slug",
"name",
"colour",
"match",
"matching_algorithm",
"is_insensitive"
)
class CorrespondentField(serializers.HyperlinkedRelatedField):
def get_queryset(self):
return Correspondent.objects.all()
class TagsField(serializers.HyperlinkedRelatedField):
def get_queryset(self):
return Tag.objects.all()
class DocumentSerializer(serializers.ModelSerializer):
correspondent = CorrespondentField(
view_name="drf:correspondent-detail", allow_null=True)
tags = TagsField(view_name="drf:tag-detail", many=True)
class Meta:
model = Document
fields = (
"id",
"correspondent",
"title",
"content",
"file_type",
"tags",
"checksum",
"created",
"modified",
"added",
"file_name",
"download_url",
"thumbnail_url",
)
class LogSerializer(serializers.ModelSerializer):
time = serializers.DateTimeField()
messages = serializers.CharField()
class Meta:
model = Log
fields = (
"time",
"messages"
)
================================================
FILE: src/documents/settings.py
================================================
# Defines the names of file/thumbnail for the manifest
# for exporting/importing commands
EXPORTER_FILE_NAME = "__exported_file_name__"
EXPORTER_THUMBNAIL_NAME = "__exported_thumbnail_name__"
================================================
FILE: src/documents/signals/__init__.py
================================================
from django.dispatch import Signal
document_consumption_started = Signal(providing_args=["filename"])
document_consumption_finished = Signal(providing_args=["document"])
document_consumer_declaration = Signal(providing_args=[])
================================================
FILE: src/documents/signals/handlers.py
================================================
import logging
import os
from subprocess import Popen
from django.conf import settings
from django.contrib.admin.models import ADDITION, LogEntry
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.utils import timezone
from ..models import Correspondent, Document, Tag
def logger(message, group):
logging.getLogger(__name__).debug(message, extra={"group": group})
def set_correspondent(sender, document=None, logging_group=None, **kwargs):
# No sense in assigning a correspondent when one is already set.
if document.correspondent:
return
# No matching correspondents, so no need to continue
potential_correspondents = list(Correspondent.match_all(document.content))
if not potential_correspondents:
return
potential_count = len(potential_correspondents)
selected = potential_correspondents[0]
if potential_count > 1:
message = "Detected {} potential correspondents, so we've opted for {}"
logger(
message.format(potential_count, selected),
logging_group
)
logger(
'Assigning correspondent "{}" to "{}" '.format(selected, document),
logging_group
)
document.correspondent = selected
document.save(update_fields=("correspondent",))
def set_tags(sender, document=None, logging_group=None, **kwargs):
current_tags = set(document.tags.all())
relevant_tags = set(Tag.match_all(document.content)) - current_tags
if not relevant_tags:
return
message = 'Tagging "{}" with "{}"'
logger(
message.format(document, ", ".join([t.slug for t in relevant_tags])),
logging_group
)
document.tags.add(*relevant_tags)
def run_pre_consume_script(sender, filename, **kwargs):
if not settings.PRE_CONSUME_SCRIPT:
return
Popen((settings.PRE_CONSUME_SCRIPT, filename)).wait()
def run_post_consume_script(sender, document, **kwargs):
if not settings.POST_CONSUME_SCRIPT:
return
Popen((
settings.POST_CONSUME_SCRIPT,
str(document.id),
document.file_name,
document.source_path,
document.thumbnail_path,
document.download_url,
document.thumbnail_url,
str(document.correspondent),
str(",".join(document.tags.all().values_list("slug", flat=True)))
)).wait()
def cleanup_document_deletion(sender, instance, using, **kwargs):
if not isinstance(instance, Document):
return
for f in (instance.source_path, instance.thumbnail_path):
try:
os.unlink(f)
except FileNotFoundError:
pass # The file's already gone, so we're cool with it.
def set_log_entry(sender, document=None, logging_group=None, **kwargs):
ct = ContentType.objects.get(model="document")
user = User.objects.get(username="consumer")
LogEntry.objects.create(
action_flag=ADDITION,
action_time=timezone.now(),
content_type=ct,
object_id=document.id,
user=user,
object_repr=document.__str__(),
)
================================================
FILE: src/documents/static/js/colours.js
================================================
// The following jQuery snippet will add a small square next to the selection
// drop-down on the `Add tag` page that will update to show the selected tag
// color as the drop-down value is changed.
django.jQuery(document).ready(function(){
if (django.jQuery("#id_colour").length) {
let colour;
let colour_num;
colour_num = django.jQuery("#id_colour").val() - 1;
colour = django.jQuery('#id_colour')[0][colour_num].text;
django.jQuery('#id_colour').after('');
django.jQuery('.colour_square').css({
'float': 'left',
'width': '20px',
'height': '20px',
'margin': '5px',
'border': '1px solid rgba(0, 0, 0, .2)',
'background': colour
});
django.jQuery('#id_colour').change(function () {
colour_num = django.jQuery("#id_colour").val() - 1;
colour = django.jQuery('#id_colour')[0][colour_num].text;
django.jQuery('.colour_square').css({'background': colour});
});
} else if (django.jQuery("select[id*='colour']").length) {
django.jQuery('select[id*="-colour"]').each(function (index, element) {
let id;
let loop_colour_num;
let loop_colour;
id = "colour_square_" + index;
django.jQuery(element).after('');
loop_colour_num = django.jQuery(element).val() - 1;
loop_colour = django.jQuery(element)[0][loop_colour_num].text;
django.jQuery("").appendTo("head");
django.jQuery('#' + id).css({'background': loop_colour});
console.log(id, loop_colour_num, loop_colour);
django.jQuery(element).change(function () {
loop_colour_num = django.jQuery(element).val() - 1;
loop_colour = django.jQuery(element)[0][loop_colour_num].text;
django.jQuery('#' + id).css({'background': loop_colour});
console.log('#' + id, loop_colour)
});
})
}
});
================================================
FILE: src/documents/static/paperless.css
================================================
th.column-document,
td.field-document {
text-align: center;
}
td a.tag {
padding: 0 0.5em;
color: #ffffff;
border-radius: 0.2em;
margin: 1px;
display: inline-block;
}
#result_list th.column-note {
text-align: right;
}
#result_list td.field-note {
text-align: right;
}
#result_list td textarea {
width: 90%;
height: 5em;
}
================================================
FILE: src/documents/templates/admin/base_site.html
================================================
{% extends 'admin/base_site.html' %}
{# NOTE: This should probably be extending base.html. See CSS comment below details. #}
{% load static %}
{% load custom_css from customisation %}
{% load custom_js from customisation %}
{% block extrahead %}
{% endblock %}
{% block branding %}
{% endblock %}
{% block blockbots %}
{% comment %}
This really should be extending `extrastyle`, but the the
django-flat-responsive package decided that it wanted to put its CSS in
this block, so to make sure that overrides are in fact overriding
everything else, we have to do the Wrong Thing here.
Once we switch to Django 2.x and drop django-flat-responsive, we should
switch this to `extrastyle` where it should be.
{% endcomment %}
{{ block.super }}
{% custom_css %}
{% endblock blockbots %}
{% block footer %}
{% comment %}
The Django admin doesn't have a block for Javascript you'd want placed in
the footer, so we have to use this one instead.
{% endcomment %}
{{ block.super }}
{% custom_js %}
{% endblock footer %}
================================================
FILE: src/documents/templates/admin/documents/document/change_form.html
================================================
{% extends 'admin/change_form.html' %}
{% block content %}
{{ block.super }}
Preview
{% if next_object %}
{% endif %}
{% endblock content %}
{% block extrastyle %}
{{ block.super }}
{% endblock %}
{% block footer %}
{{ block.super }}
{# Hack to force Django to make the created date a date input rather than `text` (the default) #}
{% endblock footer %}
================================================
FILE: src/documents/templates/admin/documents/document/change_list.html
================================================
{% extends 'admin/change_list.html' %}
{% load admin_actions from admin_list%}
{% load result_list from hacks %}
{% block result_list %}
{% if action_form and actions_on_top and cl.show_admin_actions %}{% admin_actions %}{% endif %}
{% result_list cl %}
{% if action_form and actions_on_bottom and cl.show_admin_actions %}{% admin_actions %}{% endif %}
{% endblock %}
================================================
FILE: src/documents/templates/admin/documents/document/change_list_results.html
================================================
{% load i18n %}
{# This is just copypasta from the parent change_list_results.html file #}
{% for header in result_headers %}
{% if header.sortable %}
{% if header.sort_priority > 0 %}
{% if num_sorted_fields > 1 %}{{ header.sort_priority }}{% endif %}
{% endblock %}
================================================
FILE: src/documents/templates/admin/index.html
================================================
{% extends "admin/index.html" %}
{% load i18n static %}
{# This block adds a search form on the admin start page and on the module start page so that #}
{# the user can quickly search for documents #}
{% block pretitle %}
{% trans 'Search documents' %}
{% endblock %}
{# This whole block is here just to override the `get_admin_log` line so #}
{# that the log entries aren't limited to the current user #}
{% block sidebar %}
{% trans 'Recent actions' %}
{% trans 'My actions' %}
{% load log %}
{% get_admin_log 10 as admin_log %}
{% if not admin_log %}
{% trans 'None available' %}
{% else %}
{% for entry in admin_log %}
{% if entry.is_deletion or not entry.get_admin_url %}
{{ entry.object_repr }}
{% else %}
{{ entry.object_repr }}
{% endif %}
{% if entry.content_type %}
{% filter capfirst %}{{ entry.content_type }}{% endfilter %}
{% else %}
{% trans 'Unknown content' %}
{% endif %}
{% endfor %}
{% endif %}
{% endblock %}
================================================
FILE: src/documents/templates/documents/index.html
================================================
Paperless
{# One day someone (maybe even myself) is going to write a proper web front-end for Paperless, and this is where it'll start. #}
================================================
FILE: src/documents/templatetags/__init__.py
================================================
================================================
FILE: src/documents/templatetags/customisation.py
================================================
import os
from django import template
from django.conf import settings
from django.utils.safestring import mark_safe
register = template.Library()
@register.simple_tag()
def custom_css():
theme_path = os.path.join(
settings.MEDIA_ROOT,
"overrides.css"
)
if os.path.exists(theme_path):
return mark_safe(
''.format(
os.path.join(settings.MEDIA_URL, "overrides.css")
)
)
return ""
@register.simple_tag()
def custom_js():
theme_path = os.path.join(
settings.MEDIA_ROOT,
"overrides.js"
)
if os.path.exists(theme_path):
return mark_safe(
''.format(
os.path.join(settings.MEDIA_URL, "overrides.js")
)
)
return ""
================================================
FILE: src/documents/templatetags/hacks.py
================================================
import re
from django.contrib.admin.templatetags.admin_list import (
result_headers,
result_hidden_fields,
results
)
from django.template import Library
EXTRACT_URL = re.compile(r'href="(.*?)"')
register = Library()
@register.inclusion_tag("admin/documents/document/change_list_results.html")
def result_list(cl):
"""
Copy/pasted from django.contrib.admin.templatetags.admin_list just so I can
modify the value passed to `.inclusion_tag()` in the decorator here. There
must be a cleaner way... right?
"""
headers = list(result_headers(cl))
num_sorted_fields = 0
for h in headers:
if h['sortable'] and h['sorted']:
num_sorted_fields += 1
return {'cl': cl,
'result_hidden_fields': list(result_hidden_fields(cl)),
'result_headers': headers,
'num_sorted_fields': num_sorted_fields,
'results': map(add_doc_edit_url, results(cl))}
def add_doc_edit_url(result):
"""
Make the document edit URL accessible to the view as a separate item
"""
title = result[1]
match = re.search(EXTRACT_URL, title)
edit_doc_url = match.group(1)
result.append(edit_doc_url)
return result
================================================
FILE: src/documents/tests/__init__.py
================================================
================================================
FILE: src/documents/tests/factories.py
================================================
import factory
from ..models import Document, Correspondent
class CorrespondentFactory(factory.DjangoModelFactory):
class Meta:
model = Correspondent
name = factory.Faker("name")
class DocumentFactory(factory.DjangoModelFactory):
class Meta:
model = Document
================================================
FILE: src/documents/tests/samples/inline_mail.txt
================================================
Return-Path:
Received: from mout.mailhost.com ([212.227.17.22]) by mx-ha.mailhost.com (mx101) with
ESMTPS (Nemesis) id 0M22yt-1bePTC1Tkx-00u3T3 for ; Thu,
14 Apr 2016 16:26:53 +0200
Received: from [192.168.178.11] ([65.50.20.247]) by mail.mailhost.com (mr101)
with ESMTPSA (Nemesis) id 0MB2G8-1ayNjv3pA4-009xHi for ;
Thu, 14 Apr 2016 16:26:53 +0200
From: Florian Harr
Content-Type: multipart/alternative; boundary="Apple-Mail=_E0BA9ABB-7689-4507-B940-34AA2DDB3DCC"
Subject: Paperless Inline Image
Message-Id:
Date: Thu, 14 Apr 2016 10:26:36 -0400
To: Florian Harr
Mime-Version: 1.0 (Mac OS X Mail 9.3 \(3124\))
X-Mailer: Apple Mail (2.3124)
Envelope-To:
--Apple-Mail=_E0BA9ABB-7689-4507-B940-34AA2DDB3DCC
Content-Transfer-Encoding: 7BIT
Content-Type: TEXT/plain;
charset=us-ascii
The secret word is "paperless" :-)
--Apple-Mail=_E0BA9ABB-7689-4507-B940-34AA2DDB3DCC
Content-Type: multipart/related;
boundary="Apple-Mail=_4D0B5162-526C-4D33-95B0-7037924B5757";
type="text/html"
--Apple-Mail=_4D0B5162-526C-4D33-95B0-7037924B5757
Content-Transfer-Encoding: 7BIT
Content-Type: TEXT/html;
charset=us-ascii