27 Commits
0.7.6 ... 0.10

Author SHA1 Message Date
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
Florent Lebreton
6da81f2115 Release 0.8 2014-12-04 13:58:40 +01:00
Florent Lebreton
d12b1e80ac Merge pull request #5 from Gagaro/master
Python3 / Django1.7 support & better testing
2014-12-04 13:50:38 +01:00
Gagaro
880033d3a5 fix: django.utils.six not available before django 1.6 2014-12-04 12:34:49 +01:00
Gagaro
c15074873b fix: python 3 compatibility 2014-12-04 12:27:25 +01:00
Gagaro
8e912f9738 tests: improve testing by splitting tests + use six for basestring python3 compatibility 2014-12-04 12:18:53 +01:00
Gagaro
70b4f2e9b6 fix: .travis.yml matrix exclude 2014-12-04 10:31:30 +01:00
Gagaro
124fe21d06 tests: upgrade quicktest.py + improve travis configuration 2014-12-04 10:22:31 +01:00
Florent Lebreton
738eb869fc Merge pull request #4 from Gagaro/master
splitted widgets tests
2014-12-03 12:45:50 +01:00
Gagaro
32566873a4 splitted widgets tests 2014-12-03 11:18:28 +01:00
40 changed files with 752 additions and 274 deletions

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,19 +1,46 @@
language: python language: python
python: matrix:
- "2.7" fast_finish: true
allow_failures:
- env: DJANGO=master
include:
- python: 2.7
env: DJANGO=1.11
env: - python: 3.5
- DJANGO_VERSION=1.4 env: DJANGO=1.11
- DJANGO_VERSION=1.5 - python: 3.5
env: DJANGO=2.2
- python: 3.6
env: DJANGO=1.11
- python: 3.6
env: DJANGO=2.2
- python: 3.6
env: DJANGO=3.0
- python: 3.6
env: DJANGO=master
- python: 3.7
env: DJANGO=2.2
- python: 3.7
env: DJANGO=3.0
- python: 3.7
env: DJANGO=master
- python: 3.8
env: DJANGO=2.2
- python: 3.8
env: DJANGO=3.0
- python: 3.8
env: DJANGO=master
install: install:
- pip install -r requirements.txt --use-mirrors - pip install tox tox-travis
- pip install -q Django==$DJANGO_VERSION --use-mirrors script:
- pip install coverage - tox
script: coverage run quicktest.py jsignature
after_success: after_success:
- pip install coveralls - pip install coveralls
- coveralls - coveralls

28
CHANGES
View File

@@ -2,6 +2,34 @@
CHANGELOG CHANGELOG
========= =========
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)
==================
** New **
- Add support for Python 3 (@Gagaro)
- Add support for Django 1.7 (@Gagaro)
0.7.6 (2014-11-26) 0.7.6 (2014-11-26)
================== ==================

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://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
================== ==================
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 }} <input type="submit" value="Save" />
{{ field }}
{% endfor %}
<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,11 +3,21 @@
with jSignature jQuery plugin with jSignature jQuery plugin
""" """
import json import json
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):
@@ -15,7 +25,6 @@ class JSignatureField(models.Field):
A model field handling a signature captured with jSignature A model field handling a signature captured with jSignature
""" """
description = "A signature captured with jSignature" description = "A signature captured with jSignature"
__metaclass__ = models.SubfieldBase
def get_internal_type(self): def get_internal_type(self):
return 'TextField' return 'TextField'
@@ -37,7 +46,7 @@ class JSignatureField(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, basestring): 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)
@@ -47,9 +56,3 @@ 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)
try:
from south.modelsinspector import add_introspection_rules
add_introspection_rules([], ["jsignature.fields.JSignatureField"])
except ImportError:
pass

View File

