30 Commits
1.10 ... master

Author SHA1 Message Date
88d6118939 Bump to 0.12 2022-04-27 17:00:23 +02:00
MaxJa4
9d3087e78b Updates jSignature.js to newest version 2022-04-27 16:53:44 +02:00
Sebastien Corbin
4883e37c1a Update badges 2022-01-17 14:18:10 +01:00
Sebastien Corbin
ae58c7f725 Bump version to 0.11 2022-01-17 14:07:09 +01:00
Sebastien Corbin
cf9a37a009 Remove depreciations 2022-01-17 14:02:35 +01:00
Sebastien Corbin
382c824cca Flake8 2022-01-17 14:02:35 +01:00
Sebastien Corbin
63c3d7d2dc Move to github actions 2022-01-17 14:02:35 +01:00
David Sauve
ea3800375c Improved _remove_empty so that it doesn't remove 0, just None 2022-01-17 09:12:03 +01:00
David Sauve
a42a9ca543 Filter out null values in signature data
Null values in the signature data will cause the compute box calculation to fail. Filter them out during json.loads
2022-01-17 09:12:03 +01:00
kygoh
9e81795f99 Fix django tarball url 2021-10-10 18:37:33 +02:00
kygoh
7fe2560910 Use environment markers to install pyquery based on python version 2021-10-10 18:37:33 +02:00
kygoh
e92132349c Accept float x,y values 2021-10-10 18:37:33 +02:00
Sebastien Corbin
a740767a06 Release 0.10 2020-07-26 15:58:33 +02:00
Sebastien Corbin
ea249473f1 Add full example of front-office usage 2020-07-26 15:58:12 +02:00
Sebastien Corbin
bea517d746 Add a template filter for displaying signature as base64 2020-07-26 15:55:51 +02:00
Sebastien Corbin
4dd9e5f25f Improve coverage 2020-04-18 21:08:09 +02:00
Sebastien Corbin
72e9f3d6da Fix setup.py classifiers 2020-04-18 21:04:30 +02:00
Sebastien Corbin
7601258ea8 Improve setup.py for next time 2020-04-18 21:01:55 +02:00
Sebastien Corbin
3f5d877b6a Release 0.9 2020-04-18 20:51:52 +02:00
Sebastien Corbin
cf2ad535e1 Add tests for admin usage 2020-04-18 20:48:06 +02:00
Sebastien Corbin
2be2c4dcc6 Update jSignature lib 2020-04-18 20:22:14 +02:00
Sebastien Corbin
d08e0ba276 Handle both admin and not admin usages 2020-04-18 20:22:04 +02:00
Sebastien Corbin
a6946d160d Include example project 2020-04-18 20:20:26 +02:00
Sebastien Corbin
ac8c20abb3 Remove dependency over six 2020-04-18 19:04:25 +02:00
Sebastien Corbin
622f3ea10b Rework test matrix and structure 2020-04-18 18:53:42 +02:00
Sébastien Corbin
c5f7c10d23 Merge pull request #12 from adremides/master
Fix jquery dependency for Django >2.2 version
2020-03-11 10:51:39 +01:00
Unknown
d0ae3a9c43 Fix jquery dependency for Django >2.2 version
Django changed the way media assets are merged, now using a topological sort algorithm. Dependencies are now needed to be added in the media so they are recognized by the algorithm.
2019-10-03 20:38:25 -03:00
Florent Lebreton
39f2ba9533 Merge pull request #11 from notanumber/master
Updated to fix an error when using jSignature field with Django 2
2019-07-03 16:55:36 +02:00
David Sauve
33b43b98b0 Added renderer=None to render call 2019-04-03 14:28:43 -07:00
David Sauve
9923614d6a Updated JSignatureField to remove SubfieldBase 2019-04-03 14:25:11 -07:00
44 changed files with 770 additions and 331 deletions

66
.github/workflows/actions.yml vendored Normal file
View File

@@ -0,0 +1,66 @@
name: Run Tests
on: [ push, pull_request ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
django_version:
- '2.2'
- '3.2'
- '4.0'
python-version:
- 3.6
- 3.7
- 3.8
- 3.9
- "3.10"
exclude:
- django_version: '2.2'
python-version: '3.10'
- django_version: '4.0'
python-version: '3.6'
- django_version: '4.0'
python-version: '3.7'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Cache pip
uses: actions/cache@v2
with:
# This path is specific to Ubuntu
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ matrix.django_version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e .
pip install -U flake8 coveralls argparse
pip install -U Django~=${{ matrix.django_version }}
- name: Lint with flake8
run: flake8 --ignore=E501,W504 jsignature
- name: Test Django
run: |
python -W error::DeprecationWarning -W error::PendingDeprecationWarning \
-m coverage run ./runtests.py
- name: Coverage
if: ${{ success() }}
run: |
coveralls --service=github

5
.gitignore vendored
View File

@@ -1,3 +1,8 @@
*.pyc *.pyc
.coverage .coverage
htmlcov htmlcov
*.egg-info
.tox
example.db
/dist
/build

View File

@@ -1,42 +0,0 @@
language: python
python:
- 2.6
- 2.7
- 3.2
- 3.3
- 3.4
env:
- DJANGO_VERSION=1.4 MODULE=jsignature
- DJANGO_VERSION=1.5 MODULE=jsignature
- DJANGO_VERSION=1.6 MODULE=jsignature.tests
- DJANGO_VERSION=1.7 MODULE=jsignature.tests
install:
- pip install -r requirements.txt --use-mirrors
- pip install -q Django==$DJANGO_VERSION --use-mirrors
- pip install coverage
script: coverage run quicktest.py $MODULE
after_success:
- pip install coveralls
- coveralls
# We need to exclude old versions of Django for tests with python 3.
# We need to exclude old versions of Python for tests with Django >= 1.7.
matrix:
exclude:
- python: 3.2
env: DJANGO_VERSION=1.4 MODULE=jsignature
- python: 3.3
env: DJANGO_VERSION=1.4 MODULE=jsignature
- python: 3.4
env: DJANGO_VERSION=1.4 MODULE=jsignature
- python: 3.4
env: DJANGO_VERSION=1.5 MODULE=jsignature
- python: 3.4
env: DJANGO_VERSION=1.6 MODULE=jsignature.tests
- python: 2.6
env: DJANGO_VERSION=1.7 MODULE=jsignature.tests

31
CHANGES
View File

@@ -2,6 +2,37 @@
CHANGELOG CHANGELOG
========= =========
0.11 (2022-01-17)
==================
** New **
- Django 4.0 compatibility.
- Moved to Github Actions for testing.
- Added flake8 in CI.
- Dropped support for python < 3.6
- Dropped support for Django 1.11, 2.0, 2.1, 3.0, 3.1.
0.10 (2020-07-26)
==================
** New **
- Add template filter to easily render a signature structure as base64 url.
- Add a full example of front-office usage.
0.9 (2020-04-18)
==================
** New **
- Add support for Django 2+
- Drop support for Django<1.11
- Add a ``JSIGNATURE_JQUERY`` settings to handle usage in admin in Django 2.1+
0.8 (2014-12-04) 0.8 (2014-12-04)
================== ==================

View File

@@ -5,28 +5,32 @@ It provides:
* A form field and a form widget to handle jquery plugin through a Django form; * A form field and a form widget to handle jquery plugin through a Django form;
* A model field to store a captured signature; * A model field to store a captured signature;
* A mixin adding two fields (signature / signature_date) in any of your Django models. * A mixin adding two fields (signature / signature_date) in any of your Django models.
* A template filter to render signatures as base64 image urls.
.. image:: https://travis-ci.org/fle/django-jsignature.png?branch=master .. image:: https://img.shields.io/pypi/v/django-jsignature.svg
:target: https://travis-ci.org/fle/django-jsignature :target: https://pypi.python.org/pypi/django-jsignature/
:alt: Latest PyPI version
.. image:: https://coveralls.io/repos/fle/django-jsignature/badge.png .. image:: https://github.com/fle/django-jsignature/actions/workflows/actions.yml/badge.svg
:target: https://coveralls.io/r/fle/django-jsignature :target: https://github.com/fle/django-jsignature/actions
:alt: Build status
.. image:: https://coveralls.io/repos/github/fle/django-jsignature/badge.svg?branch=master
:target: https://coveralls.io/github/fle/django-jsignature?branch=master
:alt: Coverage status
.. image:: https://github.com/fle/django-jsignature/blob/master/screen.png .. image:: https://github.com/fle/django-jsignature/blob/master/screen.png
================== ==================
INSTALL Installation
================== ==================
For now:
:: ::
pip install django-jsignature pip install django-jsignature
================== ==================
USAGE Usage
================== ==================
* Add ``jsignature`` to your ``INSTALLED_APPS``: * Add ``jsignature`` to your ``INSTALLED_APPS``:
@@ -39,51 +43,50 @@ USAGE
'jsignature', 'jsignature',
) )
* Use provided form field and widget: * Use provided model field (for easy storage):
:: ::
# forms.py # models.py
from django import forms from django.db import models
from jsignature.forms import JSignatureField from jsignature.fields import JSignatureField
class SignatureForm(forms.Form): class SignatureModel(models.Model):
signature = JSignatureField() signature = JSignatureField()
* In your template * In your form template
:: ::
{{ form.media }} {{ form.media }}
<form action="." method="POST"> <form action="" method="post">
{% for field in form %} {{ form }}
{{ field.label_tag }}
{{ field }}
{% endfor %}
<input type="submit" value="Save" /> <input type="submit" value="Save" />
{% csrf_token %} {% csrf_token %}
</form> </form>
* Render image after form validation: * Render image from db value in your display template:
:: ::
# views.py {# yourtemplate.html #}
from jsignature.utils import draw_signature {% load jsignature_filters %}
from myapp.forms import SignatureForm
def my_view(request): <img src="{{ obj.signature|signature_base64 }}" alt="{{ obj }}" />
form = SignatureForm(request.POST or None)
if form.is_valid():
signature = form.cleaned_data.get('signature') * By default, jSignature is made to work outside of admin, requiring that
if signature: you include the jQuery library in your ``<head>``.
# as an image
signature_picture = draw_signature(signature) If you want to use jSignature in the Django admin site, set the
# or as a file ``JSIGNATURE_JQUERY`` setting to ``admin``. Otherwise if set to any url
signature_file_path = draw_signature(signature, as_file=True) pointing to jQuery, it will be automatically included.
It is strongly suggested to take example from ``example_project``, which is
`located in this repo <https://github.com/fle/django-jsignature/tree/master/example_project>`_
================== ==================
CUSTOMIZATION Customization
================== ==================
JSignature plugin options are available in python: JSignature plugin options are available in python:
@@ -118,10 +121,11 @@ Available settings are:
* ``JSIGNATURE_RESET_BUTTON`` (ResetButton) * ``JSIGNATURE_RESET_BUTTON`` (ResetButton)
================== ==================
IN YOUR MODELS In your models
================== ==================
If you wan to store signatures, provided mixin gives a ``signature`` and a ``signature_date`` that update themselves: If you want to store signatures easily, a provided mixin gives a ``signature``
and a ``signature_date`` that update themselves:
:: ::
@@ -133,10 +137,69 @@ If you wan to store signatures, provided mixin gives a ``signature`` and a ``sig
================== ==================
AUTHORS In your forms
================== ==================
* Florent Lebreton <florent.lebreton@makina-corpus.com> * If you need more precise handling of the form field, you can use it directly:
::
# forms.py
from django import forms
from jsignature.forms import JSignatureField
class SignatureForm(forms.Form):
signature = JSignatureField()
* And upon saving, have direct access to the image with ``draw_signature()``
::
# views.py
from jsignature.utils import draw_signature
from myapp.forms import SignatureForm
def my_view(request):
form = SignatureForm(request.POST or None)
if form.is_valid():
signature = form.cleaned_data.get('signature')
if signature:
# as an image
signature_picture = draw_signature(signature)
# or as a file
signature_file_path = draw_signature(signature, as_file=True)
==================
Example project
==================
If you want to have a demo of this package, just use the example project:
::
git clone https://github.com/fle/django-jsignature.git
cd django-jsignature
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
pip install -e .
cd example_project
./manage.py migrate
./manage.py createsuperuser
Fill the user info, launch django with ``./manage.py runserver`` and head over
to `http://127.0.0.1:8000/ <http://127.0.0.1:8000/>`_, you can also
`login to the admin <http://127.0.0.1:8000/admin>`_ with the credentials your
provided.
==================
Authors
==================
* Florent Lebreton <florent.lebreton@makina-corpus.com> (original author)
* Sébastien Corbin <sebastien.corbin@makina-corpus.com> (maintainer)
|makinacom|_ |makinacom|_

View File

5
example_project/admin.py Normal file
View File

@@ -0,0 +1,5 @@
from django.contrib import admin
from .models import ExampleModel
admin.site.register(ExampleModel)

10
example_project/manage.py Executable file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)

View File

