2 Commits
0.11 ... 1.10

Author SHA1 Message Date
Florent Lebreton
667a5fdb46 Fix travis.yml 2016-10-28 10:44:13 +02:00
Florent Lebreton
86e3e185f7 Add Django 1.10 compatibility 2016-10-28 10:41:03 +02:00
44 changed files with 341 additions and 772 deletions

View File

@@ -1,66 +0,0 @@
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,8 +1,3 @@
*.pyc *.pyc
.coverage .coverage
htmlcov htmlcov
*.egg-info
.tox
example.db
/dist
/build

32
.travis.yml Normal file
View File

@@ -0,0 +1,32 @@
language: python
python:
- 2.7
- 3.3
- 3.4
- 3.5
env:
- DJANGO_VERSION=1.7 MODULE=jsignature.tests
- DJANGO_VERSION=1.8 MODULE=jsignature.tests
- DJANGO_VERSION=1.9 MODULE=jsignature.tests
- DJANGO_VERSION=1.10 MODULE=jsignature.tests
install:
- pip install -r requirements.txt
- pip install -q Django==$DJANGO_VERSION
- pip install coverage
script: coverage run quicktest.py $MODULE
after_success:
- pip install coveralls
- coveralls
# We need to exclude old versions of Python for tests with Django >= 1.9.
matrix:
exclude:
- python: 3.3
env: DJANGO_VERSION=1.9 MODULE=jsignature.tests
- python: 3.3
env: DJANGO_VERSION=1.10 MODULE=jsignature.tests

31
CHANGES
View File

@@ -2,37 +2,6 @@
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,32 +5,28 @@ 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://img.shields.io/pypi/v/django-jsignature.svg
:target: https://pypi.python.org/pypi/django-jsignature/
:alt: Latest PyPI version
.. image:: https://travis-ci.org/fle/django-jsignature.png?branch=master .. image:: https://travis-ci.org/fle/django-jsignature.png?branch=master
:target: https://travis-ci.org/fle/django-jsignature :target: https://travis-ci.org/fle/django-jsignature
:alt: Build status
.. image:: https://coveralls.io/repos/fle/django-jsignature/badge.png .. image:: https://coveralls.io/repos/fle/django-jsignature/badge.png
:target: https://coveralls.io/r/fle/django-jsignature :target: https://coveralls.io/r/fle/django-jsignature
: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
================== ==================
Installation INSTALL
================== ==================
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,54 +35,55 @@ Usage
# settings.py # settings.py
INSTALLED_APPS = ( INSTALLED_APPS = (
... ...
'jsignature', 'jsignature',
) )
* Use provided model field (for easy storage): * Use provided form field and widget:
:: ::
# models.py # forms.py
from django.db import models from django import forms
from jsignature.fields import JSignatureField from jsignature.forms import JSignatureField
class SignatureModel(models.Model): class SignatureForm(forms.Form):
signature = JSignatureField() signature = JSignatureField()
* In your form template * In your template
:: ::
{{ form.media }} {{ form.media }}
<form action="" method="post"> <form action="." method="POST">
{{ form }} {% for field in form %}
<input type="submit" value="Save" /> {{ field.label_tag }}
{{ field }}
{% endfor %}
<input type="submit" value="Save"/>
{% csrf_token %} {% csrf_token %}
</form> </form>
* Render image from db value in your display template: * Render image after form validation:
:: ::
{# yourtemplate.html #} # views.py
{% load jsignature_filters %} from jsignature.utils import draw_signature
from myapp.forms import SignatureForm
<img src="{{ obj.signature|signature_base64 }}" alt="{{ obj }}" /> def my_view(request):
form = SignatureForm(request.POST or None)
if form.is_valid():
* By default, jSignature is made to work outside of admin, requiring that signature = form.cleaned_data.get('signature')
you include the jQuery library in your ``<head>``. if signature:
# as an image
If you want to use jSignature in the Django admin site, set the signature_picture = draw_signature(signature)
``JSIGNATURE_JQUERY`` setting to ``admin``. Otherwise if set to any url # or as a file
pointing to jQuery, it will be automatically included. signature_file_path = draw_signature(signature, as_file=True)
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:
@@ -121,11 +118,10 @@ Available settings are:
* ``JSIGNATURE_RESET_BUTTON`` (ResetButton) * ``JSIGNATURE_RESET_BUTTON`` (ResetButton)
================== ==================
In your models IN YOUR MODELS
================== ==================
If you want to store signatures easily, a provided mixin gives a ``signature`` If you wan to store signatures, provided mixin gives a ``signature`` and a ``signature_date`` that update themselves:
and a ``signature_date`` that update themselves:
:: ::
@@ -137,69 +133,10 @@ and a ``signature_date`` that update themselves:
================== ==================
In your forms AUTHORS
================== ==================
* If you need more precise handling of the form field, you can use it directly: * Florent Lebreton <florent.lebreton@makina-corpus.com>
::
# 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

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

View File

@@ -1,10 +0,0 @@
#!/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

@@ -1,26 +0,0 @@
# 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

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

View File

@@ -1,67 +0,0 @@
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

@@ -1,39 +0,0 @@
<!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

@@ -1,14 +0,0 @@
{% 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

@@ -1,24 +0,0 @@
{% 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 %}

View File

@@ -1,13 +0,0 @@
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),
]