@@ -2,6 +2,7 @@
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 ugettext_lazy as _
@@ -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,4 +1,5 @@
$(document).ready(function() { (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');
@@ -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)

100
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 "2012-11-01T22:48" "commit ID 1c15dfafecc75925c3b7d529356a558b59220edb"
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,47 @@ 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(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?
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< (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&&
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= (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===
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.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)};
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= 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=
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= 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=
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(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=
".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(), 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);
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.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);
$('<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.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));
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= 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?
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? -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",
(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&& 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);
(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"=== 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],
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= 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],
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= 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(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.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,
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()<= 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=
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, 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");
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; 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",
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", 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,
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(); 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."+
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), 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,
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+ 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,
".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, "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],
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;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&&
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); C[b])return C[b].apply(this,Array.prototype.slice.call(arguments,1));$.error("Method "+String(b)+" does not exist on jQuery."+e)}})();
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."+ (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),
g)}else return m.init.apply(this,arguments)}})(window)})(); 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))})}})})})();
(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", (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(){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)}})})})(); [],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(){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= 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=
[];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", {remapTailChars:e,compressstrokeleg:y,uncompressstrokeleg:v,compressstrokes:z,uncompressstrokes:u,charmap:q})}).call("undefined"!==typeof window?window:this);
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(){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/
(this.jSignatureDebug.base30={remapTailChars:B,compressstrokeleg:u,uncompressstrokeleg:g,compressstrokes:v,uncompressstrokes:w,charmap:r})}).call("undefined"!==typeof window?window:this); 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-
(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/ 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,
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- 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,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, 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=
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, 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<
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;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,
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< 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(""),
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!= 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",
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(""), "export",p,v);u("addPlugin","export","svgbase64",z);u("addPlugin","export",h,z)})();
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

@@ -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

@@ -4,10 +4,12 @@
""" """
import json import json
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 ugettext_lazy as _
from jsignature.settings import JSIGNATURE_DEFAULT_CONFIG from jsignature.settings import JSIGNATURE_DEFAULT_CONFIG
@@ -15,7 +17,14 @@ 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
""" """
@@ -24,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)
@@ -51,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, basestring): 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,64 +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 = (
'django.contrib.contenttypes',
'django.contrib.sessions',
)
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(
DATABASES={
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(self.DIRNAME, 'database.db'),
'USER': '',
'PASSWORD': '',
'HOST': '',
'PORT': '',
}
},
INSTALLED_APPS=self.INSTALLED_APPS + self.apps,
)
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)

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.7.6', version='0.10',
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.7.6", 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'],
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,6 +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 :: 2.7'], 'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'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):

34
tests/settings.py Normal file
View File

@@ -0,0 +1,34 @@
import os
DEBUG = 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,42 +1,64 @@
import json import json
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):
def test_to_python(self): def test_to_python_empty(self):
f = JSignatureField() f = JSignatureField()
# Empty values
for val in ['', [], '[]']: for val in ['', [], '[]']:
self.assertIsNone(f.to_python(val)) self.assertIsNone(f.to_python(val))
# Correct values
def test_to_python_correct_value_python(self):
f = JSignatureField()
val = [{"x": [1, 2], "y": [3, 4]}] val = [{"x": [1, 2], "y": [3, 4]}]
self.assertEquals(val, f.to_python(val)) self.assertEquals(val, f.to_python(val))
def test_to_python_correct_value_json(self):
f = JSignatureField()
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.assertEquals(val, f.to_python(val_str))
# Incorrect values
def test_to_python_incorrect_value(self):
f = JSignatureField()
val = 'foo' val = 'foo'
self.assertRaises(ValidationError, f.to_python, val) self.assertRaises(ValidationError, f.to_python, val)
def test_get_prep_value(self): def test_get_prep_value_empty(self):
f = JSignatureField() f = JSignatureField()
# Empty values
for val in ['', [], '[]']: for val in ['', [], '[]']:
self.assertIsNone(f.get_prep_value(val)) self.assertIsNone(f.get_prep_value(val))
# Correct values
def test_get_prep_value_correct_values_python(self):
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, basestring) self.assertIsInstance(val_prep, string_types)
self.assertEquals(val, json.loads(val_prep)) self.assertEquals(val, json.loads(val_prep))
def test_get_prep_value_correct_values_json(self):
f = JSignatureField()
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, basestring) self.assertIsInstance(val_prep, string_types)
self.assertEquals(val, json.loads(val_prep)) self.assertEquals(val, json.loads(val_prep))
# Incorrect values
def test_get_prep_value_incorrect_values(self):
f = JSignatureField()
val = type('Foo') val = type('Foo')
self.assertRaises(ValidationError, f.get_prep_value, val) self.assertRaises(ValidationError, f.get_prep_value, val)

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):
@@ -11,14 +11,17 @@ class JSignatureFormFieldTest(SimpleTestCase):
f = JSignatureField() f = JSignatureField()
self.assertIsInstance(f.widget, JSignatureWidget) self.assertIsInstance(f.widget, JSignatureWidget)
def test_to_python(self): def test_to_python_empty_values(self):
f = JSignatureField() f = JSignatureField()
# Empty values
for val in ['', [], '[]']: for val in ['', [], '[]']:
self.assertIsNone(f.to_python(val)) self.assertIsNone(f.to_python(val))
# Correct values
def test_to_python_correct_values(self):
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.assertEquals([{'x': [1, 2], 'y': [3, 4]}], f.to_python(val))
# Incorrect values
def test_to_python_incorrect_values(self):
f = JSignatureField()
val = 'foo' val = 'foo'
self.assertRaises(ValidationError, f.to_python, val) self.assertRaises(ValidationError, f.to_python, val)