@@ -0,0 +1,26 @@
# Generated by Django 3.0.5 on 2020-04-18 12:18
from django.db import migrations, models
import jsignature.fields
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='ExampleModel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('signature', jsignature.fields.JSignatureField(blank=True, null=True, verbose_name='Signature')),
('signature_date', models.DateTimeField(blank=True, null=True, verbose_name='Signature date')),
],
options={
'abstract': False,
},
),
]

View File

View File

@@ -0,0 +1,5 @@
from jsignature.mixins import JSignatureFieldsMixin
class ExampleModel(JSignatureFieldsMixin):
pass

View File

@@ -0,0 +1,67 @@
import os
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
DEBUG = True
ALLOWED_HOSTS = ['*']
MANAGERS = ADMINS = ()
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'example.db'),
'USER': '',
'PASSWORD': '',
'HOST': '',
'PORT': '',
}
}
SECRET_KEY = 'notsosecret'
MIDDLEWARE = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': True,
'OPTIONS': {
'context_processors': (
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.template.context_processors.debug",
"django.template.context_processors.i18n",
"django.template.context_processors.media",
"django.template.context_processors.static",
"django.template.context_processors.tz",
"django.contrib.messages.context_processors.messages",
),
},
},
]
ROOT_URLCONF = 'example_project.urls'
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.admin',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.staticfiles',
'django.contrib.messages',
'jsignature',
'example_project',
)
STATIC_URL = '/static/'
JSIGNATURE_JQUERY = 'admin'

View File