View File

@@ -1,20 +0,0 @@
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,21 +3,14 @@
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(models.Field): class JSignatureField(models.Field):
@@ -30,10 +23,6 @@ class JSignatureField(models.Field):
return 'TextField' return 'TextField'
def to_python(self, value): def to_python(self, value):
"""
Validates that the input can be red as a JSON object. Returns a Python
datetime.date object.
"""
if value in JSIGNATURE_EMPTY_VALUES: if value in JSIGNATURE_EMPTY_VALUES:
return None return None
elif isinstance(value, list): elif isinstance(value, list):
@@ -43,10 +32,18 @@ class JSignatureField(models.Field):
except ValueError: except ValueError:
raise ValidationError('Invalid JSON format.') raise ValidationError('Invalid JSON format.')
def from_db_value(self, value, expression, connection, context):
if value in JSIGNATURE_EMPTY_VALUES:
return None
try:
return json.loads(value)
except ValueError:
raise ValidationError('Invalid JSON format.')
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, string_types): elif isinstance(value, six.string_types):
return value return value
elif isinstance(value, list): elif isinstance(value, list):
return json.dumps(value) return json.dumps(value)
@@ -56,3 +53,4 @@ class JSignatureField(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)

View File

@@ -18,13 +18,13 @@ class JSignatureField(Field):
widget = JSignatureWidget() widget = JSignatureWidget()
def to_python(self, value): def to_python(self, value):
""" """
Validates that the input can be red as a JSON object. Validates that the input can be red as a JSON object.
Returns a Python list (JSON object unserialized). Returns a Python list (JSON object unserialized).
""" """
if value in JSIGNATURE_EMPTY_VALUES: if value in JSIGNATURE_EMPTY_VALUES:
return None return None
try: try:
return json.loads(value) return json.loads(value)
except ValueError: except ValueError:
raise ValidationError('Invalid JSON format.') raise ValidationError('Invalid JSON format.')

View File

@@ -2,10 +2,9 @@
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 gettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from .fields import JSignatureField from .fields import JSignatureField
@@ -29,7 +28,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 json.dumps(self.signature) != original.signature: if is_new or 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,9 +17,6 @@ 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,22 +1,21 @@
(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'); var value = $(this).data('initial-value');
var value = $(this).data('initial-value'); $(this).jSignature(config);
$(this).jSignature(config); $(this).jSignature("setData", value, "native");
$(this).jSignature("setData", value, "native");
});
/* Each time user is done drawing a stroke, update value of hidden input */
$(".jsign-container").on("change", function(e) {
var jSignature_data = $(this).jSignature('getData', 'native');
var django_field_name = $(this).attr('id').split(/_(.+)/)[1];
$('#id_' + django_field_name).val(JSON.stringify(jSignature_data));
});
/* Bind clear button */
$(".jsign-wrapper input").on("click", function(e) {
$(this).siblings('.jsign-container').jSignature('reset');
});
}); });
})(jQuery || django.jQuery)
/* Each time user is done drawing a stroke, update value of hidden input */
$(".jsign-container").on("change", function(e) {
var jSignature_data = $(this).jSignature('getData', 'native');
var django_field_name = $(this).attr('id').split(/_(.+)/)[1];
$('#id_' + django_field_name).val(JSON.stringify(jSignature_data));
});
/* Bind clear button */
$(".jsign-wrapper input").on("click", function(e) {
$(this).siblings('.jsign-container').jSignature('reset');
});
});

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

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

View File

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

View File

@@ -1,21 +0,0 @@
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

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

View File