View File

@@ -1,25 +1,11 @@
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 test_save_create(self):
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(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]}]
i = JSignatureTestModel(signature=signature_value) i = JSignatureTestModel(signature=signature_value)
@@ -27,16 +13,19 @@ class JSignatureFieldsMixinTest(SimpleTestCase):
i = JSignatureTestModel.objects.get(pk=i.pk) i = JSignatureTestModel.objects.get(pk=i.pk)
self.assertEqual(date.today(), i.signature_date.date()) self.assertEqual(date.today(), i.signature_date.date())
def test_save_no_change(self):
# If signature doesn't change, signature date must not be updated # If signature doesn't change, signature date must not be updated
signature_value = [{"x": [1, 2], "y": [3, 4]}]
i = JSignatureTestModel(signature=signature_value) i = JSignatureTestModel(signature=signature_value)
i.save() i.save()
i.signature_date = date(2013, 1, 1) i.signature_date = date(2013, 1, 1)
i.signature = signature_value
i.save() i.save()
i = JSignatureTestModel.objects.get(pk=i.pk) i = JSignatureTestModel.objects.get(pk=i.pk)
self.assertEqual(date(2013, 1, 1), i.signature_date.date()) self.assertEqual(date(2013, 1, 1), i.signature_date.date())
def test_save_change(self):
# If signature changes, signature date must be updated too # If signature changes, signature date must be updated too
signature_value = [{"x": [1, 2], "y": [3, 4]}]
new_signature_value = [{"x": [5, 6], "y": [7, 8]}] new_signature_value = [{"x": [5, 6], "y": [7, 8]}]
i = JSignatureTestModel(signature=signature_value, i = JSignatureTestModel(signature=signature_value,
signature_date=date(2013, 1, 1)) signature_date=date(2013, 1, 1))
@@ -47,7 +36,9 @@ class JSignatureFieldsMixinTest(SimpleTestCase):
i = JSignatureTestModel.objects.get(pk=i.pk) i = JSignatureTestModel.objects.get(pk=i.pk)
self.assertEqual(date.today(), i.signature_date.date()) self.assertEqual(date.today(), i.signature_date.date())
def test_save_none(self):
# If sinature is set to None, it must be the same for signature_date # If sinature is set to None, it must be the same for signature_date
signature_value = [{"x": [1, 2], "y": [3, 4]}]
i = JSignatureTestModel(signature=signature_value) i = JSignatureTestModel(signature=signature_value)
i.save() i.save()
i.signature = None i.signature = None

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]}]
@@ -13,22 +13,24 @@ DUMMY_STR_VALUE = json.dumps(DUMMY_VALUE)
class UtilsTest(SimpleTestCase): class UtilsTest(SimpleTestCase):
def test_inputs(self): def test_inputs_bad_str_value(self):
# Bad str value
self.assertRaises(ValueError, draw_signature, 'foo_bar') self.assertRaises(ValueError, draw_signature, 'foo_bar')
# Bad type value
def test_inputs_bad_type_value(self):
self.assertRaises(ValueError, draw_signature, object()) self.assertRaises(ValueError, draw_signature, object())
# Good list value
def test_inputs_good_list_value(self):
draw_signature(DUMMY_VALUE) draw_signature(DUMMY_VALUE)
# Good str value
def test_inputs_good_str_value(self):
draw_signature(DUMMY_STR_VALUE) draw_signature(DUMMY_STR_VALUE)
def test_outputs(self): def test_outputs_as_file(self):
# As a file
output = draw_signature(DUMMY_VALUE, as_file=True) output = draw_signature(DUMMY_VALUE, as_file=True)
self.assertTrue(os.path.isfile(output)) self.assertTrue(os.path.isfile(output))
self.assertIsNotNone(imghdr.what(output)) self.assertIsNotNone(imghdr.what(output))
# As an Image
def test_outputs_as_image(self):
output = draw_signature(DUMMY_VALUE) output = draw_signature(DUMMY_VALUE)
self.assertTrue(issubclass(output.__class__, Image.Image)) self.assertTrue(issubclass(output.__class__, Image.Image))
self.assertTrue(all(output.getbbox())) self.assertTrue(all(output.getbbox()))