@@ -0,0 +1,39 @@
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<title>Example project</title>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="{% url 'admin:index' %}">Admnistration</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'list' %}">List</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'create' %}">Create</a>
</li>
</ul>
</div>
</nav>
<div class="container">
{% block content %}{% endblock %}
</div>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
{% block extra_media %}{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,14 @@
{% extends "base.html" %}
{% block extra_media %}
{{ form.media }}
{% endblock %}
{% block content %}
<form action="" method="post">
{{ form }}
<input type="submit" value="Save" class="btn btn-success" />
<a href="{% url 'list' %}" class="btn btn-danger">Cancel</a>
{% csrf_token %}
</form>
{% endblock %}

View File

@@ -0,0 +1,24 @@
{% extends "base.html" %}{% load jsignature_filters %}
{% block content %}
<h1>List</h1>
<ul>
{% for obj in object_list %}
<li>
<dl>
<dt>Raw data (from db)</dt>
<dd>{{ obj.signature }}</dd>
<dt>As image</dt>
<dd>
<img src="{{ obj.signature|signature_base64 }}" alt="{{ obj }}" />
<a href="{% url 'update' obj.pk %}" class="btn btn-primary">
Update
</a>
</dd>
</dl>
</li>
{% endfor %}
</ul>
<a href="{% url 'create' %}" class="btn btn-primary">Create</a>
{% endblock %}

13
example_project/urls.py Normal file
View File

@@ -0,0 +1,13 @@
from __future__ import unicode_literals
from django.contrib import admin
from django.urls import path
from example_project import views
urlpatterns = [
path('', views.ExampleListView.as_view(), name='list'),
path('create', views.ExampleCreateView.as_view(), name='create'),
path('update/<int:pk>', views.ExampleUpdateView.as_view(), name='update'),
path('admin', admin.site.urls),
]

20
example_project/views.py Normal file
View File

@@ -0,0 +1,20 @@
from django.urls import reverse_lazy
from django.views import generic
from example_project.models import ExampleModel
class ExampleCreateView(generic.CreateView):
model = ExampleModel
fields = '__all__'
success_url = reverse_lazy('list')
class ExampleUpdateView(generic.UpdateView):
model = ExampleModel
fields = '__all__'
success_url = reverse_lazy('list')
class ExampleListView(generic.ListView):
model = ExampleModel

View File

@@ -3,17 +3,24 @@
with jSignature jQuery plugin with jSignature jQuery plugin
""" """
import json import json
import six
from django.db import models from django.db import models
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from .forms import ( from .forms import (
JSignatureField as JSignatureFormField, JSignatureField as JSignatureFormField,
JSIGNATURE_EMPTY_VALUES) JSIGNATURE_EMPTY_VALUES,
)
try:
from django.utils import six
string_types = six.string_types
except ImportError:
string_types = str
class JSignatureField(six.with_metaclass(models.SubfieldBase, models.Field)): class JSignatureField(models.Field):
""" """
A model field handling a signature captured with jSignature A model field handling a signature captured with jSignature
""" """
@@ -39,7 +46,7 @@ class JSignatureField(six.with_metaclass(models.SubfieldBase, models.Field)):
def get_prep_value(self, value): def get_prep_value(self, value):
if value in JSIGNATURE_EMPTY_VALUES: if value in JSIGNATURE_EMPTY_VALUES:
return None return None
elif isinstance(value, six.string_types): elif isinstance(value, string_types):
return value return value
elif isinstance(value, list): elif isinstance(value, list):
return json.dumps(value) return json.dumps(value)
@@ -49,9 +56,3 @@ class JSignatureField(six.with_metaclass(models.SubfieldBase, models.Field)):
defaults = {'form_class': JSignatureFormField} defaults = {'form_class': JSignatureFormField}
defaults.update(kwargs) defaults.update(kwargs)
return super(JSignatureField, self).formfield(**defaults) return super(JSignatureField, self).formfield(**defaults)
try:
from south.modelsinspector import add_introspection_rules
add_introspection_rules([], ["jsignature.fields.JSignatureField"])
except ImportError:
pass

View File

@@ -2,9 +2,10 @@
A django mixin providing fields to store a signature captured A django mixin providing fields to store a signature captured
with jSignature jQuery plugin with jSignature jQuery plugin
""" """
import json
from datetime import datetime from datetime import datetime
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
from .fields import JSignatureField from .fields import JSignatureField
@@ -28,7 +29,7 @@ class JSignatureFieldsMixin(models.Model):
original = not is_new and self.__class__.objects.get(pk=self.pk) original = not is_new and self.__class__.objects.get(pk=self.pk)
if self.signature: if self.signature:
if is_new or self.signature != original.signature: if is_new or json.dumps(self.signature) != original.signature:
self.signature_date = datetime.now() self.signature_date = datetime.now()
else: else:
self.signature_date = None self.signature_date = None

View File

@@ -17,6 +17,9 @@ JSIGNATURE_UNDO_BUTTON = getattr(
JSIGNATURE_RESET_BUTTON = getattr( JSIGNATURE_RESET_BUTTON = getattr(
settings, 'JSIGNATURE_RESET_BUTTON', True) settings, 'JSIGNATURE_RESET_BUTTON', True)
JSIGNATURE_JQUERY = getattr(
settings, 'JSIGNATURE_JQUERY', 'custom')
JSIGNATURE_DEFAULT_CONFIG = { JSIGNATURE_DEFAULT_CONFIG = {
'width': JSIGNATURE_WIDTH, 'width': JSIGNATURE_WIDTH,
'height': JSIGNATURE_HEIGHT, 'height': JSIGNATURE_HEIGHT,

View File

@@ -1,3 +1,4 @@
(function($) {
$(document).ready(function() { $(document).ready(function() {
$(".jsign-container").each(function(){ $(".jsign-container").each(function(){
var config = $(this).data('config'); var config = $(this).data('config');
@@ -17,5 +18,5 @@ $(document).ready(function() {
$(".jsign-wrapper input").on("click", function(e) { $(".jsign-wrapper input").on("click", function(e) {
$(this).siblings('.jsign-container').jSignature('reset'); $(this).siblings('.jsign-container').jSignature('reset');
}); });
}); });
})(jQuery || django.jQuery)

104
jsignature/static/js/jSignature.min.js vendored Normal file → Executable file
View File

@@ -1,11 +1,16 @@
/* /*
jSignature v2 "2013-08-26T07:00" "commit ID 986265299fecdb0f5b20acce92319220809707bf" jSignature v2 "2018-11-06T13:56" "commit ID 89c22b348ab2e1d92a928d8fd992f175e8bc5cbd"
Copyright (c) 2012 Willow Systems Corp http://willow-systems.com Copyright (c) 2012 Willow Systems Corp http://willow-systems.com
Copyright (c) 2010 Brinley Ang http://www.unbolt.net Copyright (c) 2010 Brinley Ang http://www.unbolt.net
MIT License <http://www.opensource.org/licenses/mit-license.php> MIT License <http://www.opensource.org/licenses/mit-license.php>
Simplify.js BSD
(c) 2012, Vladimir Agafonkin
mourner.github.com/simplify-js
base64 encoder base64 encoder
MIT, GPL MIT, GPL
http://phpjs.org/functions/base64_encode http://phpjs.org/functions/base64_encode
@@ -26,54 +31,51 @@ jSignature v2 jSignature's custom "base30" format export and import plugins.
jSignature v2 SVG export plugin. jSignature v2 SVG export plugin.
Simplify.js BSD
(c) 2012, Vladimir Agafonkin
mourner.github.com/simplify-js
*/ */
(function(){function r(b){var a,e=b.css("color"),d;b=b[0];for(var q=!1;b&&!d&&!q;){try{a=$(b).css("background-color")}catch(c){a="transparent"}"transparent"!==a&&"rgba(0, 0, 0, 0)"!==a&&(d=a);q=b.body;b=b.parentNode}b=/rgb[a]*\((\d+),\s*(\d+),\s*(\d+)/;var q=/#([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})/,k;a=void 0;(a=e.match(b))?k={r:parseInt(a[1],10),g:parseInt(a[2],10),b:parseInt(a[3],10)}:(a=e.match(q))&&(k={r:parseInt(a[1],16),g:parseInt(a[2],16),b:parseInt(a[3],16)});var n; (function(){function q(a){var b=a.css("color"),c;a=a[0];for(var g=!1;a&&!c&&!g;){try{var d=$(a).css("background-color")}catch(l){d="transparent"}"transparent"!==d&&"rgba(0, 0, 0, 0)"!==d&&(c=d);g=a.body;a=a.parentNode}a=/rgb[a]*\((\d+),\s*(\d+),\s*(\d+)/;g=/#([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})/;d=void 0;if(d=b.match(a))var m={r:parseInt(d[1],10),g:parseInt(d[2],10),b:parseInt(d[3],10)};else(d=b.match(g))&&(m={r:parseInt(d[1],16),g:parseInt(d[2],16),b:parseInt(d[3],16)});
d?(a=void 0,(a=d.match(b))?n={r:parseInt(a[1],10),g:parseInt(a[2],10),b:parseInt(a[3],10)}:(a=d.match(q))&&(n={r:parseInt(a[1],16),g:parseInt(a[2],16),b:parseInt(a[3],16)})):n=k?127<Math.max.apply(null,[k.r,k.g,k.b])?{r:0,g:0,b:0}:{r:255,g:255,b:255}:{r:255,g:255,b:255};a=function(a){return"rgb("+[a.r,a.g,a.b].join(", ")+")"};k&&n?(b=Math.max.apply(null,[k.r,k.g,k.b]),k=Math.max.apply(null,[n.r,n.g,n.b]),k=Math.round(k+-0.75*(k-b)),k={r:k,g:k,b:k}):k?(k=Math.max.apply(null,[k.r,k.g,k.b]),b=1,127< if(c)if(d=void 0,d=c.match(a))var e={r:parseInt(d[1],10),g:parseInt(d[2],10),b:parseInt(d[3],10)};else(d=c.match(g))&&(e={r:parseInt(d[1],16),g:parseInt(d[2],16),b:parseInt(d[3],16)});else e=m?127<Math.max.apply(null,[m.r,m.g,m.b])?{r:0,g:0,b:0}:{r:255,g:255,b:255}:{r:255,g:255,b:255};d=function(a){return"rgb("+[a.r,a.g,a.b].join(", ")+")"};m&&e?(a=Math.max.apply(null,[m.r,m.g,m.b]),m=Math.max.apply(null,[e.r,e.g,e.b]),m=Math.round(m+-.75*(m-a)),m={r:m,g:m,b:m}):m?(m=Math.max.apply(null,[m.r,m.g,
k&&(b=-1),k=Math.round(k+96*b),k={r:k,g:k,b:k}):k={r:191,g:191,b:191};return{color:e,"background-color":n?a(n):d,"decor-color":a(k)}}function l(b,a){this.x=b;this.y=a;this.reverse=function(){return new this.constructor(-1*this.x,-1*this.y)};this._length=null;this.getLength=function(){this._length||(this._length=Math.sqrt(Math.pow(this.x,2)+Math.pow(this.y,2)));return this._length};var e=function(a){return Math.round(a/Math.abs(a))};this.resizeTo=function(a){if(0===this.x&&0===this.y)this._length= m.b]),a=1,127<m&&(a=-1),m=Math.round(m+96*a),m={r:m,g:m,b:m}):m={r:191,g:191,b:191};return{color:b,"background-color":e?d(e):c,"decor-color":d(m)}}function k(a,b){this.x=a;this.y=b;this.reverse=function(){return new this.constructor(-1*this.x,-1*this.y)};this._length=null;this.getLength=function(){this._length||(this._length=Math.sqrt(Math.pow(this.x,2)+Math.pow(this.y,2)));return this._length};var c=function(a){return Math.round(a/Math.abs(a))};this.resizeTo=function(a){if(0===this.x&&0===this.y)this._length=
0;else if(0===this.x)this._length=a,this.y=a*e(this.y);else if(0===this.y)this._length=a,this.x=a*e(this.x);else{var b=Math.abs(this.y/this.x),c=Math.sqrt(Math.pow(a,2)/(1+Math.pow(b,2))),b=b*c;this._length=a;this.x=c*e(this.x);this.y=b*e(this.y)}return this};this.angleTo=function(a){var b=this.getLength()*a.getLength();return 0===b?0:Math.acos(Math.min(Math.max((this.x*a.x+this.y*a.y)/b,-1),1))/Math.PI}}function h(b,a){this.x=b;this.y=a;this.getVectorToCoordinates=function(a,b){return new l(a-this.x, 0;else if(0===this.x)this._length=a,this.y=a*c(this.y);else if(0===this.y)this._length=a,this.x=a*c(this.x);else{var b=Math.abs(this.y/this.x),g=Math.sqrt(Math.pow(a,2)/(1+Math.pow(b,2)));b*=g;this._length=a;this.x=g*c(this.x);this.y=b*c(this.y)}return this};this.angleTo=function(a){var b=this.getLength()*a.getLength();return 0===b?0:Math.acos(Math.min(Math.max((this.x*a.x+this.y*a.y)/b,-1),1))/Math.PI}}function h(a,b){this.x=a;this.y=b;this.getVectorToCoordinates=function(a,b){return new k(a-this.x,
b-this.y)};this.getVectorFromCoordinates=function(a,b){return this.getVectorToCoordinates(a,b).reverse()};this.getVectorToPoint=function(a){return new l(a.x-this.x,a.y-this.y)};this.getVectorFromPoint=function(a){return this.getVectorToPoint(a).reverse()}}function p(b,a,e,d,c){this.data=b;this.context=a;if(b.length)for(var E=b.length,k,n,f=0;f<E;f++){k=b[f];n=k.x.length;e.call(a,k);for(var D=1;D<n;D++)d.call(a,k,D);c.call(a,k)}this.changed=function(){};this.startStrokeFn=e;this.addToStrokeFn=d;this.endStrokeFn= b-this.y)};this.getVectorFromCoordinates=function(a,b){return this.getVectorToCoordinates(a,b).reverse()};this.getVectorToPoint=function(a){return new k(a.x-this.x,a.y-this.y)};this.getVectorFromPoint=function(a){return this.getVectorToPoint(a).reverse()}}function p(a,b,c,g,d){this.data=a;this.context=b;if(a.length)for(var m=a.length,e,l,f=0;f<m;f++){e=a[f];l=e.x.length;c.call(b,e);for(var t=1;t<l;t++)g.call(b,e,t);d.call(b,e)}this.changed=function(){};this.startStrokeFn=c;this.addToStrokeFn=g;this.endStrokeFn=
c;this.inStroke=!1;this._stroke=this._lastPoint=null;this.startStroke=function(a){if(a&&"number"==typeof a.x&&"number"==typeof a.y){this._stroke={x:[a.x],y:[a.y]};this.data.push(this._stroke);this._lastPoint=a;this.inStroke=!0;var b=this._stroke,e=this.startStrokeFn,d=this.context;setTimeout(function(){e.call(d,b)},3);return a}return null};this.addToStroke=function(a){if(this.inStroke&&"number"===typeof a.x&&"number"===typeof a.y&&4<Math.abs(a.x-this._lastPoint.x)+Math.abs(a.y-this._lastPoint.y)){var b= d;this.inStroke=!1;this._stroke=this._lastPoint=null;this.startStroke=function(a){if(a&&"number"==typeof a.x&&"number"==typeof a.y){this._stroke={x:[a.x],y:[a.y]};this.data.push(this._stroke);this._lastPoint=a;this.inStroke=!0;var b=this._stroke,c=this.startStrokeFn,d=this.context;setTimeout(function(){c.call(d,b)},3);return a}return null};this.addToStroke=function(a){if(this.inStroke&&"number"===typeof a.x&&"number"===typeof a.y&&4<Math.abs(a.x-this._lastPoint.x)+Math.abs(a.y-this._lastPoint.y)){var b=
this._stroke.x.length;this._stroke.x.push(a.x);this._stroke.y.push(a.y);this._lastPoint=a;var e=this._stroke,d=this.addToStrokeFn,c=this.context;setTimeout(function(){d.call(c,e,b)},3);return a}return null};this.endStroke=function(){var a=this.inStroke;this.inStroke=!1;this._lastPoint=null;if(a){var b=this._stroke,e=this.endStrokeFn,d=this.context,c=this.changed;setTimeout(function(){e.call(d,b);c.call(d)},3);return!0}return null}}function m(b,a,e,d){if("ratio"===a||"%"===a.split("")[a.length-1])this.eventTokens[e+ this._stroke.x.length;this._stroke.x.push(a.x);this._stroke.y.push(a.y);this._lastPoint=a;var c=this._stroke,d=this.addToStrokeFn,g=this.context;setTimeout(function(){d.call(g,c,b)},3);return a}return null};this.endStroke=function(){var a=this.inStroke;this.inStroke=!1;this._lastPoint=null;if(a){var b=this._stroke,c=this.endStrokeFn,d=this.context,g=this.changed;setTimeout(function(){c.call(d,b);g.call(d)},3);return!0}return null}}function n(a,b,c,g){if("ratio"===b||"%"===b.split("")[b.length-1])this.eventTokens[c+
".parentresized"]=d.subscribe(e+".parentresized",function(a,c,k,n){return function(){var n=c.width();if(n!==k){for(var f in a)a.hasOwnProperty(f)&&(d.unsubscribe(a[f]),delete a[f]);var z=b.settings;b.$parent.children().remove();for(f in b)b.hasOwnProperty(f)&&delete b[f];f=z.data;var n=1*n/k,t=[],g,h,l,m,p,r;h=0;for(l=f.length;h<l;h++){r=f[h];g={x:[],y:[]};m=0;for(p=r.x.length;m<p;m++)g.x.push(r.x[m]*n),g.y.push(r.y[m]*n);t.push(g)}z.data=t;c[e](z)}}}(this.eventTokens,this.$parent,this.$parent.width(), ".parentresized"]=g.subscribe(c+".parentresized",function(b,m,e,l){return function(){var d=m.width();if(d!==e){for(var l in b)b.hasOwnProperty(l)&&(g.unsubscribe(b[l]),delete b[l]);var f=a.settings;a.$parent.children().remove();for(l in a)a.hasOwnProperty(l)&&delete a[l];l=f.data;d=1*d/e;var r=[],D,E;var h=0;for(D=l.length;h<D;h++){var k=l[h];var n={x:[],y:[]};var p=0;for(E=k.x.length;p<E;p++)n.x.push(k.x[p]*d),n.y.push(k.y[p]*d);r.push(n)}f.data=r;m[c](f)}}}(this.eventTokens,this.$parent,this.$parent.width(),
1*this.canvas.width/this.canvas.height))}function B(b,a,e){var d=this.$parent=$(b);b=this.eventTokens={};this.events=new w(this);var c=$.fn[g]("globalEvents"),f={width:"ratio",height:"ratio",sizeRatio:4,color:"#000","background-color":"#fff","decor-color":"#eee",lineWidth:0,minFatFingerCompensation:-10,showUndoButton:!1,readOnly:!1,data:[]};$.extend(f,r(d));a&&$.extend(f,a);this.settings=f;for(var k in e)e.hasOwnProperty(k)&&e[k].call(this,k);this.events.publish(g+".initializing");this.$controlbarUpper= 1*this.canvas.width/this.canvas.height))}function w(a,b,c){var g=this.$parent=$(a);a=this.eventTokens={};this.events=new u(this);var d=$.fn.jSignature("globalEvents"),e={width:"ratio",height:"ratio",sizeRatio:4,color:"#000","background-color":"#fff","decor-color":"#eee",lineWidth:0,minFatFingerCompensation:-10,showUndoButton:!1,readOnly:!1,data:[],signatureLine:!1};$.extend(e,q(g));b&&$.extend(e,b);this.settings=e;for(var f in c)c.hasOwnProperty(f)&&c[f].call(this,f);this.events.publish("jSignature.initializing");
$('<div style="padding:0 !important; margin:0 !important;width: 100% !important; height: 0 !important; -ms-touch-action: none;margin-top:-1em !important; margin-bottom:1em !important;"></div>').appendTo(d);this.isCanvasEmulator=!1;a=this.canvas=this.initializeCanvas(f);e=$(a);this.$controlbarLower=$('<div style="padding:0 !important; margin:0 !important;width: 100% !important; height: 0 !important; -ms-touch-action: none;margin-top:-1.5em !important; margin-bottom:1.5em !important; position: relative;"></div>').appendTo(d); this.$controlbarUpper=$('<div style="padding:0 !important; margin:0 !important;width: 100% !important; height: 0 !important; -ms-touch-action: none; touch-action: none;margin-top:-1em !important; margin-bottom:1em !important;"></div>').appendTo(g);this.isCanvasEmulator=!1;b=this.canvas=this.initializeCanvas(e);c=$(b);this.$controlbarLower=$('<div style="padding:0 !important; margin:0 !important;width: 100% !important; height: 0 !important; -ms-touch-action: none; touch-action: none;margin-top:-1.5em !important; margin-bottom:1.5em !important; position: relative;"></div>').appendTo(g);
this.canvasContext=a.getContext("2d");e.data(g+".this",this);f.lineWidth=f.lineWidth?f.lineWidth:Math.max(Math.round(a.width/400),2);this.lineCurveThreshold=3*f.lineWidth;f.cssclass&&""!=$.trim(f.cssclass)&&e.addClass(f.cssclass);this.fatFingerCompensation=0;d=function(a){var b,e,d=function(d){d=d.changedTouches&&0<d.changedTouches.length?d.changedTouches[0]:d;return new h(Math.round(d.pageX+b),Math.round(d.pageY+e)+a.fatFingerCompensation)},c=new v(750,function(){a.dataEngine.endStroke()});this.drawEndHandler= this.canvasContext=b.getContext("2d");c.data("jSignature.this",this);e.lineWidth=function(a,b){return a?a:Math.max(Math.round(b/400),2)}(e.lineWidth,b.width);this.lineCurveThreshold=3*e.lineWidth;e.cssclass&&""!=$.trim(e.cssclass)&&c.addClass(e.cssclass);this.fatFingerCompensation=0;g=function(a){var b,c,d=function(d){d=d.changedTouches&&0<d.changedTouches.length?d.changedTouches[0]:d;return new h(Math.round(d.pageX+b),Math.round(d.pageY+c)+a.fatFingerCompensation)},g=new v(750,function(){a.dataEngine.endStroke()});
function(b){if(!a.settings.readOnly){try{b.preventDefault()}catch(e){}c.clear();a.dataEngine.endStroke()}};this.drawStartHandler=function(f){if(!a.settings.readOnly){f.preventDefault();var k=$(a.canvas).offset();b=-1*k.left;e=-1*k.top;a.dataEngine.startStroke(d(f));c.kick()}};this.drawMoveHandler=function(b){a.settings.readOnly||(b.preventDefault(),a.dataEngine.inStroke&&(a.dataEngine.addToStroke(d(b)),c.kick()))};return this}.call({},this);(function(a,b,e){var d=this.canvas,c=$(d);this.isCanvasEmulator? this.drawEndHandler=function(b){if(!a.settings.readOnly){try{b.preventDefault()}catch(A){}g.clear();a.dataEngine.endStroke()}};this.drawStartHandler=function(e){if(!a.settings.readOnly){e.preventDefault();var m=$(a.canvas).offset();b=-1*m.left;c=-1*m.top;a.dataEngine.startStroke(d(e));g.kick()}};this.drawMoveHandler=function(b){a.settings.readOnly||(b.preventDefault(),a.dataEngine.inStroke&&(a.dataEngine.addToStroke(d(b)),g.kick()))};return this}.call({},this);(function(a,b,c){var d=this.canvas,g=
(c.bind("mousemove."+g,e),c.bind("mouseup."+g,a),c.bind("mousedown."+g,b)):(d.ontouchstart=function(c){d.onmousedown=d.onmouseup=d.onmousemove=void 0;this.fatFingerCompensation=f.minFatFingerCompensation&&-3*f.lineWidth>f.minFatFingerCompensation?-3*f.lineWidth:f.minFatFingerCompensation;b(c);d.ontouchend=a;d.ontouchstart=b;d.ontouchmove=e},d.onmousedown=function(c){d.ontouchstart=d.ontouchend=d.ontouchmove=void 0;b(c);d.onmousedown=b;d.onmouseup=a;d.onmousemove=e},window.navigator.msPointerEnabled&& $(d);if(this.isCanvasEmulator)g.bind("mousemove.jSignature",c),g.bind("mouseup.jSignature",a),g.bind("mousedown.jSignature",b);else{var m="function"===typeof d.addEventListener;this.ontouchstart=function(g){d.onmousedown=d.onmouseup=d.onmousemove=void 0;this.fatFingerCompensation=e.minFatFingerCompensation&&-3*e.lineWidth>e.minFatFingerCompensation?-3*e.lineWidth:e.minFatFingerCompensation;b(g);m?(d.addEventListener("touchend",a),d.addEventListener("touchstart",b),d.addEventListener("touchmove",c)):
(d.onmspointerdown=b,d.onmspointerup=a,d.onmspointermove=e))}).call(this,d.drawEndHandler,d.drawStartHandler,d.drawMoveHandler);b[g+".windowmouseup"]=c.subscribe(g+".windowmouseup",d.drawEndHandler);this.events.publish(g+".attachingEventHandlers");m.call(this,this,f.width.toString(10),g,c);this.resetCanvas(f.data);this.events.publish(g+".initialized");return this}function u(b){if(b.getContext)return!1;var a=b.ownerDocument.parentWindow,e=a.FlashCanvas?b.ownerDocument.parentWindow.FlashCanvas:"undefined"=== (d.ontouchend=a,d.ontouchstart=b,d.ontouchmove=c)};m?d.addEventListener("touchstart",this.ontouchstart):d.ontouchstart=ontouchstart;d.onmousedown=function(g){m?d.removeEventListener("touchstart",this.ontouchstart):d.ontouchstart=d.ontouchend=d.ontouchmove=void 0;b(g);d.onmousedown=b;d.onmouseup=a;d.onmousemove=c};window.navigator.msPointerEnabled&&(d.onmspointerdown=b,d.onmspointerup=a,d.onmspointermove=c)}}).call(this,g.drawEndHandler,g.drawStartHandler,g.drawMoveHandler);a["jSignature.windowmouseup"]=
typeof FlashCanvas?void 0:FlashCanvas;if(e){b=e.initElement(b);e=1;a&&(a.screen&&a.screen.deviceXDPI&&a.screen.logicalXDPI)&&(e=1*a.screen.deviceXDPI/a.screen.logicalXDPI);if(1!==e)try{$(b).children("object").get(0).resize(Math.ceil(b.width*e),Math.ceil(b.height*e)),b.getContext("2d").scale(e,e)}catch(d){}return!0}throw Error("Canvas element does not support 2d context. jSignature cannot proceed.");}var g="jSignature",v=function(b,a){var e;this.kick=function(){clearTimeout(e);e=setTimeout(a,b)};this.clear= d.subscribe("jSignature.windowmouseup",g.drawEndHandler);this.events.publish("jSignature.attachingEventHandlers");n.call(this,this,e.width.toString(10),"jSignature",d);this.resetCanvas(e.data);this.events.publish("jSignature.initialized");return this}function x(a){if(a.getContext)return!1;var b=a.ownerDocument.parentWindow,c=b.FlashCanvas?a.ownerDocument.parentWindow.FlashCanvas:"undefined"===typeof FlashCanvas?void 0:FlashCanvas;if(c){a=c.initElement(a);c=1;b&&b.screen&&b.screen.deviceXDPI&&b.screen.logicalXDPI&&
function(){clearTimeout(e)};return this},w=function(b){this.topics={};this.context=b?b:this;this.publish=function(a,b,d,c){if(this.topics[a]){var f=this.topics[a],k=Array.prototype.slice.call(arguments,1),n=[],g,h,z,t;h=0;for(z=f.length;h<z;h++)t=f[h],g=t[0],t[1]&&(t[0]=function(){},n.push(h)),g.apply(this.context,k);h=0;for(z=n.length;h<z;h++)f.splice(n[h],1)}};this.subscribe=function(a,b,d){this.topics[a]?this.topics[a].push([b,d]):this.topics[a]=[[b,d]];return{topic:a,callback:b}};this.unsubscribe= (c=1*b.screen.deviceXDPI/b.screen.logicalXDPI);if(1!==c)try{$(a).children("object").get(0).resize(Math.ceil(a.width*c),Math.ceil(a.height*c)),a.getContext("2d").scale(c,c)}catch(g){}return!0}throw Error("Canvas element does not support 2d context. jSignature cannot proceed.");}var v=function(a,b){var c;this.kick=function(){clearTimeout(c);c=setTimeout(b,a)};this.clear=function(){clearTimeout(c)};return this},u=function(a){this.topics={};this.context=a?a:this;this.publish=function(a,c,g,d){if(this.topics[a]){var b=
function(a){if(this.topics[a.topic])for(var b=this.topics[a.topic],d=0,c=b.length;d<c;d++)b[d]&&b[d][0]===a.callback&&b.splice(d,1)}},x=function(b,a,e,d,c){b.beginPath();b.moveTo(a,e);b.lineTo(d,c);b.stroke()},y=function(b){var a=this.canvasContext,e=b.x[0];b=b.y[0];var d=this.settings.lineWidth,c=a.fillStyle;a.fillStyle=a.strokeStyle;a.fillRect(e+d/-2,b+d/-2,d,d);a.fillStyle=c},c=function(b,a){var e=new h(b.x[a-1],b.y[a-1]),d=new h(b.x[a],b.y[a]),c=e.getVectorToPoint(d);if(1<a){var f=new h(b.x[a- this.topics[a],e=Array.prototype.slice.call(arguments,1),f=[],h=[],t;var k=0;for(t=b.length;k<t;k++){var r=b[k];var D=r[0];r[1]&&(r[0]=function(){},f.push(k));h.push(D)}k=0;for(t=f.length;k<t;k++)b.splice(f[k],1);k=0;for(t=h.length;k<t;k++)h[k].apply(this.context,e)}};this.subscribe=function(a,c,g){this.topics[a]?this.topics[a].push([c,g]):this.topics[a]=[[c,g]];return{topic:a,callback:c}};this.unsubscribe=function(a){if(this.topics[a.topic])for(var b=this.topics[a.topic],g=0,d=b.length;g<d;g++)b[g]&&
2],b.y[a-2]),k=f.getVectorToPoint(e),g;if(k.getLength()>this.lineCurveThreshold){g=2<a?(new h(b.x[a-3],b.y[a-3])).getVectorToPoint(f):new l(0,0);var s=0.35*k.getLength(),m=k.angleTo(g.reverse()),z=c.angleTo(k.reverse());g=(new l(g.x+k.x,g.y+k.y)).resizeTo(Math.max(0.05,m)*s);var t=(new l(k.x+c.x,k.y+c.y)).reverse().resizeTo(Math.max(0.05,z)*s),k=this.canvasContext,s=f.x,z=f.y,m=e.x,F=e.y,p=f.x+g.x,f=f.y+g.y;g=e.x+t.x;t=e.y+t.y;k.beginPath();k.moveTo(s,z);k.bezierCurveTo(p,f,g,t,m,F);k.stroke()}}c.getLength()<= b[g][0]===a.callback&&b.splice(g,1)}},y=function(a,b,c,g,d){a.beginPath();a.moveTo(b,c);a.lineTo(g,d);a.closePath();a.stroke()},C=function(a){var b=this.canvasContext,c=a.x[0];a=a.y[0];var g=this.settings.lineWidth,d=b.fillStyle;b.fillStyle=b.strokeStyle;b.fillRect(c+g/-2,a+g/-2,g,g);b.fillStyle=d},f=function(a,b){var c=new h(a.x[b-1],a.y[b-1]),g=new h(a.x[b],a.y[b]),d=c.getVectorToPoint(g);if(1<b){var e=new h(a.x[b-2],a.y[b-2]),f=e.getVectorToPoint(c);if(f.getLength()>this.lineCurveThreshold){var l=
this.lineCurveThreshold&&x(this.canvasContext,e.x,e.y,d.x,d.y)},f=function(b){var a=b.x.length-1;if(0<a){var e=new h(b.x[a],b.y[a]),d=new h(b.x[a-1],b.y[a-1]),c=d.getVectorToPoint(e);if(c.getLength()>this.lineCurveThreshold)if(1<a){b=(new h(b.x[a-2],b.y[a-2])).getVectorToPoint(d);var f=(new l(b.x+c.x,b.y+c.y)).resizeTo(c.getLength()/2),c=this.canvasContext;b=d.x;var a=d.y,k=e.x,g=e.y,s=d.x+f.x,d=d.y+f.y,f=e.x,e=e.y;c.beginPath();c.moveTo(b,a);c.bezierCurveTo(s,d,f,e,k,g);c.stroke()}else x(this.canvasContext, 2<b?(new h(a.x[b-3],a.y[b-3])).getVectorToPoint(e):new k(0,0);var n=.35*f.getLength(),t=f.angleTo(l.reverse()),p=d.angleTo(f.reverse());l=(new k(l.x+f.x,l.y+f.y)).resizeTo(Math.max(.05,t)*n);var r=(new k(f.x+d.x,f.y+d.y)).reverse().resizeTo(Math.max(.05,p)*n);f=this.canvasContext;n=e.x;p=e.y;t=c.x;var D=c.y,A=e.x+l.x;e=e.y+l.y;l=c.x+r.x;r=c.y+r.y;f.beginPath();f.moveTo(n,p);f.bezierCurveTo(A,e,l,r,t,D);f.closePath();f.stroke()}}d.getLength()<=this.lineCurveThreshold&&y(this.canvasContext,c.x,c.y,
d.x,d.y,e.x,e.y)}};B.prototype.resetCanvas=function(b,a){var e=this.canvas,d=this.settings,q=this.canvasContext,h=this.isCanvasEmulator,k=e.width,n=e.height;a||q.clearRect(0,0,k+30,n+30);q.shadowColor=q.fillStyle=d["background-color"];h&&q.fillRect(0,0,k+30,n+30);q.lineWidth=Math.ceil(parseInt(d.lineWidth,10));q.lineCap=q.lineJoin="round";if(null!=d["decor-color"]){q.strokeStyle=d["decor-color"];q.shadowOffsetX=0;q.shadowOffsetY=0;var s=Math.round(n/5);x(q,1.5*s,n-s,k-1.5*s,n-s)}q.strokeStyle=d.color; g.x,g.y)},e=function(a){var b=a.x.length-1;if(0<b){var c=new h(a.x[b],a.y[b]),e=new h(a.x[b-1],a.y[b-1]),d=e.getVectorToPoint(c);if(d.getLength()>this.lineCurveThreshold)if(1<b){a=(new h(a.x[b-2],a.y[b-2])).getVectorToPoint(e);var f=(new k(a.x+d.x,a.y+d.y)).resizeTo(d.getLength()/2);d=this.canvasContext;a=e.x;b=e.y;var E=c.x,l=c.y,n=e.x+f.x;e=e.y+f.y;f=c.x;c=c.y;d.beginPath();d.moveTo(a,b);d.bezierCurveTo(n,e,f,c,E,l);d.closePath();d.stroke()}else y(this.canvasContext,e.x,e.y,c.x,c.y)}};w.prototype.resetCanvas=
h||(q.shadowColor=q.strokeStyle,q.shadowOffsetX=0.5*q.lineWidth,q.shadowOffsetY=-0.6*q.lineWidth,q.shadowBlur=0);b||(b=[]);q=this.dataEngine=new p(b,this,y,c,f);d.data=b;$(e).data(g+".data",b).data(g+".settings",d);q.changed=function(a,b,d){return function(){b.publish(d+".change");a.trigger("change")}}(this.$parent,this.events,g);q.changed();return!0};B.prototype.initializeCanvas=function(b){var a=document.createElement("canvas"),e=$(a);b.width===b.height&&"ratio"===b.height&&(b.width="100%");e.css("margin", function(a,b){var c=this.canvas,g=this.settings,d=this.canvasContext,m=this.isCanvasEmulator,h=c.width,l=c.height;b||d.clearRect(0,0,h+30,l+30);d.shadowColor=d.fillStyle=g["background-color"];m&&d.fillRect(0,0,h+30,l+30);d.lineWidth=Math.ceil(parseInt(g.lineWidth,10));d.lineCap=d.lineJoin="round";if(g.signatureLine){if(null!=g["decor-color"]){d.strokeStyle=g["decor-color"];d.shadowOffsetX=0;d.shadowOffsetY=0;var k=Math.round(l/5);y(d,1.5*k,l-k,h-1.5*k,l-k)}m||(d.shadowColor=d.strokeStyle,d.shadowOffsetX=
0).css("padding",0).css("border","none").css("height","ratio"!==b.height&&b.height?b.height.toString(10):1).css("width","ratio"!==b.width&&b.width?b.width.toString(10):1).css("-ms-touch-action","none");e.appendTo(this.$parent);"ratio"===b.height?e.css("height",Math.round(e.width()/b.sizeRatio)):"ratio"===b.width&&e.css("width",Math.round(e.height()*b.sizeRatio));e.addClass(g);a.width=e.width();a.height=e.height();this.isCanvasEmulator=u(a);a.onselectstart=function(a){a&&a.preventDefault&&a.preventDefault(); .5*d.lineWidth,d.shadowOffsetY=-.6*d.lineWidth,d.shadowBlur=0)}d.strokeStyle=g.color;a||(a=[]);d=this.dataEngine=new p(a,this,C,f,e);g.data=a;$(c).data("jSignature.data",a).data("jSignature.settings",g);d.changed=function(a,b,d){return function(){b.publish(d+".change");a.trigger("change")}}(this.$parent,this.events,"jSignature");d.changed();return!0};w.prototype.initializeCanvas=function(a){var b=document.createElement("canvas"),c=$(b);a.width===a.height&&"ratio"===a.height&&(a.width="100%");c.css({margin:0,
a&&a.stopPropagation&&a.stopPropagation();return!1};return a};(function(b){function a(a,b,d){var c=new Image,e=this;c.onload=function(){e.getContext("2d").drawImage(c,0,0,c.width<e.width?c.width:e.width,c.height<e.height?c.height:e.height)};c.src="data:"+b+","+a}function e(a,b){this.find("canvas."+g).add(this.filter("canvas."+g)).data(g+".this").resetCanvas(a,b);return this}function d(a,b){if(void 0===b&&("string"===typeof a&&"data:"===a.substr(0,5))&&(b=a.slice(5).split(",")[0],a=a.slice(6+b.length), padding:0,border:"none",height:"ratio"!==a.height&&a.height?a.height.toString(10):1,width:"ratio"!==a.width&&a.width?a.width.toString(10):1,"-ms-touch-action":"none","touch-action":"none","background-color":a["background-color"]});c.appendTo(this.$parent);"ratio"===a.height?c.css("height",Math.round(c.width()/a.sizeRatio)):"ratio"===a.width&&c.css("width",Math.round(c.height()*a.sizeRatio));c.addClass("jSignature");b.width=c.width();b.height=c.height();this.isCanvasEmulator=x(b);b.onselectstart=function(a){a&&
b===a))return;var c=this.find("canvas."+g).add(this.filter("canvas."+g));if(h.hasOwnProperty(b))0!==c.length&&h[b].call(c[0],a,b,function(a){return function(){return a.resetCanvas.apply(a,arguments)}}(c.data(g+".this")));else throw Error(g+" is unable to find import plugin with for format '"+String(b)+"'");return this}var c=new w;(function(a,b,c,e){var d,f=function(){a.publish(b+".parentresized")};c(e).bind("resize."+b,function(){d&&clearTimeout(d);d=setTimeout(f,500)}).bind("mouseup."+b,function(c){a.publish(b+ a.preventDefault&&a.preventDefault();a&&a.stopPropagation&&a.stopPropagation();return!1};return b};(function(a){function b(a,b,d){var c=new Image,e=this;c.onload=function(){var a=e.getContext("2d"),b=a.shadowColor;a.shadowColor="transparent";a.drawImage(c,0,0,c.width<e.width?c.width:e.width,c.height<e.height?c.height:e.height);a.shadowColor=b};c.src="data:"+b+","+a}function c(a,b){this.find("canvas.jSignature").add(this.filter("canvas.jSignature")).data("jSignature.this").resetCanvas(a,b);return this}
".windowmouseup")})})(c,g,$,b);var f={},k={"default":function(a){return this.toDataURL()},"native":function(a){return a},image:function(a){a=this.toDataURL();if("string"===typeof a&&4<a.length&&"data:"===a.slice(0,5)&&-1!==a.indexOf(",")){var b=a.indexOf(",");return[a.slice(5,b),a.substr(b+1)]}return[]}},h={"native":function(a,b,c){c(a)},image:a,"image/png;base64":a,"image/jpeg;base64":a,"image/jpg;base64":a},s={"export":k,"import":h,instance:f},m={init:function(a){return this.each(function(){var b, function e(a,b){if(void 0===b&&"string"===typeof a&&"data:"===a.substr(0,5)&&(b=a.slice(5).split(",")[0],a=a.slice(6+b.length),b===a))return;var c=this.find("canvas.jSignature").add(this.filter("canvas.jSignature"));if(l.hasOwnProperty(b))0!==c.length&&l[b].call(c[0],a,b,function(a){return function(){return a.resetCanvas.apply(a,arguments)}}(c.data("jSignature.this")));else throw Error("jSignature is unable to find import plugin with for format '"+String(b)+"'");return this}var d=new u;(function(a,
c=!1;for(b=this.parentNode;b&&!c;)c=b.body,b=b.parentNode;c&&new B(this,a,f)})},getSettings:function(){return this.find("canvas."+g).add(this.filter("canvas."+g)).data(g+".this").settings},isModified:function(){return null!==this.find("canvas."+g).add(this.filter("canvas."+g)).data(g+".this").dataEngine._stroke},updateSetting:function(a,b,c){var e=this.find("canvas."+g).add(this.filter("canvas."+g)).data(g+".this");e.settings[a]=b;e.resetCanvas(c?null:e.settings.data,!0);return e.settings[a]},clear:e, b,c,d){var e,g=function(){a.publish(b+".parentresized")};c(d).bind("resize."+b,function(){e&&clearTimeout(e);e=setTimeout(g,500)}).bind("mouseup."+b,function(c){a.publish(b+".windowmouseup")})})(d,"jSignature",$,a);var f={},h={"default":function(a){return this.toDataURL()},"native":function(a){return a},image:function(a){a=this.toDataURL();if("string"===typeof a&&4<a.length&&"data:"===a.slice(0,5)&&-1!==a.indexOf(",")){var b=a.indexOf(",");return[a.slice(5,b),a.substr(b+1)]}return[]}},l={"native":function(a,
reset:e,addPlugin:function(a,b,c){s.hasOwnProperty(a)&&(s[a][b]=c);return this},listPlugins:function(a){var b=[];if(s.hasOwnProperty(a)){a=s[a];for(var c in a)a.hasOwnProperty(c)&&b.push(c)}return b},getData:function(a){var b=this.find("canvas."+g).add(this.filter("canvas."+g));void 0===a&&(a="default");if(0!==b.length&&k.hasOwnProperty(a))return k[a].call(b.get(0),b.data(g+".data"))},importData:d,setData:d,globalEvents:function(){return c},disable:function(){this.find("input").attr("disabled",1); b,c){c(a)},image:b,"image/png;base64":b,"image/jpeg;base64":b,"image/jpg;base64":b},k=function(a){var b=!1;for(a=a.parentNode;a&&!b;)b=a.body,a=a.parentNode;return!b},n={"export":h,"import":l,instance:f},p={init:function(a){return this.each(function(){k(this)||new w(this,a,f)})},destroy:function(){return this.each(function(){if(!k(this)){var a=$(this).find("canvas").data("jSignature.this");if(a){a.$controlbarLower.remove();a.$controlbarUpper.remove();$(a.canvas).remove();for(var b in a.eventTokens)a.eventTokens.hasOwnProperty(b)&&
this.find("canvas."+g).addClass("disabled").data(g+".this").settings.readOnly=!0},enable:function(){this.find("input").removeAttr("disabled");this.find("canvas."+g).removeClass("disabled").data(g+".this").settings.readOnly=!1},events:function(){return this.find("canvas."+g).add(this.filter("canvas."+g)).data(g+".this").events}};$.fn[g]=function(a){if(a&&"object"!==typeof a){if("string"===typeof a&&m[a])return m[a].apply(this,Array.prototype.slice.call(arguments,1));$.error("Method "+String(a)+" does not exist on jQuery."+ d.unsubscribe(a.eventTokens[b])}}})},getSettings:function(){return this.find("canvas.jSignature").add(this.filter("canvas.jSignature")).data("jSignature.this").settings},isModified:function(){return null!==this.find("canvas.jSignature").add(this.filter("canvas.jSignature")).data("jSignature.this").dataEngine._stroke},updateSetting:function(a,b,c){var d=this.find("canvas.jSignature").add(this.filter("canvas.jSignature")).data("jSignature.this");d.settings[a]=b;d.resetCanvas(c?null:d.settings.data,
g)}else return m.init.apply(this,arguments)}})(window)})(); !0);return d.settings[a]},clear:c,reset:c,addPlugin:function(a,b,c){n.hasOwnProperty(a)&&(n[a][b]=c);return this},listPlugins:function(a){var b=[];if(n.hasOwnProperty(a)){a=n[a];for(var c in a)a.hasOwnProperty(c)&&b.push(c)}return b},getData:function(a){var b=this.find("canvas.jSignature").add(this.filter("canvas.jSignature"));void 0===a&&(a="default");if(0!==b.length&&h.hasOwnProperty(a))return h[a].call(b.get(0),b.data("jSignature.data"),b.data("jSignature.settings"))},importData:e,setData:e,globalEvents:function(){return d},
(function(){function r(l,h,p){l=l.call(this);(function(h,l,p){h.events.subscribe(p+".change",function(){h.dataEngine.data.length?l.show():l.hide()})})(this,l,h);(function(h,l,p){var g=p+".undo";l.bind("click",function(){h.events.publish(g)});h.events.subscribe(g,function(){var g=h.dataEngine.data;g.length&&(g.pop(),h.resetCanvas(g))})})(this,l,this.events.topics.hasOwnProperty(h+".undo")?p:h)}$.fn.jSignature("addPlugin","instance","UndoButton",function(l){this.events.subscribe("jSignature.attachingEventHandlers", disable:function(){this.find("input").attr("disabled",1);this.find("canvas.jSignature").addClass("disabled").data("jSignature.this").settings.readOnly=!0},enable:function(){this.find("input").removeAttr("disabled");this.find("canvas.jSignature").removeClass("disabled").data("jSignature.this").settings.readOnly=!1},events:function(){return this.find("canvas.jSignature").add(this.filter("canvas.jSignature")).data("jSignature.this").events}};$.fn.jSignature=function(a){if(a&&"object"!==typeof a){if("string"===
function(){if(this.settings[l]){var h=this.settings[l];"function"!==typeof h&&(h=function(){var h=$('<input type="button" value="Undo last stroke" style="position:absolute;display:none;margin:0 !important;top:auto" />').appendTo(this.$controlbarLower),l=h.width();h.css("left",Math.round((this.canvas.width-l)/2));l!==h.width()&&h.width(l);return h});r.call(this,h,"jSignature",l)}})})})(); typeof a&&p[a])return p[a].apply(this,Array.prototype.slice.call(arguments,1));$.error("Method "+String(a)+" does not exist on jQuery.jSignature")}else return p.init.apply(this,arguments)}})(window)})();
(function(){for(var r={},l={},h="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX".split(""),p=h.length/2,m=p-1;-1<m;m--)r[h[m]]=h[m+p],l[h[m+p]]=h[m];var B=function(c){c=c.split("");for(var f=c.length,b=1;b<f;b++)c[b]=r[c[b]];return c.join("")},u=function(c){for(var f=[],b=0,a=1,e=c.length,d,g,h=0;h<e;h++)d=Math.round(c[h]),g=d-b,b=d,0>g&&0<a?(a=-1,f.push("Z")):0<g&&0>a&&(a=1,f.push("Y")),d=Math.abs(g),d>=p?f.push(B(d.toString(p))):f.push(d.toString(p));return f.join("")},g=function(c){var f= (function(){function q(k,h,p){k=k.call(this);(function(h,k,p){h.events.subscribe(p+".change",function(){h.dataEngine.data.length?k.show():k.hide()})})(this,k,h);(function(h,k,p){var n=p+".undo";k.bind("click",function(){h.events.publish(n)});h.events.subscribe(n,function(){var k=h.dataEngine.data;k.length&&(k.pop(),h.resetCanvas(k))})})(this,k,this.events.topics.hasOwnProperty(h+".undo")?p:h)}$.fn.jSignature("addPlugin","instance","UndoButton",function(k){this.events.subscribe("jSignature.attachingEventHandlers",
[];c=c.split("");for(var b=c.length,a,e=1,d=[],g=0,h=0;h<b;h++)a=c[h],a in r||"Z"===a||"Y"===a?(0!==d.length&&(d=parseInt(d.join(""),p)*e+g,f.push(d),g=d),"Z"===a?(e=-1,d=[]):"Y"===a?(e=1,d=[]):d=[a]):d.push(l[a]);f.push(parseInt(d.join(""),p)*e+g);return f},v=function(c){for(var f=[],b=c.length,a,e=0;e<b;e++)a=c[e],f.push(u(a.x)),f.push(u(a.y));return f.join("_")},w=function(c){var f=[];c=c.split("_");for(var b=c.length/2,a=0;a<b;a++)f.push({x:g(c[2*a]),y:g(c[2*a+1])});return f},x=function(c){return["image/jsignature;base30", function(){if(this.settings[k]){var h=this.settings[k];"function"!==typeof h&&(h=function(){var h=$('<input type="button" value="Undo last stroke" style="position:absolute;display:none;margin:0 !important;top:auto" />').appendTo(this.$controlbarLower),k=h.width();h.css("left",Math.round((this.canvas.width-k)/2));k!==h.width()&&h.width(k);return h});q.call(this,h,"jSignature",k)}})})})();
v(c)]},y=function(c,f,b){"string"===typeof c&&("image/jsignature;base30"===c.substring(0,23).toLowerCase()&&(c=c.substring(24)),b(w(c)))};if(null==this.jQuery)throw Error("We need jQuery for some of the functionality. jQuery is not detected. Failing to initialize...");(function(c){c=c.fn.jSignature;c("addPlugin","export","base30",x);c("addPlugin","export","image/jsignature;base30",x);c("addPlugin","import","base30",y);c("addPlugin","import","image/jsignature;base30",y)})(this.jQuery);this.jSignatureDebug&& (function(){for(var q={},k={},h="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX".split(""),p=h.length/2,n=p-1;-1<n;n--)q[h[n]]=h[n+p],k[h[n+p]]=h[n];var w=function(e){e=e.split("");for(var a=e.length,b=1;b<a;b++)e[b]=q[e[b]];return e.join("")},x=function(e){for(var a=[],b=0,c=1,g=e.length,d,f,h=0;h<g;h++)d=Math.round(e[h]),f=d-b,b=d,0>f&&0<c?(c=-1,a.push("Z")):0<f&&0>c&&(c=1,a.push("Y")),d=Math.abs(f),d>=p?a.push(w(d.toString(p))):a.push(d.toString(p));return a.join("")},v=function(e){var a=
(this.jSignatureDebug.base30={remapTailChars:B,compressstrokeleg:u,uncompressstrokeleg:g,compressstrokes:v,uncompressstrokes:w,charmap:r})}).call("undefined"!==typeof window?window:this); [];e=e.split("");for(var b=e.length,c,g=1,d=[],f=0,h=0;h<b;h++)c=e[h],c in q||"Z"===c||"Y"===c?(0!==d.length&&(d=parseInt(d.join(""),p)*g+f,a.push(d),f=d),"Z"===c?(g=-1,d=[]):"Y"===c?(g=1,d=[]):d=[c]):d.push(k[c]);a.push(parseInt(d.join(""),p)*g+f);return a},u=function(e){for(var a=[],b=e.length,c,g=0;g<b;g++)c=e[g],a.push(x(c.x)),a.push(x(c.y));return a.join("_")},y=function(e){var a=[];e=e.split("_");for(var b=e.length/2,c=0;c<b;c++)a.push({x:v(e[2*c]),y:v(e[2*c+1])});return a},C=function(e){return["image/jsignature;base30",
(function(){function r(c,f){this.x=c;this.y=f;this.reverse=function(){return new this.constructor(-1*this.x,-1*this.y)};this._length=null;this.getLength=function(){this._length||(this._length=Math.sqrt(Math.pow(this.x,2)+Math.pow(this.y,2)));return this._length};var b=function(a){return Math.round(a/Math.abs(a))};this.resizeTo=function(a){if(0===this.x&&0===this.y)this._length=0;else if(0===this.x)this._length=a,this.y=a*b(this.y);else if(0===this.y)this._length=a,this.x=a*b(this.x);else{var c=Math.abs(this.y/ u(e)]},f=function(e,a,b){"string"===typeof e&&("image/jsignature;base30"===e.substring(0,23).toLowerCase()&&(e=e.substring(24)),b(y(e)))};if(null==this.jQuery)throw Error("We need jQuery for some of the functionality. jQuery is not detected. Failing to initialize...");(function(e){e=e.fn.jSignature;e("addPlugin","export","base30",C);e("addPlugin","export","image/jsignature;base30",C);e("addPlugin","import","base30",f);e("addPlugin","import","image/jsignature;base30",f)})(this.jQuery);this.jSignatureDebug&&
this.x),d=Math.sqrt(Math.pow(a,2)/(1+Math.pow(c,2))),c=c*d;this._length=a;this.x=d*b(this.x);this.y=c*b(this.y)}return this};this.angleTo=function(a){var b=this.getLength()*a.getLength();return 0===b?0:Math.acos(Math.min(Math.max((this.x*a.x+this.y*a.y)/b,-1),1))/Math.PI}}function l(c,f){this.x=c;this.y=f;this.getVectorToCoordinates=function(b,a){return new r(b-this.x,a-this.y)};this.getVectorFromCoordinates=function(b,a){return this.getVectorToCoordinates(b,a).reverse()};this.getVectorToPoint=function(b){return new r(b.x- (this.jSignatureDebug.base30={remapTailChars:w,compressstrokeleg:x,uncompressstrokeleg:v,compressstrokes:u,uncompressstrokes:y,charmap:q})}).call("undefined"!==typeof window?window:this);
this.x,b.y-this.y)};this.getVectorFromPoint=function(b){return this.getVectorToPoint(b).reverse()}}function h(c,f){var b=Math.pow(10,f);return Math.round(c*b)/b}function p(c,f,b){f+=1;var a=new l(c.x[f-1],c.y[f-1]),e=new l(c.x[f],c.y[f]),e=a.getVectorToPoint(e),d=new l(c.x[f-2],c.y[f-2]),a=d.getVectorToPoint(a);return a.getLength()>b?(b=2<f?(new l(c.x[f-3],c.y[f-3])).getVectorToPoint(d):new r(0,0),c=0.35*a.getLength(),d=a.angleTo(b.reverse()),f=e.angleTo(a.reverse()),b=(new r(b.x+a.x,b.y+a.y)).resizeTo(Math.max(0.05, (function(){function q(f,e){this.x=f;this.y=e;this.reverse=function(){return new this.constructor(-1*this.x,-1*this.y)};this._length=null;this.getLength=function(){this._length||(this._length=Math.sqrt(Math.pow(this.x,2)+Math.pow(this.y,2)));return this._length};var a=function(a){return Math.round(a/Math.abs(a))};this.resizeTo=function(b){if(0===this.x&&0===this.y)this._length=0;else if(0===this.x)this._length=b,this.y=b*a(this.y);else if(0===this.y)this._length=b,this.x=b*a(this.x);else{var c=Math.abs(this.y/
d)*c),e=(new r(a.x+e.x,a.y+e.y)).reverse().resizeTo(Math.max(0.05,f)*c),e=new r(a.x+e.x,a.y+e.y),["c",h(b.x,2),h(b.y,2),h(e.x,2),h(e.y,2),h(a.x,2),h(a.y,2)]):["l",h(a.x,2),h(a.y,2)]}function m(c,f){var b=c.x.length-1,a=new l(c.x[b],c.y[b]),e=new l(c.x[b-1],c.y[b-1]),a=e.getVectorToPoint(a);if(1<b&&a.getLength()>f){var b=(new l(c.x[b-2],c.y[b-2])).getVectorToPoint(e),e=a.angleTo(b.reverse()),d=0.35*a.getLength(),b=(new r(b.x+a.x,b.y+a.y)).resizeTo(Math.max(0.05,e)*d);return["c",h(b.x,2),h(b.y,2),h(a.x, this.x),e=Math.sqrt(Math.pow(b,2)/(1+Math.pow(c,2)));c*=e;this._length=b;this.x=e*a(this.x);this.y=c*a(this.y)}return this};this.angleTo=function(a){var b=this.getLength()*a.getLength();return 0===b?0:Math.acos(Math.min(Math.max((this.x*a.x+this.y*a.y)/b,-1),1))/Math.PI}}function k(f,e){this.x=f;this.y=e;this.getVectorToCoordinates=function(a,b){return new q(a-this.x,b-this.y)};this.getVectorFromCoordinates=function(a,b){return this.getVectorToCoordinates(a,b).reverse()};this.getVectorToPoint=function(a){return new q(a.x-
2),h(a.y,2),h(a.x,2),h(a.y,2)]}return["l",h(a.x,2),h(a.y,2)]}function B(c,f,b){f=["M",h(c.x[0]-f,2),h(c.y[0]-b,2)];b=1;for(var a=c.x.length-1;b<a;b++)f.push.apply(f,p(c,b,1));0<a?f.push.apply(f,m(c,b,1)):0===a&&f.push.apply(f,["l",1,1]);return f.join(" ")}function u(c){var f=['<?xml version="1.0" encoding="UTF-8" standalone="no"?>','<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'],b,a=c.length,e,d=[],g=[],h=e=b=0,k=0,n=[];if(0!==a){for(b=0;b<a;b++){h= this.x,a.y-this.y)};this.getVectorFromPoint=function(a){return this.getVectorToPoint(a).reverse()}}function h(f,e){var a=Math.pow(10,e);return Math.round(f*a)/a}function p(f,e,a){e+=1;var b=new k(f.x[e-1],f.y[e-1]),c=new k(f.x[e],f.y[e]);c=b.getVectorToPoint(c);var g=new k(f.x[e-2],f.y[e-2]);b=g.getVectorToPoint(b);return b.getLength()>a?(a=2<e?(new k(f.x[e-3],f.y[e-3])).getVectorToPoint(g):new q(0,0),f=.35*b.getLength(),g=b.angleTo(a.reverse()),e=c.angleTo(b.reverse()),a=(new q(a.x+b.x,a.y+b.y)).resizeTo(Math.max(.05,
c[b];k=[];e={x:[],y:[]};for(var l=void 0,m=void 0,l=0,m=h.x.length;l<m;l++)k.push({x:h.x[l],y:h.y[l]});k=simplify(k,0.7,!0);l=0;for(m=k.length;l<m;l++)e.x.push(k[l].x),e.y.push(k[l].y);n.push(e);d=d.concat(e.x);g=g.concat(e.y)}c=Math.min.apply(null,d)-1;a=Math.max.apply(null,d)+1;d=Math.min.apply(null,g)-1;g=Math.max.apply(null,g)+1;h=0>c?0:c;k=0>d?0:d;b=a-c;e=g-d}f.push('<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="'+b.toString()+'" height="'+e.toString()+'">');b=0;for(a=n.length;b< g)*f),c=(new q(b.x+c.x,b.y+c.y)).reverse().resizeTo(Math.max(.05,e)*f),c=new q(b.x+c.x,b.y+c.y),["c",h(a.x,2),h(a.y,2),h(c.x,2),h(c.y,2),h(b.x,2),h(b.y,2)]):["l",h(b.x,2),h(b.y,2)]}function n(f,e){var a=f.x.length-1,b=new k(f.x[a],f.y[a]),c=new k(f.x[a-1],f.y[a-1]);b=c.getVectorToPoint(b);if(1<a&&b.getLength()>e){a=(new k(f.x[a-2],f.y[a-2])).getVectorToPoint(c);c=b.angleTo(a.reverse());var g=.35*b.getLength();a=(new q(a.x+b.x,a.y+b.y)).resizeTo(Math.max(.05,c)*g);return["c",h(a.x,2),h(a.y,2),h(b.x,
a;b++)e=n[b],f.push('<path fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="'+B(e,h,k)+'"/>');f.push("</svg>");return f.join("")}function g(c){return[x,u(c)]}function v(c){return[y,w(u(c))]}(function(c,f){"use strict";(typeof exports!=f+""?exports:c).simplify=function(b,a,c){a=a!==f?a*a:1;if(!c){var d=b.length,g,h=b[0],k=[h];for(c=1;c<d;c++){g=b[c];var l=g.x-h.x,m=g.y-h.y;l*l+m*m>a&&(k.push(g),h=g)}b=(h!==g&&k.push(g),k)}g=b;c=g.length;var d=new (typeof Uint8Array!= 2),h(b.y,2),h(b.x,2),h(b.y,2)]}return["l",h(b.x,2),h(b.y,2)]}function w(f,e,a){e=["M",h(f.x[0]-e,2),h(f.y[0]-a,2)];a=1;for(var b=f.x.length-1;a<b;a++)e.push.apply(e,p(f,a,1));0<b?e.push.apply(e,n(f,a,1)):0===b&&e.push.apply(e,["l",1,1]);return e.join(" ")}function x(f){for(var e=[],a=[["fill",void 0,"none"],["stroke","color","#000000"],["stroke-width","lineWidth",2],["stroke-linecap",void 0,"round"],["stroke-linejoin",void 0,"round"]],b=a.length-1;0<=b;b--){var c=a[b][1],g=a[b][2];e.push(a[b][0]+
f+""?Uint8Array:Array)(c),h=0,k=c-1,p,r,t=[],x=[],B=[];for(d[h]=d[k]=1;k;){m=0;for(l=h+1;l<k;l++){p=g[l];var A=g[h],w=g[k],u=A.x,v=A.y,A=w.x-u,C=w.y-v,y=void 0;if(0!==A||0!==C)y=((p.x-u)*A+(p.y-v)*C)/(A*A+C*C),1<y?(u=w.x,v=w.y):0<y&&(u+=A*y,v+=C*y);p=(A=p.x-u,C=p.y-v,A*A+C*C);p>m&&(r=l,m=p)}m>a&&(d[r]=1,t.push(h),x.push(r),t.push(r),x.push(k));h=t.pop();k=x.pop()}for(l=0;l<c;l++)d[l]&&B.push(g[l]);return b=B,b}})(window);if("function"!==typeof w)var w=function(c){var f="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(""), '="'+(c in f&&f[c]?f[c]:g)+'"')}return e.join(" ")}function v(f,e){var a=['<?xml version="1.0" encoding="UTF-8" standalone="no"?>','<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'],b,c=f.length,g,d=[],h=[],k=g=b=0,l=0,p=[];if(0!==c){for(b=0;b<c;b++){g=f[b];var n=[],q={x:[],y:[]};l=0;for(k=g.x.length;l<k;l++)n.push({x:g.x[l],y:g.y[l]});n=simplify(n,.7,!0);l=0;for(k=n.length;l<k;l++)q.x.push(n[l].x),q.y.push(n[l].y);g=q;p.push(g);d=d.concat(g.x);h=
b,a,e,d,g=0,h=0,k="",k=[];do b=c.charCodeAt(g++),a=c.charCodeAt(g++),e=c.charCodeAt(g++),d=b<<16|a<<8|e,b=d>>18&63,a=d>>12&63,e=d>>6&63,d&=63,k[h++]=f[b]+f[a]+f[e]+f[d];while(g<c.length);k=k.join("");c=c.length%3;return(c?k.slice(0,c-3):k)+"===".slice(c||3)};var x="image/svg+xml",y="image/svg+xml;base64";if("undefined"===typeof $)throw Error("We need jQuery for some of the functionality. jQuery is not detected. Failing to initialize...");(function(c){c=c.fn.jSignature;c("addPlugin","export","svg", h.concat(g.y)}c=Math.min.apply(null,d)-1;b=Math.max.apply(null,d)+1;d=Math.min.apply(null,h)-1;h=Math.max.apply(null,h)+1;k=0>c?0:c;l=0>d?0:d;b-=c;g=h-d}a.push('<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="'+b.toString()+'" height="'+g.toString()+'">');b=0;for(c=p.length;b<c;b++)g=p[b],a.push("<path "+x(e)+' d="'+w(g,k,l)+'"/>');a.push("</svg>");return a.join("")}function u(f,e){return["image/svg+xml",v(f,e)]}function y(f,e){return["image/svg+xml;base64",C(v(f,e))]}(function(f,e){"use strict";
g);c("addPlugin","export",x,g);c("addPlugin","export","svgbase64",v);c("addPlugin","export",y,v)})($)})(); f.simplify=function(a,b,c){b=b!==e?b*b:1;if(!c){var g=a.length,d=a[0],f=[d];for(c=1;c<g;c++){var h=a[c];var k=h.x-d.x,n=h.y-d.y;k*k+n*n>b&&(f.push(h),d=h)}a=(d!==h&&f.push(h),f)}h=a;c=h.length;g=new (typeof Uint8Array!=e+""?Uint8Array:Array)(c);d=0;f=c-1;var p,q=[],r=[],y=[];for(g[d]=g[f]=1;f;){n=0;for(k=d+1;k<f;k++){var A=h[k];var z=h[d],w=h[f],u=z.x,v=z.y;z=w.x-u;var B=w.y-v;if(0!==z||0!==B){var x=((A.x-u)*z+(A.y-v)*B)/(z*z+B*B);1<x?(u=w.x,v=w.y):0<x&&(u+=z*x,v+=B*x)}A=(z=A.x-u,B=A.y-v,z*z+B*B);
A>n&&(p=k,n=A)}n>b&&(g[p]=1,q.push(d),r.push(p),q.push(p),r.push(f));d=q.pop();f=r.pop()}for(k=0;k<c;k++)g[k]&&y.push(h[k]);return a=y,a}})(window);if("function"!==typeof C)var C=function(f){var e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(""),a=0,b=0,c=[];do{var g=f.charCodeAt(a++);var d=f.charCodeAt(a++);var h=f.charCodeAt(a++);var k=g<<16|d<<8|h;g=k>>18&63;d=k>>12&63;h=k>>6&63;k&=63;c[b++]=e[g]+e[d]+e[h]+e[k]}while(a<f.length);e=c.join("");f=f.length%3;return(f?e.slice(0,
f-3):e)+"===".slice(f||3)};if("undefined"===typeof $)throw Error("We need jQuery for some of the functionality. jQuery is not detected. Failing to initialize...");(function(f){f=f.fn.jSignature;f("addPlugin","export","svg",u);f("addPlugin","export","image/svg+xml",u);f("addPlugin","export","svgbase64",y);f("addPlugin","export","image/svg+xml;base64",y)})($)})();

View File

@@ -0,0 +1,4 @@
if (django && django.jQuery) {
var jQuery = django.jQuery;
var $ = django.jQuery;
}

View File

View File

@@ -0,0 +1,21 @@
import base64
import io
from django import template
from django.utils.encoding import iri_to_uri
from jsignature.utils import draw_signature
register = template.Library()
@register.filter
def signature_base64(value):
if value is None or not isinstance(value, str):
return ""
in_mem_file = io.BytesIO()
draw_signature(value).save(in_mem_file, format="PNG")
in_mem_file.seek(0)
return "data:image/png;base64,{}".format(
iri_to_uri(base64.b64encode(in_mem_file.read()).decode('utf8'))
)

View File

@@ -1,5 +0,0 @@
from .widgets import JSignatureWidgetTest
from .forms import JSignatureFormFieldTest
from .fields import JSignatureFieldTest
from .mixins import JSignatureFieldsMixinTest
from .utils import UtilsTest

View File

@@ -15,16 +15,22 @@ def draw_signature(data, as_file=False):
if `as_file` is True, a temp file is returned instead of Image instance if `as_file` is True, a temp file is returned instead of Image instance
""" """
def _remove_empty_pts(pt):
return {
'x': list(filter(lambda n: n is not None, pt['x'])),
'y': list(filter(lambda n: n is not None, pt['y']))
}
if type(data) is str: if type(data) is str:
drawing = json.loads(data) drawing = json.loads(data, object_hook=_remove_empty_pts)
elif type(data) is list: elif type(data) is list:
drawing = data drawing = data
else: else:
raise ValueError raise ValueError
# Compute box # Compute box
width = max(chain(*[d['x'] for d in drawing])) + 10 width = int(round(max(chain(*[d['x'] for d in drawing])))) + 10
height = max(chain(*[d['y'] for d in drawing])) + 10 height = int(round(max(chain(*[d['y'] for d in drawing])))) + 10
# Draw image # Draw image
im = Image.new("RGBA", (width * AA, height * AA)) im = Image.new("RGBA", (width * AA, height * AA))

View File

@@ -3,21 +3,28 @@
with jSignature jQuery plugin with jSignature jQuery plugin
""" """
import json import json
import six
from django.conf import settings
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.forms.widgets import HiddenInput from django import forms
from django.core import validators from django.core import validators
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
from jsignature.settings import JSIGNATURE_DEFAULT_CONFIG from jsignature.settings import JSIGNATURE_DEFAULT_CONFIG
JSIGNATURE_EMPTY_VALUES = validators.EMPTY_VALUES + ('[]', ) JSIGNATURE_EMPTY_VALUES = validators.EMPTY_VALUES + ('[]', )
class JSignatureWidget(HiddenInput): try:
from django.utils import six
string_types = six.string_types
except ImportError:
string_types = str
class JSignatureWidget(forms.HiddenInput):
""" """
A widget handling a signature capture field with with jSignature A widget handling a signature capture field with with jSignature
""" """
@@ -26,9 +33,22 @@ class JSignatureWidget(HiddenInput):
# normal field, not a hidden one # normal field, not a hidden one
is_hidden = False is_hidden = False
class Media: @property
js = ('js/jSignature.min.js', def media(self):
'js/django_jsignature.js') JSIGNATURE_JQUERY = getattr(settings, 'JSIGNATURE_JQUERY', 'custom')
files = ()
if JSIGNATURE_JQUERY == 'admin':
files = (
'admin/js/jquery.init.js',
'js/jsignature_admin_init.js',
)
elif JSIGNATURE_JQUERY != 'custom':
files = (JSIGNATURE_JQUERY,)
files += (
'js/jSignature.min.js',
'js/django_jsignature.js',
)
return forms.Media(js=files)
def __init__(self, attrs=None, jsignature_attrs=None): def __init__(self, attrs=None, jsignature_attrs=None):
super(JSignatureWidget, self).__init__(attrs) super(JSignatureWidget, self).__init__(attrs)
@@ -53,13 +73,13 @@ class JSignatureWidget(HiddenInput):
""" Prepare value before effectively render widget """ """ Prepare value before effectively render widget """
if value in JSIGNATURE_EMPTY_VALUES: if value in JSIGNATURE_EMPTY_VALUES:
return "[]" return "[]"
elif isinstance(value, six.string_types): elif isinstance(value, string_types):
return value return value
elif isinstance(value, list): elif isinstance(value, list):
return json.dumps(value) return json.dumps(value)
raise ValidationError('Invalid format.') raise ValidationError('Invalid format.')
def render(self, name, value, attrs=None): def render(self, name, value, attrs=None, renderer=None):
""" Render widget """ """ Render widget """
# Build config # Build config
jsign_id = self.build_jsignature_id(name) jsign_id = self.build_jsignature_id(name)

View File

@@ -1,82 +0,0 @@
import os
import sys
import argparse
from django.conf import settings
class QuickDjangoTest(object):
"""
A quick way to run the Django test suite without a fully-configured project.
Example usage:
>>> QuickDjangoTest('app1', 'app2')
Based on a script published by Lukasz Dziedzia at:
http://stackoverflow.com/questions/3841725/how-to-launch-tests-for-django-reusable-app
"""
DIRNAME = os.path.dirname(__file__)
INSTALLED_APPS = (
)
def __init__(self, *args, **kwargs):
self.apps = args
self.run_tests()
def run_tests(self):
"""
Fire up the Django test suite developed for version 1.2
"""
settings.configure(
TEMPLATE_DIRS = ('jsignature/templates/',),
ROOT_URLCONF = 'jsignature.tests',
DEBUG = True,
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(self.DIRNAME, 'database.db'),
'USER': '',
'PASSWORD': '',
'HOST': '',
'PORT': '',
}
},
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
),
INSTALLED_APPS = self.INSTALLED_APPS + self.apps
)
# Setup is needed for Django >= 1.7
import django
if hasattr(django, 'setup'):
django.setup()
try:
from django.test.runner import DiscoverRunner
failures = DiscoverRunner().run_tests(self.apps, verbosity=1)
except ImportError:
# DjangoTestSuiteRunner has been deprecated in Django 1.7
from django.test.simple import DjangoTestSuiteRunner
failures = DjangoTestSuiteRunner().run_tests(self.apps, verbosity=1)
if failures: # pragma: no cover
sys.exit(failures)
if __name__ == '__main__':
"""
What do when the user hits this file from the shell.
Example usage:
$ python quicktest.py app1 app2
"""
parser = argparse.ArgumentParser(
usage="[args]",
description="Run Django tests on the provided applications."
)
parser.add_argument('apps', nargs='+', type=str)
args = parser.parse_args()
QuickDjangoTest(*args.apps)

View File

@@ -1,3 +1,3 @@
pillow pillow
pyquery pyquery<1.4.2; python_version<="2.7"
six pyquery>=1.4.2; python_version>"2.7"

15
runtests.py Executable file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env python
import os
import sys
import django
from django.conf import settings
from django.test.utils import get_runner
if __name__ == "__main__":
os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.settings'
django.setup()
TestRunner = get_runner(settings)
test_runner = TestRunner()
failures = test_runner.run_tests(["tests"])
sys.exit(bool(failures))

View File

@@ -5,17 +5,17 @@ here = os.path.abspath(os.path.dirname(__file__))
setup( setup(
name='django-jsignature', name='django-jsignature',
version='0.8', version='0.12',
author='Florent Lebreton', author='Florent Lebreton',
author_email='florent.lebreton@makina-corpus.com', author_email='florent.lebreton@makina-corpus.com',
url='https://github.com/fle/django-jsignature', url='https://github.com/fle/django-jsignature',
download_url='https://github.com/fle/django-jsignature/tarball/0.8', download_url='http://pypi.python.org/pypi/django-jsignature',
description='Use jSignature jQuery plugin in your django projects', description='Use jSignature jQuery plugin in your django projects',
long_description=open(os.path.join(here, 'README.rst')).read() + '\n\n' + long_description=open(os.path.join(here, 'README.rst')).read() + '\n\n' +
open(os.path.join(here, 'CHANGES')).read(), open(os.path.join(here, 'CHANGES')).read(),
license='LPGL, see LICENSE file.', license='LPGL, see LICENSE file.',
install_requires=['Django'], install_requires=['Django>=1.11', 'pillow', 'pyquery>=1.4.2'],
packages=find_packages(), packages=find_packages(exclude=['example_project*', 'tests']),
include_package_data=True, include_package_data=True,
zip_safe=False, zip_safe=False,
classifiers=[ classifiers=[
@@ -25,14 +25,17 @@ setup(
'Intended Audience :: Developers', 'Intended Audience :: Developers',
'Environment :: Web Environment', 'Environment :: Web Environment',
'Framework :: Django', 'Framework :: Django',
'Framework :: Django :: 1.11',
'Framework :: Django :: 2.2',
'Framework :: Django :: 3.0',
'Development Status :: 5 - Production/Stable', 'Development Status :: 5 - Production/Stable',
'Programming Language :: Python', 'Programming Language :: Python',
'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.4' 'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
], ],
) )

0
tests/__init__.py Normal file
View File

View File

@@ -1,5 +1,5 @@
""" Provides a dummy model implementing JSignatureFieldsMixin """ """ Provides a dummy model implementing JSignatureFieldsMixin """
from ..mixins import JSignatureFieldsMixin from jsignature.mixins import JSignatureFieldsMixin
class JSignatureTestModel(JSignatureFieldsMixin): class JSignatureTestModel(JSignatureFieldsMixin):

36
tests/settings.py Normal file
View File

@@ -0,0 +1,36 @@
import os
DEBUG = True
USE_TZ = True
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
SECRET_KEY = 'thisisntactuallysecretatall'
ALLOWED_HOSTS = ['*']
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
}
# ROOT_URLCONF = 'tests.urls'
INSTALLED_APPS = [
'jsignature',
'tests',
]
PASSWORD_HASHERS = {
'django.contrib.auth.hashers.MD5PasswordHasher',
}
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': True,
},
]

View File

@@ -0,0 +1,12 @@
import json
from django.test import SimpleTestCase
from jsignature.templatetags.jsignature_filters import signature_base64
FAULTY_SIGNATURE = [{"x": [120.00675156801722, 116.11464070635179, 114.16858527551909], "y": [83.0316983821957, 76.54484694608666, 224.44505968937275]}, {"x": [161.52260075911508, 165.4147116207805, 173.84761848772226, 184.2265807854967, 201.0923945193802], "y": [84.97775381302841, 79.78827266414118, 72.00405094081033, 62.922458930257676, 56.43560749414864]}]
DUMMY_STR_VALUE = json.dumps(FAULTY_SIGNATURE)
class TemplateFilterFailedTest(SimpleTestCase):
def test_throw_error(self):
output = signature_base64(DUMMY_STR_VALUE)

View File

@@ -1,11 +1,17 @@
import json import json
import six
from django.test import SimpleTestCase from django.test import SimpleTestCase
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from ..fields import JSignatureField from jsignature.fields import JSignatureField
from ..forms import JSignatureField as JSignatureFormField from jsignature.forms import JSignatureField as JSignatureFormField
try:
from django.utils import six
string_types = six.string_types
except ImportError:
string_types = str
class JSignatureFieldTest(SimpleTestCase): class JSignatureFieldTest(SimpleTestCase):
@@ -18,13 +24,13 @@ class JSignatureFieldTest(SimpleTestCase):
def test_to_python_correct_value_python(self): def test_to_python_correct_value_python(self):
f = JSignatureField() f = JSignatureField()
val = [{"x": [1, 2], "y": [3, 4]}] val = [{"x": [1, 2], "y": [3, 4]}]
self.assertEquals(val, f.to_python(val)) self.assertEqual(val, f.to_python(val))
def test_to_python_correct_value_json(self): def test_to_python_correct_value_json(self):
f = JSignatureField() f = JSignatureField()
val = [{"x": [1, 2], "y": [3, 4]}] val = [{"x": [1, 2], "y": [3, 4]}]
val_str = '[{"x":[1,2], "y":[3,4]}]' val_str = '[{"x":[1,2], "y":[3,4]}]'
self.assertEquals(val, f.to_python(val_str)) self.assertEqual(val, f.to_python(val_str))
def test_to_python_incorrect_value(self): def test_to_python_incorrect_value(self):
f = JSignatureField() f = JSignatureField()
@@ -40,16 +46,16 @@ class JSignatureFieldTest(SimpleTestCase):
f = JSignatureField() f = JSignatureField()
val = [{"x": [1, 2], "y": [3, 4]}] val = [{"x": [1, 2], "y": [3, 4]}]
val_prep = f.get_prep_value(val) val_prep = f.get_prep_value(val)
self.assertIsInstance(val_prep, six.string_types) self.assertIsInstance(val_prep, string_types)
self.assertEquals(val, json.loads(val_prep)) self.assertEqual(val, json.loads(val_prep))
def test_get_prep_value_correct_values_json(self): def test_get_prep_value_correct_values_json(self):
f = JSignatureField() f = JSignatureField()
val = [{"x": [1, 2], "y": [3, 4]}] val = [{"x": [1, 2], "y": [3, 4]}]
val_str = '[{"x":[1,2], "y":[3,4]}]' val_str = '[{"x":[1,2], "y":[3,4]}]'
val_prep = f.get_prep_value(val_str) val_prep = f.get_prep_value(val_str)
self.assertIsInstance(val_prep, six.string_types) self.assertIsInstance(val_prep, string_types)
self.assertEquals(val, json.loads(val_prep)) self.assertEqual(val, json.loads(val_prep))
def test_get_prep_value_incorrect_values(self): def test_get_prep_value_incorrect_values(self):
f = JSignatureField() f = JSignatureField()

19
tests/test_filter.py Normal file
View File

@@ -0,0 +1,19 @@
import json
from django.test import SimpleTestCase
from jsignature.templatetags.jsignature_filters import signature_base64
DUMMY_VALUE = [{"x": [205, 210], "y": [59, 63]},
{"x": [205, 207], "y": [67, 64]}]
DUMMY_STR_VALUE = json.dumps(DUMMY_VALUE)
class TemplateFilterTest(SimpleTestCase):
def test_inputs_bad_type_value(self):
self.assertEqual(signature_base64(object()), '')
self.assertEqual(signature_base64(None), '')
def test_outputs_as_base64(self):
output = signature_base64(DUMMY_STR_VALUE)
self.assertEqual(output, "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANwAAABNCAYAAADNYApnAAABIklEQVR4nO3ZP0odURjG4d/ce5dgE3EHgXTp07iD6B7cga3YpUoh7iFrCti4BkGw0OLMoEVIEW4yxPs81ZxzGPial+/8KQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPh3ttW0dhFwCN4GbbNaFXAAlrCdVh/m791KtcC7tmkE7mP1XP2sPs1rQgd7NjVCd1TdNkL3UJ2tWRQciovqqRG8q0aXc6aDPZsat5RVX6q7Rui+zXPbX/wD/KFla7mc306qH9XneazLwZ4s3eu8eqwuV6wF3rXllvK4um9sI7/Oc7s8hMNeLVf/3xthu5nHzmzwF0y9drjrxvPAMgcA/7/lWUBnAwAAAAAAfucFwt4TZmdXW+AAAAAASUVORK5CYII=")

View File

@@ -1,8 +1,8 @@
from django.test import SimpleTestCase from django.test import SimpleTestCase
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from ..widgets import JSignatureWidget from jsignature.widgets import JSignatureWidget
from ..forms import JSignatureField from jsignature.forms import JSignatureField
class JSignatureFormFieldTest(SimpleTestCase): class JSignatureFormFieldTest(SimpleTestCase):
@@ -19,7 +19,7 @@ class JSignatureFormFieldTest(SimpleTestCase):
def test_to_python_correct_values(self): def test_to_python_correct_values(self):
f = JSignatureField() f = JSignatureField()
val = '[{"x":[1,2], "y":[3,4]}]' val = '[{"x":[1,2], "y":[3,4]}]'
self.assertEquals([{'x': [1, 2], 'y': [3, 4]}], f.to_python(val)) self.assertEqual([{'x': [1, 2], 'y': [3, 4]}], f.to_python(val))
def test_to_python_incorrect_values(self): def test_to_python_incorrect_values(self):
f = JSignatureField() f = JSignatureField()

View File

@@ -1,24 +1,10 @@
from datetime import date from datetime import date
from django.conf import settings from django.test import TestCase
from django.db.models import loading
from django.test import SimpleTestCase
from django.core.management import call_command
from .models import JSignatureTestModel from .models import JSignatureTestModel
class JSignatureFieldsMixinTest(SimpleTestCase): class JSignatureFieldsMixinTest(TestCase):
def setUp(self):
self.old_installed_apps = settings.INSTALLED_APPS
settings.INSTALLED_APPS = list(settings.INSTALLED_APPS)
settings.INSTALLED_APPS.append('jsignature.tests')
loading.cache.loaded = False
call_command('syncdb', verbosity=0)
def tearDown(self):
settings.INSTALLED_APPS = self.old_installed_apps
def test_save_create(self): def test_save_create(self):
# If an object is created signed, signature date must be set # If an object is created signed, signature date must be set
signature_value = [{"x": [1, 2], "y": [3, 4]}] signature_value = [{"x": [1, 2], "y": [3, 4]}]

View File

@@ -4,7 +4,7 @@ import imghdr
from PIL import Image from PIL import Image
from django.test import SimpleTestCase from django.test import SimpleTestCase
from ..utils import draw_signature from jsignature.utils import draw_signature
DUMMY_VALUE = [{"x": [205, 210], "y": [59, 63]}, DUMMY_VALUE = [{"x": [205, 210], "y": [59, 63]},
{"x": [205, 207], "y": [67, 64]}] {"x": [205, 207], "y": [67, 64]}]

View File

@@ -1,16 +1,21 @@
import json import json
from pyquery import PyQuery as pq from pyquery import PyQuery as pq
import six
from django.test import SimpleTestCase from django.test import SimpleTestCase, override_settings
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from ..widgets import JSignatureWidget from jsignature.widgets import JSignatureWidget
from ..settings import JSIGNATURE_HEIGHT from jsignature.settings import JSIGNATURE_HEIGHT
try:
from django.utils import six
string_types = six.string_types
except ImportError:
string_types = str
class JSignatureWidgetTest(SimpleTestCase): class JSignatureWidgetTest(SimpleTestCase):
def test_default_media(self): def test_default_media(self):
widget = JSignatureWidget() widget = JSignatureWidget()
media = widget.media media = widget.media
@@ -20,14 +25,28 @@ class JSignatureWidgetTest(SimpleTestCase):
self.assertIn('jSignature.min.js', media_js_str) self.assertIn('jSignature.min.js', media_js_str)
self.assertIn('django_jsignature.js', media_js_str) self.assertIn('django_jsignature.js', media_js_str)
media_css = list(media.render_css()) media_css = list(media.render_css())
self.assertEquals([], media_css) self.assertEqual([], media_css)
@override_settings(JSIGNATURE_JQUERY='admin')
def test_media_in_admin(self):
widget = JSignatureWidget()
media = widget.media
media_js = list(media.render_js())
self.assertEqual(4, len(media_js))
@override_settings(JSIGNATURE_JQUERY='https://code.jquery.com/jquery-3.5.0.min.js')
def test_media_custom_jquery(self):
widget = JSignatureWidget()
media = widget.media
media_js = list(media.render_js())
self.assertEqual(3, len(media_js))
def test_init(self): def test_init(self):
w = JSignatureWidget() w = JSignatureWidget()
self.assertEquals({}, w.jsignature_attrs) self.assertEqual({}, w.jsignature_attrs)
given_attrs = {'width': 300, 'height': 100} given_attrs = {'width': 300, 'height': 100}
w = JSignatureWidget(jsignature_attrs=given_attrs) w = JSignatureWidget(jsignature_attrs=given_attrs)
self.assertEquals(given_attrs, w.jsignature_attrs) self.assertEqual(given_attrs, w.jsignature_attrs)
def test_build_jsignature_id(self): def test_build_jsignature_id(self):
w = JSignatureWidget() w = JSignatureWidget()
@@ -49,16 +68,16 @@ class JSignatureWidgetTest(SimpleTestCase):
w = JSignatureWidget() w = JSignatureWidget()
val = [{"x": [1, 2], "y": [3, 4]}] val = [{"x": [1, 2], "y": [3, 4]}]
val_prep = w.prep_value(val) val_prep = w.prep_value(val)
self.assertIsInstance(val_prep, six.string_types) self.assertIsInstance(val_prep, string_types)
self.assertEquals(val, json.loads(val_prep)) self.assertEqual(val, json.loads(val_prep))
def test_prep_value_correct_values_json(self): def test_prep_value_correct_values_json(self):
w = JSignatureWidget() w = JSignatureWidget()
val = [{"x": [1, 2], "y": [3, 4]}] val = [{"x": [1, 2], "y": [3, 4]}]
val_str = '[{"x":[1,2], "y":[3,4]}]' val_str = '[{"x":[1,2], "y":[3,4]}]'
val_prep = w.prep_value(val_str) val_prep = w.prep_value(val_str)
self.assertIsInstance(val_prep, six.string_types) self.assertIsInstance(val_prep, string_types)
self.assertEquals(val, json.loads(val_prep)) self.assertEqual(val, json.loads(val_prep))
def test_prep_value_incorrect_values(self): def test_prep_value_incorrect_values(self):
w = JSignatureWidget() w = JSignatureWidget()

25
tox.ini Normal file
View File

@@ -0,0 +1,25 @@
[tox]
envlist =
{py36,py37,py38,py39,py310}-django22,
{py36,py37,py38,py39,py310}-django32,
{py38,py39,py310}-django{40,master},
py310-djangomaster
[testenv]
deps=
django22: Django>=2.2,<3.0
django32: Django>=3.2,<4.0
django40: Django>=4.0,<4.1
djangomaster: https://github.com/django/django/archive/main.tar.gz
-r requirements.txt
coverage
commands= coverage run ./runtests.py
[gh-actions]
python =
3.6: py36-django{22,32}
3.7: py37-django{22,32}
3.8: py38-django{22,32,40}
3.9: py39-django{22,32,40}
3.10: py310-django{22,32,40,master}