@@ -1,17 +1,11 @@
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 jsignature.fields import JSignatureField from ..fields import JSignatureField
from jsignature.forms import JSignatureField as JSignatureFormField from ..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):
@@ -24,19 +18,34 @@ 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.assertEqual(val, f.to_python(val)) self.assertEquals(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.assertEqual(val, f.to_python(val_str)) self.assertEquals(val, f.to_python(val_str))
def test_to_python_incorrect_value(self): def test_to_python_incorrect_value(self):
f = JSignatureField() f = JSignatureField()
val = 'foo' val = 'foo'
self.assertRaises(ValidationError, f.to_python, val) self.assertRaises(ValidationError, f.to_python, val)
def test_from_db_value_empty(self):
f = JSignatureField()
self.assertIsNone(f.from_db_value(''))
def test_from_db_value_correct_value_json(self):
f = JSignatureField()
val = [{"x": [1, 2], "y": [3, 4]}]
val_str = '[{"x":[1,2], "y":[3,4]}]'
self.assertEquals(val, f.from_db_value(val_str))
def test_from_db_value_incorrect_value(self):
f = JSignatureField()
val = 'foo'
self.assertRaises(ValidationError, f.to_python, val)
def test_get_prep_value_empty(self): def test_get_prep_value_empty(self):
f = JSignatureField() f = JSignatureField()
for val in ['', [], '[]']: for val in ['', [], '[]']:
@@ -46,16 +55,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, string_types) self.assertIsInstance(val_prep, six.string_types)
self.assertEqual(val, json.loads(val_prep)) self.assertEquals(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, string_types) self.assertIsInstance(val_prep, six.string_types)
self.assertEqual(val, json.loads(val_prep)) self.assertEquals(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()

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 jsignature.widgets import JSignatureWidget from ..widgets import JSignatureWidget
from jsignature.forms import JSignatureField from ..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.assertEqual([{'x': [1, 2], 'y': [3, 4]}], f.to_python(val)) self.assertEquals([{'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,10 +1,24 @@
from datetime import date from datetime import date
from django.test import TestCase from django.conf import settings
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(TestCase): class JSignatureFieldsMixinTest(SimpleTestCase):
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

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

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 jsignature.utils import draw_signature from ..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,21 +1,16 @@
import json import json
from pyquery import PyQuery as pq from pyquery import PyQuery as pq
import six
from django.test import SimpleTestCase, override_settings from django.test import SimpleTestCase
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from jsignature.widgets import JSignatureWidget from ..widgets import JSignatureWidget
from jsignature.settings import JSIGNATURE_HEIGHT from ..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
@@ -25,28 +20,14 @@ 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.assertEqual([], media_css) self.assertEquals([], 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.assertEqual({}, w.jsignature_attrs) self.assertEquals({}, 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.assertEqual(given_attrs, w.jsignature_attrs) self.assertEquals(given_attrs, w.jsignature_attrs)
def test_build_jsignature_id(self): def test_build_jsignature_id(self):
w = JSignatureWidget() w = JSignatureWidget()
@@ -68,16 +49,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, string_types) self.assertIsInstance(val_prep, six.string_types)
self.assertEqual(val, json.loads(val_prep)) self.assertEquals(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, string_types) self.assertIsInstance(val_prep, six.string_types)
self.assertEqual(val, json.loads(val_prep)) self.assertEquals(val, json.loads(val_prep))
def test_prep_value_incorrect_values(self): def test_prep_value_incorrect_values(self):
w = JSignatureWidget() w = JSignatureWidget()

View File

@@ -15,31 +15,25 @@ 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, object_hook=_remove_empty_pts) drawing = json.loads(data)
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 = int(round(max(chain(*[d['x'] for d in drawing])))) + 10 width = max(chain(*[d['x'] for d in drawing])) + 10
height = int(round(max(chain(*[d['y'] for d in drawing])))) + 10 height = 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))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
for line in drawing: for line in drawing:
len_line = len(line['x']) len_line = len(line['x'])
points = [(line['x'][i] * AA, line['y'][i] * AA) points = [(line['x'][i]*AA, line['y'][i]*AA)
for i in range(0, len_line)] for i in range(0, len_line)]
draw.line(points, fill="#000", width=2 * AA) draw.line(points, fill="#000", width=2*AA)
im = ImageOps.expand(im) im = ImageOps.expand(im)
# Smart crop # Smart crop
bbox = im.getbbox() bbox = im.getbbox()

View File

@@ -3,28 +3,21 @@
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 import forms from django.forms.widgets import HiddenInput
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 gettext_lazy as _ from django.utils.translation import ugettext_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 + ('[]', )
try: class JSignatureWidget(HiddenInput):
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
""" """
@@ -33,22 +26,9 @@ class JSignatureWidget(forms.HiddenInput):
# normal field, not a hidden one # normal field, not a hidden one
is_hidden = False is_hidden = False
@property class Media:
def media(self): js = ('js/jSignature.min.js',
JSIGNATURE_JQUERY = getattr(settings, 'JSIGNATURE_JQUERY', 'custom') 'js/django_jsignature.js')
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)
@@ -73,13 +53,13 @@ class JSignatureWidget(forms.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, string_types): elif isinstance(value, six.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, renderer=None): def render(self, name, value, attrs=None):
""" Render widget """ """ Render widget """
# Build config # Build config
jsign_id = self.build_jsignature_id(name) jsign_id = self.build_jsignature_id(name)

82
quicktest.py Normal file
View File

@@ -0,0 +1,82 @@
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<1.4.2; python_version<="2.7" pyquery
pyquery>=1.4.2; python_version>"2.7" six

View File

@@ -1,15 +0,0 @@
#!/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.11', version='0.8',
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='http://pypi.python.org/pypi/django-jsignature', download_url='https://github.com/fle/django-jsignature/tarball/0.8',
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>=1.11', 'pillow', 'pyquery>=1.4.2'], install_requires=['Django'],
packages=find_packages(exclude=['example_project*', 'tests']), packages=find_packages(),
include_package_data=True, include_package_data=True,
zip_safe=False, zip_safe=False,
classifiers=[ classifiers=[
@@ -25,17 +25,14 @@ 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.7', 'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.4'
], ],
) )

View File

View File

@@ -1,36 +0,0 @@
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

@@ -1,12 +0,0 @@
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,19 +0,0 @@
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=")

25
tox.ini
View File

@@ -1,25 +0,0 @@
[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}