View File

@@ -1,15 +1,21 @@
import json import json
from pyquery import PyQuery as pq from pyquery import PyQuery as pq
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
@@ -21,6 +27,20 @@ class JSignatureWidgetTest(SimpleTestCase):
media_css = list(media.render_css()) media_css = list(media.render_css())
self.assertEquals([], 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.assertEquals({}, w.jsignature_attrs) self.assertEquals({}, w.jsignature_attrs)
@@ -39,21 +59,28 @@ class JSignatureWidgetTest(SimpleTestCase):
self.assertEqual(400, config.get('width')) self.assertEqual(400, config.get('width'))
self.assertEqual(JSIGNATURE_HEIGHT, config.get('height')) self.assertEqual(JSIGNATURE_HEIGHT, config.get('height'))
def test_prep_value(self): def test_prep_value_empty_values(self):
w = JSignatureWidget() w = JSignatureWidget()
# Empty values
for val in ['', [], '[]']: for val in ['', [], '[]']:
self.assertEqual('[]', w.prep_value(val)) self.assertEqual('[]', w.prep_value(val))
# Correct values
def test_prep_value_correct_values_python(self):
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, basestring) self.assertIsInstance(val_prep, string_types)
self.assertEquals(val, json.loads(val_prep)) self.assertEquals(val, json.loads(val_prep))
def test_prep_value_correct_values_json(self):
w = JSignatureWidget()
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, basestring) self.assertIsInstance(val_prep, string_types)
self.assertEquals(val, json.loads(val_prep)) self.assertEquals(val, json.loads(val_prep))
# Incorrect values
def test_prep_value_incorrect_values(self):
w = JSignatureWidget()
val = type('Foo') val = type('Foo')
self.assertRaises(ValidationError, w.prep_value, val) self.assertRaises(ValidationError, w.prep_value, val)
@@ -64,11 +91,12 @@ class JSignatureWidgetTest(SimpleTestCase):
self.assertEqual(1, len(pq('.jsign-wrapper', output))) self.assertEqual(1, len(pq('.jsign-wrapper', output)))
self.assertEqual(1, len(pq('[type=hidden]', output))) self.assertEqual(1, len(pq('[type=hidden]', output)))
def test_render_reset_button(self): def test_render_reset_button_true(self):
w = JSignatureWidget(jsignature_attrs={'ResetButton': True}) w = JSignatureWidget(jsignature_attrs={'ResetButton': True})
output = w.render(name='foo', value=None) output = w.render(name='foo', value=None)
self.assertEqual(1, len(pq('[type=button]', output))) self.assertEqual(1, len(pq('[type=button]', output)))
def test_render_reset_button_false(self):
w = JSignatureWidget(jsignature_attrs={'ResetButton': False}) w = JSignatureWidget(jsignature_attrs={'ResetButton': False})
output = w.render(name='foo', value=None) output = w.render(name='foo', value=None)
self.assertEqual(0, len(pq('[type=button]', output))) self.assertEqual(0, len(pq('[type=button]', output)))

23
tox.ini Normal file
View File

@@ -0,0 +1,23 @@
[tox]
envlist =
{py27,py35,py36}-django111,
{py35,py36,py37,py38}-django22,
{py36,py37,py38}-django{30,master}
[testenv]
deps=
django111: Django>=1.11,<2.0
django22: Django>=2.2,<3.0
django30: Django>=3.0,<4.0
djangomaster: https://github.com/django/django/archive/master.tar.gz
-r requirements.txt
coverage
commands= coverage run ./runtests.py
[travis:env]
DJANGO =
1.11: django111
2.2: django22
3.0: django30
master: djangomaster