First version
This commit is contained in:
6
jsignature/__init__.py
Normal file
6
jsignature/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
"""
|
||||
django-jsignature provides a simple way to handle jSignature jQuery plugin
|
||||
through a django form.
|
||||
|
||||
See : https://github.com/brinley/jSignature/blob/master/README.md
|
||||
"""
|
||||
54
jsignature/fields.py
Normal file
54
jsignature/fields.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Provides a django model field to store a signature captured
|
||||
with jSignature jQuery plugin
|
||||
"""
|
||||
import json
|
||||
from django.db import models
|
||||
from django.core import validators
|
||||
from django.core.exceptions import ValidationError
|
||||
from .forms import JSignatureField as JSignatureFormField
|
||||
|
||||
class JSignatureField(models.Field):
|
||||
"""
|
||||
A model field handling a signature captured with jSignature
|
||||
"""
|
||||
description = "A signature captured with jSignature"
|
||||
__metaclass__ = models.SubfieldBase
|
||||
|
||||
def get_internal_type(self):
|
||||
return 'TextField'
|
||||
|
||||
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 validators.EMPTY_VALUES:
|
||||
return None
|
||||
elif isinstance(value, list):
|
||||
return value
|
||||
try:
|
||||
return json.loads(value)
|
||||
except ValueError:
|
||||
raise ValidationError('Invalid JSON format.')
|
||||
|
||||
def get_prep_value(self, value):
|
||||
return self.to_python(value)
|
||||
|
||||
def get_db_prep_value(self, value, connection, prepared=False):
|
||||
if value in validators.EMPTY_VALUES:
|
||||
return None
|
||||
elif isinstance(value, basestring):
|
||||
return value
|
||||
elif isinstance(value, list):
|
||||
return json.dumps(value)
|
||||
raise ValidationError('Invalid format.')
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
defaults = {'form_class': JSignatureFormField}
|
||||
defaults.update(kwargs)
|
||||
return super(JSignatureField, self).formfield(**defaults)
|
||||
|
||||
|
||||
from south.modelsinspector import add_introspection_rules
|
||||
add_introspection_rules([], ["jsignature.fields.JSignatureField"])
|
||||
27
jsignature/forms.py
Normal file
27
jsignature/forms.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""
|
||||
Provides a django form field to handle a signature capture field with
|
||||
with jSignature jQuery plugin
|
||||
"""
|
||||
import json
|
||||
from django.forms.fields import Field
|
||||
from django.core import validators
|
||||
from django.core.exceptions import ValidationError
|
||||
from .widgets import JSignatureWidget
|
||||
|
||||
class JSignatureField(Field):
|
||||
"""
|
||||
A field handling a signature capture field with with jSignature
|
||||
"""
|
||||
widget = JSignatureWidget()
|
||||
|
||||
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 validators.EMPTY_VALUES:
|
||||
return None
|
||||
try:
|
||||
return json.loads(value)
|
||||
except ValueError:
|
||||
raise ValidationError('Invalid JSON format.')
|
||||
36
jsignature/mixins.py
Normal file
36
jsignature/mixins.py
Normal file
@@ -0,0 +1,36 @@
|
||||
"""
|
||||
A django mixin providing fields to store a signature captured
|
||||
with jSignature jQuery plugin
|
||||
"""
|
||||
from datetime import datetime
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from .fields import JSignatureField
|
||||
|
||||
class JSignatureFieldsMixin(models.Model):
|
||||
""" Mixin class providing fields to store a signature with jSignature """
|
||||
signature = JSignatureField(
|
||||
_('Signature'),
|
||||
blank=True,
|
||||
null=True)
|
||||
signature_date = models.DateTimeField(
|
||||
_('Signature date'),
|
||||
blank=True,
|
||||
null=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
is_new = self.pk is None
|
||||
original = not is_new and self.__class__.objects.get(pk=self.pk) or None
|
||||
|
||||
print self.signature
|
||||
if self.signature:
|
||||
if is_new or self.signature != original.signature:
|
||||
self.signature_date = datetime.now()
|
||||
else:
|
||||
self.signature_date = None
|
||||
|
||||
super(JSignatureFieldsMixin, self).save()
|
||||
1
jsignature/models.py
Normal file
1
jsignature/models.py
Normal file
@@ -0,0 +1 @@
|
||||
""" Simply turns this module in a django app """
|
||||
19
jsignature/settings.py
Normal file
19
jsignature/settings.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from django.conf import settings
|
||||
|
||||
JSIGNATURE_WIDTH = getattr(settings, 'JSIGNATURE_WIDTH', 'ratio')
|
||||
JSIGNATURE_HEIGHT = getattr(settings, 'JSIGNATURE_HEIGHT', 'ratio')
|
||||
JSIGNATURE_COLOR = getattr(settings, 'JSIGNATURE_COLOR', '#000')
|
||||
JSIGNATURE_BACKGROUND_COLOR = getattr(settings, 'JSIGNATURE_BACKGROUND_COLOR', '#FFF')
|
||||
JSIGNATURE_DECOR_COLOR = getattr(settings, 'JSIGNATURE_DECOR_COLOR', '#DDD')
|
||||
JSIGNATURE_LINE_WIDTH = getattr(settings, 'JSIGNATURE_LINE_WIDTH', 0)
|
||||
JSIGNATURE_UNDO_BUTTON = getattr(settings, 'JSIGNATURE_UNDO_BUTTON', False)
|
||||
|
||||
JSIGNATURE_DEFAULT_CONFIG = {
|
||||
'width': JSIGNATURE_WIDTH,
|
||||
'height': JSIGNATURE_HEIGHT,
|
||||
'color': JSIGNATURE_COLOR,
|
||||
'background-color': JSIGNATURE_BACKGROUND_COLOR,
|
||||
'decor-color': JSIGNATURE_DECOR_COLOR,
|
||||
'lineWidth': JSIGNATURE_LINE_WIDTH,
|
||||
'UndoButton': JSIGNATURE_UNDO_BUTTON,
|
||||
}
|
||||
15
jsignature/static/js/django_jsignature.js
Normal file
15
jsignature/static/js/django_jsignature.js
Normal file
@@ -0,0 +1,15 @@
|
||||
$(document).ready(function() {
|
||||
|
||||
/* Each time user is done drawing a stroke, update value of hidden input */
|
||||
$(document).delegate(".jsign-container", "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 */
|
||||
$(document).delegate(".jsign-wrapper input", "click", function(e) {
|
||||
$(this).siblings('.jsign-container').jSignature('reset');
|
||||
});
|
||||
|
||||
})
|
||||
79
jsignature/static/js/jSignature.min.js
vendored
Normal file
79
jsignature/static/js/jSignature.min.js
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
|
||||
jSignature v2 "2013-08-26T07:00" "commit ID 986265299fecdb0f5b20acce92319220809707bf"
|
||||
Copyright (c) 2012 Willow Systems Corp http://willow-systems.com
|
||||
Copyright (c) 2010 Brinley Ang http://www.unbolt.net
|
||||
MIT License <http://www.opensource.org/licenses/mit-license.php>
|
||||
|
||||
|
||||
base64 encoder
|
||||
MIT, GPL
|
||||
http://phpjs.org/functions/base64_encode
|
||||
+ original by: Tyler Akins (http://rumkin.com)
|
||||
+ improved by: Bayron Guevara
|
||||
+ improved by: Thunder.m
|
||||
+ improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
|
||||
+ bugfixed by: Pellentesque Malesuada
|
||||
+ improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
|
||||
+ improved by: Rafal Kukawski (http://kukawski.pl)
|
||||
|
||||
|
||||
jSignature v2 jSignature's Undo Button and undo functionality plugin
|
||||
|
||||
|
||||
jSignature v2 jSignature's custom "base30" format export and import plugins.
|
||||
|
||||
|
||||
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;
|
||||
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<
|
||||
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=
|
||||
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,
|
||||
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(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(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+
|
||||
".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(),
|
||||
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=
|
||||
$('<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.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=
|
||||
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?
|
||||
(c.bind("mousemove."+g,e),c.bind("mouseup."+g,a),c.bind("mousedown."+g,b)):(d.ontouchstart=function(c){d.onmousedown=d.onmouseup=d.onmousemove=void 0;this.fatFingerCompensation=f.minFatFingerCompensation&&-3*f.lineWidth>f.minFatFingerCompensation?-3*f.lineWidth:f.minFatFingerCompensation;b(c);d.ontouchend=a;d.ontouchstart=b;d.ontouchmove=e},d.onmousedown=function(c){d.ontouchstart=d.ontouchend=d.ontouchmove=void 0;b(c);d.onmousedown=b;d.onmouseup=a;d.onmousemove=e},window.navigator.msPointerEnabled&&
|
||||
(d.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"===
|
||||
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=
|
||||
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=
|
||||
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-
|
||||
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()<=
|
||||
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,
|
||||
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;
|
||||
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",
|
||||
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();
|
||||
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),
|
||||
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+
|
||||
".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=!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,
|
||||
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);
|
||||
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."+
|
||||
g)}else return m.init.apply(this,arguments)}})(window)})();
|
||||
(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(){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)}})})})();
|
||||
(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=
|
||||
[];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",
|
||||
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.jSignatureDebug.base30={remapTailChars:B,compressstrokeleg:u,uncompressstrokeleg:g,compressstrokes:v,uncompressstrokes:w,charmap:r})}).call("undefined"!==typeof window?window:this);
|
||||
(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),d=Math.sqrt(Math.pow(a,2)/(1+Math.pow(c,2))),c=c*d;this._length=a;this.x=d*b(this.x);this.y=c*b(this.y)}return this};this.angleTo=function(a){var b=this.getLength()*a.getLength();return 0===b?0:Math.acos(Math.min(Math.max((this.x*a.x+this.y*a.y)/b,-1),1))/Math.PI}}function l(c,f){this.x=c;this.y=f;this.getVectorToCoordinates=function(b,a){return new r(b-this.x,a-this.y)};this.getVectorFromCoordinates=function(b,a){return this.getVectorToCoordinates(b,a).reverse()};this.getVectorToPoint=function(b){return new r(b.x-
|
||||
this.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,
|
||||
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,
|
||||
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[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<
|
||||
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!=
|
||||
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)})($)})();
|
||||
51
jsignature/utils.py
Normal file
51
jsignature/utils.py
Normal file
@@ -0,0 +1,51 @@
|
||||
"""
|
||||
Very inspired by zivezab's django-autograph
|
||||
https://github.com/zivezab/django-autograph/blob/master/autograph/utils.py
|
||||
"""
|
||||
import json
|
||||
import cStringIO
|
||||
from itertools import chain
|
||||
from tempfile import NamedTemporaryFile
|
||||
from PIL import Image, ImageDraw, ImageOps
|
||||
|
||||
AA = 5 # super sampling gor antialiasing
|
||||
|
||||
def draw_signature(data, as_file=False):
|
||||
""" Draw signature based on lines stored in json_string.
|
||||
`data` can be a json object (list in fact) or a json string
|
||||
if `as_file` is True, a temp file is returned instead of Image instance
|
||||
"""
|
||||
|
||||
if type(data) is str:
|
||||
drawing = json.loads(data)
|
||||
elif type(data) is list:
|
||||
drawing = data
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
# Compute box
|
||||
width = max(chain(*[d['x'] for d in data])) + 10
|
||||
height = max(chain(*[d['y'] for d in data])) + 10
|
||||
|
||||
# Draw image
|
||||
im = Image.new("RGBA", (width*AA, height*AA))
|
||||
draw = ImageDraw.Draw(im)
|
||||
for line in drawing:
|
||||
len_line = len(line['x'])
|
||||
points = [(line['x'][i]*AA, line['y'][i]*AA) for i in range(0, len_line)]
|
||||
draw.line(points, fill="#000", width=2*AA)
|
||||
im = ImageOps.expand(im)
|
||||
# Smart crop
|
||||
bbox = im.getbbox()
|
||||
if bbox:
|
||||
im.crop(bbox)
|
||||
|
||||
im.thumbnail((width, height), Image.ANTIALIAS)
|
||||
|
||||
if as_file:
|
||||
ret = im._dump(format='PNG')
|
||||
else:
|
||||
ret = img
|
||||
|
||||
return ret
|
||||
|
||||
41
jsignature/widgets.py
Normal file
41
jsignature/widgets.py
Normal file
@@ -0,0 +1,41 @@
|
||||
"""
|
||||
Provides a django form widget to handle a signature capture field with
|
||||
with jSignature jQuery plugin
|
||||
"""
|
||||
import json
|
||||
from django.forms.widgets import HiddenInput
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from jsignature.settings import JSIGNATURE_DEFAULT_CONFIG
|
||||
|
||||
class JSignatureWidget(HiddenInput):
|
||||
"""
|
||||
A widget handling a signature capture field with with jSignature
|
||||
"""
|
||||
|
||||
class Media:
|
||||
js = ('js/jSignature.min.js',
|
||||
'js/django_jsignature.js')
|
||||
|
||||
def __init__(self, attrs=None, jsignature_attrs=None):
|
||||
super(JSignatureWidget, self).__init__(attrs)
|
||||
# Store jSignature js config
|
||||
self.jsignature_attrs = jsignature_attrs or {}
|
||||
|
||||
def render(self, name, value, attrs=None):
|
||||
|
||||
# Build config
|
||||
jsign_id = 'jsign_%s' % name
|
||||
jsignature_config = JSIGNATURE_DEFAULT_CONFIG.copy()
|
||||
jsignature_config.update(self.jsignature_attrs)
|
||||
|
||||
# Build output
|
||||
hidden_input = super(JSignatureWidget, self).render(name, value, attrs)
|
||||
div = u'<div id="%s" class="jsign-container"></div>' % jsign_id
|
||||
clr = u'<input type="button" value="%s" class="btn">' % _('Reset')
|
||||
js = u'$("#%s").jSignature(%s);' % (jsign_id, json.dumps(jsignature_config))
|
||||
js += u'$("#%s").jSignature("setData", %s,"native");' % (jsign_id, json.dumps(value))
|
||||
js = u'<script type="text/javascript">%s</script>' % js
|
||||
out = u'<div class="jsign-wrapper">%s%s%s%s</div>' % (hidden_input, div, clr, js)
|
||||
|
||||
return mark_safe(out)
|
||||
Reference in New Issue
Block a user