You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1404 lines
52 KiB

  1. /** @preserve
  2. jSignature v2 "${buildDate}" "${commitID}"
  3. Copyright (c) 2012 Willow Systems Corp http://willow-systems.com
  4. Copyright (c) 2010 Brinley Ang http://www.unbolt.net
  5. MIT License <http://www.opensource.org/licenses/mit-license.php>
  6. */
  7. ;(function() {
  8. var apinamespace = 'jSignature'
  9. /**
  10. Allows one to delay certain eventual action by setting up a timer for it and allowing one to delay it
  11. by "kick"ing it. Sorta like "kick the can down the road"
  12. @public
  13. @class
  14. @param
  15. @returns {Type}
  16. */
  17. var KickTimerClass = function(time, callback) {
  18. var timer
  19. this.kick = function() {
  20. clearTimeout(timer)
  21. timer = setTimeout(
  22. callback
  23. , time
  24. )
  25. }
  26. this.clear = function() {
  27. clearTimeout(timer)
  28. }
  29. return this
  30. }
  31. var PubSubClass = function(context){
  32. 'use strict'
  33. /* @preserve
  34. -----------------------------------------------------------------------------------------------
  35. JavaScript PubSub library
  36. 2012 (c) Willow Systems Corp (www.willow-systems.com)
  37. based on Peter Higgins (dante@dojotoolkit.org)
  38. Loosely based on Dojo publish/subscribe API, limited in scope. Rewritten blindly.
  39. Original is (c) Dojo Foundation 2004-2010. Released under either AFL or new BSD, see:
  40. http://dojofoundation.org/license for more information.
  41. -----------------------------------------------------------------------------------------------
  42. */
  43. this.topics = {}
  44. // here we choose what will be "this" for the called events.
  45. // if context is defined, it's context. Else, 'this' is this instance of PubSub
  46. this.context = context ? context : this
  47. /**
  48. * Allows caller to emit an event and pass arguments to event listeners.
  49. * @public
  50. * @function
  51. * @param topic {String} Name of the channel on which to voice this event
  52. * @param **arguments Any number of arguments you want to pass to the listeners of this event.
  53. */
  54. this.publish = function(topic, arg1, arg2, etc) {
  55. 'use strict'
  56. if (this.topics[topic]) {
  57. var currentTopic = this.topics[topic]
  58. , args = Array.prototype.slice.call(arguments, 1)
  59. , toremove = []
  60. , fn
  61. , i, l
  62. , pair
  63. for (i = 0, l = currentTopic.length; i < l; i++) {
  64. pair = currentTopic[i] // this is a [function, once_flag] array
  65. fn = pair[0]
  66. if (pair[1] /* 'run once' flag set */){
  67. pair[0] = function(){}
  68. toremove.push(i)
  69. }
  70. fn.apply(this.context, args)
  71. }
  72. for (i = 0, l = toremove.length; i < l; i++) {
  73. currentTopic.splice(toremove[i], 1)
  74. }
  75. }
  76. }
  77. /**
  78. * Allows listener code to subscribe to channel and be called when data is available
  79. * @public
  80. * @function
  81. * @param topic {String} Name of the channel on which to voice this event
  82. * @param callback {Function} Executable (function pointer) that will be ran when event is voiced on this channel.
  83. * @param once {Boolean} (optional. False by default) Flag indicating if the function is to be triggered only once.
  84. * @returns {Object} A token object that cen be used for unsubscribing.
  85. */
  86. this.subscribe = function(topic, callback, once) {
  87. 'use strict'
  88. if (!this.topics[topic]) {
  89. this.topics[topic] = [[callback, once]];
  90. } else {
  91. this.topics[topic].push([callback,once]);
  92. }
  93. return {
  94. "topic": topic,
  95. "callback": callback
  96. };
  97. };
  98. /**
  99. * Allows listener code to unsubscribe from a channel
  100. * @public
  101. * @function
  102. * @param token {Object} A token object that was returned by `subscribe` method
  103. */
  104. this.unsubscribe = function(token) {
  105. if (this.topics[token.topic]) {
  106. var currentTopic = this.topics[token.topic]
  107. for (var i = 0, l = currentTopic.length; i < l; i++) {
  108. if (currentTopic[i][0] === token.callback) {
  109. currentTopic.splice(i, 1)
  110. }
  111. }
  112. }
  113. }
  114. }
  115. /// Returns front, back and "decor" colors derived from element (as jQuery obj)
  116. function getColors($e){
  117. var tmp
  118. , undef
  119. , frontcolor = $e.css('color')
  120. , backcolor
  121. , e = $e[0]
  122. var toOfDOM = false
  123. while(e && !backcolor && !toOfDOM){
  124. try{
  125. tmp = $(e).css('background-color')
  126. } catch (ex) {
  127. tmp = 'transparent'
  128. }
  129. if (tmp !== 'transparent' && tmp !== 'rgba(0, 0, 0, 0)'){
  130. backcolor = tmp
  131. }
  132. toOfDOM = e.body
  133. e = e.parentNode
  134. }
  135. var rgbaregex = /rgb[a]*\((\d+),\s*(\d+),\s*(\d+)/ // modern browsers
  136. , hexregex = /#([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})/ // IE 8 and less.
  137. , frontcolorcomponents
  138. // Decomposing Front color into R, G, B ints
  139. tmp = undef
  140. tmp = frontcolor.match(rgbaregex)
  141. if (tmp){
  142. frontcolorcomponents = {'r':parseInt(tmp[1],10),'g':parseInt(tmp[2],10),'b':parseInt(tmp[3],10)}
  143. } else {
  144. tmp = frontcolor.match(hexregex)
  145. if (tmp) {
  146. frontcolorcomponents = {'r':parseInt(tmp[1],16),'g':parseInt(tmp[2],16),'b':parseInt(tmp[3],16)}
  147. }
  148. }
  149. // if(!frontcolorcomponents){
  150. // frontcolorcomponents = {'r':255,'g':255,'b':255}
  151. // }
  152. var backcolorcomponents
  153. // Decomposing back color into R, G, B ints
  154. if(!backcolor){
  155. // HIghly unlikely since this means that no background styling was applied to any element from here to top of dom.
  156. // we'll pick up back color from front color
  157. if(frontcolorcomponents){
  158. if (Math.max.apply(null, [frontcolorcomponents.r, frontcolorcomponents.g, frontcolorcomponents.b]) > 127){
  159. backcolorcomponents = {'r':0,'g':0,'b':0}
  160. } else {
  161. backcolorcomponents = {'r':255,'g':255,'b':255}
  162. }
  163. } else {
  164. // arg!!! front color is in format we don't understand (hsl, named colors)
  165. // Let's just go with white background.
  166. backcolorcomponents = {'r':255,'g':255,'b':255}
  167. }
  168. } else {
  169. tmp = undef
  170. tmp = backcolor.match(rgbaregex)
  171. if (tmp){
  172. backcolorcomponents = {'r':parseInt(tmp[1],10),'g':parseInt(tmp[2],10),'b':parseInt(tmp[3],10)}
  173. } else {
  174. tmp = backcolor.match(hexregex)
  175. if (tmp) {
  176. backcolorcomponents = {'r':parseInt(tmp[1],16),'g':parseInt(tmp[2],16),'b':parseInt(tmp[3],16)}
  177. }
  178. }
  179. // if(!backcolorcomponents){
  180. // backcolorcomponents = {'r':0,'g':0,'b':0}
  181. // }
  182. }
  183. // Deriving Decor color
  184. // THis is LAZY!!!! Better way would be to use HSL and adjust luminocity. However, that could be an overkill.
  185. var toRGBfn = function(o){return 'rgb(' + [o.r, o.g, o.b].join(', ') + ')'}
  186. , decorcolorcomponents
  187. , frontcolorbrightness
  188. , adjusted
  189. if (frontcolorcomponents && backcolorcomponents){
  190. var backcolorbrightness = Math.max.apply(null, [frontcolorcomponents.r, frontcolorcomponents.g, frontcolorcomponents.b])
  191. frontcolorbrightness = Math.max.apply(null, [backcolorcomponents.r, backcolorcomponents.g, backcolorcomponents.b])
  192. adjusted = Math.round(frontcolorbrightness + (-1 * (frontcolorbrightness - backcolorbrightness) * 0.75)) // "dimming" the difference between pen and back.
  193. decorcolorcomponents = {'r':adjusted,'g':adjusted,'b':adjusted} // always shade of gray
  194. } else if (frontcolorcomponents) {
  195. frontcolorbrightness = Math.max.apply(null, [frontcolorcomponents.r, frontcolorcomponents.g, frontcolorcomponents.b])
  196. var polarity = +1
  197. if (frontcolorbrightness > 127){
  198. polarity = -1
  199. }
  200. // shifting by 25% (64 points on RGB scale)
  201. adjusted = Math.round(frontcolorbrightness + (polarity * 96)) // "dimming" the pen's color by 75% to get decor color.
  202. decorcolorcomponents = {'r':adjusted,'g':adjusted,'b':adjusted} // always shade of gray
  203. } else {
  204. decorcolorcomponents = {'r':191,'g':191,'b':191} // always shade of gray
  205. }
  206. return {
  207. 'color': frontcolor
  208. , 'background-color': backcolorcomponents? toRGBfn(backcolorcomponents) : backcolor
  209. , 'decor-color': toRGBfn(decorcolorcomponents)
  210. }
  211. }
  212. function Vector(x,y){
  213. this.x = x
  214. this.y = y
  215. this.reverse = function(){
  216. return new this.constructor(
  217. this.x * -1
  218. , this.y * -1
  219. )
  220. }
  221. this._length = null
  222. this.getLength = function(){
  223. if (!this._length){
  224. this._length = Math.sqrt( Math.pow(this.x, 2) + Math.pow(this.y, 2) )
  225. }
  226. return this._length
  227. }
  228. var polarity = function (e){
  229. return Math.round(e / Math.abs(e))
  230. }
  231. this.resizeTo = function(length){
  232. // proportionally changes x,y such that the hypotenuse (vector length) is = new length
  233. if (this.x === 0 && this.y === 0){
  234. this._length = 0
  235. } else if (this.x === 0){
  236. this._length = length
  237. this.y = length * polarity(this.y)
  238. } else if(this.y === 0){
  239. this._length = length
  240. this.x = length * polarity(this.x)
  241. } else {
  242. var proportion = Math.abs(this.y / this.x)
  243. , x = Math.sqrt(Math.pow(length, 2) / (1 + Math.pow(proportion, 2)))
  244. , y = proportion * x
  245. this._length = length
  246. this.x = x * polarity(this.x)
  247. this.y = y * polarity(this.y)
  248. }
  249. return this
  250. }
  251. /**
  252. * Calculates the angle between 'this' vector and another.
  253. * @public
  254. * @function
  255. * @returns {Number} The angle between the two vectors as measured in PI.
  256. */
  257. this.angleTo = function(vectorB) {
  258. var divisor = this.getLength() * vectorB.getLength()
  259. if (divisor === 0) {
  260. return 0
  261. } else {
  262. // JavaScript floating point math is screwed up.
  263. // because of it, the core of the formula can, on occasion, have values
  264. // over 1.0 and below -1.0.
  265. return Math.acos(
  266. Math.min(
  267. Math.max(
  268. ( this.x * vectorB.x + this.y * vectorB.y ) / divisor
  269. , -1.0
  270. )
  271. , 1.0
  272. )
  273. ) / Math.PI
  274. }
  275. }
  276. }
  277. function Point(x,y){
  278. this.x = x
  279. this.y = y
  280. this.getVectorToCoordinates = function (x, y) {
  281. return new Vector(x - this.x, y - this.y)
  282. }
  283. this.getVectorFromCoordinates = function (x, y) {
  284. return this.getVectorToCoordinates(x, y).reverse()
  285. }
  286. this.getVectorToPoint = function (point) {
  287. return new Vector(point.x - this.x, point.y - this.y)
  288. }
  289. this.getVectorFromPoint = function (point) {
  290. return this.getVectorToPoint(point).reverse()
  291. }
  292. }
  293. /*
  294. * About data structure:
  295. * We don't store / deal with "pictures" this signature capture code captures "vectors"
  296. *
  297. * We don't store bitmaps. We store "strokes" as arrays of arrays. (Actually, arrays of objects containing arrays of coordinates.
  298. *
  299. * Stroke = mousedown + mousemoved * n (+ mouseup but we don't record that as that was the "end / lack of movement" indicator)
  300. *
  301. * Vectors = not classical vectors where numbers indicated shift relative last position. Our vectors are actually coordinates against top left of canvas.
  302. * we could calc the classical vectors, but keeping the the actual coordinates allows us (through Math.max / min)
  303. * to calc the size of resulting drawing very quickly. If we want classical vectors later, we can always get them in backend code.
  304. *
  305. * So, the data structure:
  306. *
  307. * var data = [
  308. * { // stroke starts
  309. * x : [101, 98, 57, 43] // x points
  310. * , y : [1, 23, 65, 87] // y points
  311. * } // stroke ends
  312. * , { // stroke starts
  313. * x : [55, 56, 57, 58] // x points
  314. * , y : [101, 97, 54, 4] // y points
  315. * } // stroke ends
  316. * , { // stroke consisting of just a dot
  317. * x : [53] // x points
  318. * , y : [151] // y points
  319. * } // stroke ends
  320. * ]
  321. *
  322. * we don't care or store stroke width (it's canvas-size-relative), color, shadow values. These can be added / changed on whim post-capture.
  323. *
  324. */
  325. function DataEngine(storageObject, context, startStrokeFn, addToStrokeFn, endStrokeFn){
  326. this.data = storageObject // we expect this to be an instance of Array
  327. this.context = context
  328. if (storageObject.length){
  329. // we have data to render
  330. var numofstrokes = storageObject.length
  331. , stroke
  332. , numofpoints
  333. for (var i = 0; i < numofstrokes; i++){
  334. stroke = storageObject[i]
  335. numofpoints = stroke.x.length
  336. startStrokeFn.call(context, stroke)
  337. for(var j = 1; j < numofpoints; j++){
  338. addToStrokeFn.call(context, stroke, j)
  339. }
  340. endStrokeFn.call(context, stroke)
  341. }
  342. }
  343. this.changed = function(){}
  344. this.startStrokeFn = startStrokeFn
  345. this.addToStrokeFn = addToStrokeFn
  346. this.endStrokeFn = endStrokeFn
  347. this.inStroke = false
  348. this._lastPoint = null
  349. this._stroke = null
  350. this.startStroke = function(point){
  351. if(point && typeof(point.x) == "number" && typeof(point.y) == "number"){
  352. this._stroke = {'x':[point.x], 'y':[point.y]}
  353. this.data.push(this._stroke)
  354. this._lastPoint = point
  355. this.inStroke = true
  356. // 'this' does not work same inside setTimeout(
  357. var stroke = this._stroke
  358. , fn = this.startStrokeFn
  359. , context = this.context
  360. setTimeout(
  361. // some IE's don't support passing args per setTimeout API. Have to create closure every time instead.
  362. function() {fn.call(context, stroke)}
  363. , 3
  364. )
  365. return point
  366. } else {
  367. return null
  368. }
  369. }
  370. // that "5" at the very end of this if is important to explain.
  371. // we do NOT render links between two captured points (in the middle of the stroke) if the distance is shorter than that number.
  372. // not only do we NOT render it, we also do NOT capture (add) these intermediate points to storage.
  373. // when clustering of these is too tight, it produces noise on the line, which, because of smoothing, makes lines too curvy.
  374. // maybe, later, we can expose this as a configurable setting of some sort.
  375. this.addToStroke = function(point){
  376. if (this.inStroke &&
  377. typeof(point.x) === "number" &&
  378. typeof(point.y) === "number" &&
  379. // calculates absolute shift in diagonal pixels away from original point
  380. (Math.abs(point.x - this._lastPoint.x) + Math.abs(point.y - this._lastPoint.y)) > 4
  381. ){
  382. var positionInStroke = this._stroke.x.length
  383. this._stroke.x.push(point.x)
  384. this._stroke.y.push(point.y)
  385. this._lastPoint = point
  386. var stroke = this._stroke
  387. , fn = this.addToStrokeFn
  388. , context = this.context
  389. setTimeout(
  390. // some IE's don't support passing args per setTimeout API. Have to create closure every time instead.
  391. function() {fn.call(context, stroke, positionInStroke)}
  392. , 3
  393. )
  394. return point
  395. } else {
  396. return null
  397. }
  398. }
  399. this.endStroke = function(){
  400. var c = this.inStroke
  401. this.inStroke = false
  402. this._lastPoint = null
  403. if (c){
  404. var stroke = this._stroke
  405. , fn = this.endStrokeFn // 'this' does not work same inside setTimeout(
  406. , context = this.context
  407. , changedfn = this.changed
  408. setTimeout(
  409. // some IE's don't support passing args per setTimeout API. Have to create closure every time instead.
  410. function(){
  411. fn.call(context, stroke)
  412. changedfn.call(context)
  413. }
  414. , 3
  415. )
  416. return true
  417. } else {
  418. return null
  419. }
  420. }
  421. }
  422. var basicDot = function(ctx, x, y, size){
  423. var fillStyle = ctx.fillStyle
  424. ctx.fillStyle = ctx.strokeStyle
  425. ctx.fillRect(x + size / -2 , y + size / -2, size, size)
  426. ctx.fillStyle = fillStyle
  427. }
  428. , basicLine = function(ctx, startx, starty, endx, endy){
  429. ctx.beginPath()
  430. ctx.moveTo(startx, starty)
  431. ctx.lineTo(endx, endy)
  432. ctx.stroke()
  433. }
  434. , basicCurve = function(ctx, startx, starty, endx, endy, cp1x, cp1y, cp2x, cp2y){
  435. ctx.beginPath()
  436. ctx.moveTo(startx, starty)
  437. ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, endx, endy)
  438. ctx.stroke()
  439. }
  440. , strokeStartCallback = function(stroke) {
  441. // this = jSignatureClass instance
  442. basicDot(this.canvasContext, stroke.x[0], stroke.y[0], this.settings.lineWidth)
  443. }
  444. , strokeAddCallback = function(stroke, positionInStroke){
  445. // this = jSignatureClass instance
  446. // Because we are funky this way, here we draw TWO curves.
  447. // 1. POSSIBLY "this line" - spanning from point right before us, to this latest point.
  448. // 2. POSSIBLY "prior curve" - spanning from "latest point" to the one before it.
  449. // Why you ask?
  450. // long lines (ones with many pixels between them) do not look good when they are part of a large curvy stroke.
  451. // You know, the jaggedy crocodile spine instead of a pretty, smooth curve. Yuck!
  452. // We want to approximate pretty curves in-place of those ugly lines.
  453. // To approximate a very nice curve we need to know the direction of line before and after.
  454. // Hence, on long lines we actually wait for another point beyond it to come back from
  455. // mousemoved before we draw this curve.
  456. // So for "prior curve" to be calc'ed we need 4 points
  457. // A, B, C, D (we are on D now, A is 3 points in the past.)
  458. // and 3 lines:
  459. // pre-line (from points A to B),
  460. // this line (from points B to C), (we call it "this" because if it was not yet, it's the only one we can draw for sure.)
  461. // post-line (from points C to D) (even through D point is 'current' we don't know how we can draw it yet)
  462. //
  463. // Well, actually, we don't need to *know* the point A, just the vector A->B
  464. var Cpoint = new Point(stroke.x[positionInStroke-1], stroke.y[positionInStroke-1])
  465. , Dpoint = new Point(stroke.x[positionInStroke], stroke.y[positionInStroke])
  466. , CDvector = Cpoint.getVectorToPoint(Dpoint)
  467. // Again, we have a chance here to draw TWO things:
  468. // BC Curve (only if it's long, because if it was short, it was drawn by previous callback) and
  469. // CD Line (only if it's short)
  470. // So, let's start with BC curve.
  471. // if there is only 2 points in stroke array, we don't have "history" long enough to have point B, let alone point A.
  472. // Falling through to drawing line CD is proper, as that's the only line we have points for.
  473. if(positionInStroke > 1) {
  474. // we are here when there are at least 3 points in stroke array.
  475. var Bpoint = new Point(stroke.x[positionInStroke-2], stroke.y[positionInStroke-2])
  476. , BCvector = Bpoint.getVectorToPoint(Cpoint)
  477. , ABvector
  478. if(BCvector.getLength() > this.lineCurveThreshold){
  479. // Yey! Pretty curves, here we come!
  480. if(positionInStroke > 2) {
  481. // we are here when at least 4 points in stroke array.
  482. ABvector = (new Point(stroke.x[positionInStroke-3], stroke.y[positionInStroke-3])).getVectorToPoint(Bpoint)
  483. } else {
  484. ABvector = new Vector(0,0)
  485. }
  486. var minlenfraction = 0.05
  487. , maxlen = BCvector.getLength() * 0.35
  488. , ABCangle = BCvector.angleTo(ABvector.reverse())
  489. , BCDangle = CDvector.angleTo(BCvector.reverse())
  490. , BCP1vector = new Vector(ABvector.x + BCvector.x, ABvector.y + BCvector.y).resizeTo(
  491. Math.max(minlenfraction, ABCangle) * maxlen
  492. )
  493. , CCP2vector = (new Vector(BCvector.x + CDvector.x, BCvector.y + CDvector.y)).reverse().resizeTo(
  494. Math.max(minlenfraction, BCDangle) * maxlen
  495. )
  496. basicCurve(
  497. this.canvasContext
  498. , Bpoint.x
  499. , Bpoint.y
  500. , Cpoint.x
  501. , Cpoint.y
  502. , Bpoint.x + BCP1vector.x
  503. , Bpoint.y + BCP1vector.y
  504. , Cpoint.x + CCP2vector.x
  505. , Cpoint.y + CCP2vector.y
  506. )
  507. }
  508. }
  509. if(CDvector.getLength() <= this.lineCurveThreshold){
  510. basicLine(
  511. this.canvasContext
  512. , Cpoint.x
  513. , Cpoint.y
  514. , Dpoint.x
  515. , Dpoint.y
  516. )
  517. }
  518. }
  519. , strokeEndCallback = function(stroke){
  520. // this = jSignatureClass instance
  521. // Here we tidy up things left unfinished in last strokeAddCallback run.
  522. // What's POTENTIALLY left unfinished there is the curve between the last points
  523. // in the stroke, if the len of that line is more than lineCurveThreshold
  524. // If the last line was shorter than lineCurveThreshold, it was drawn there, and there
  525. // is nothing for us here to do.
  526. // We can also be called when there is only one point in the stroke (meaning, the
  527. // stroke was just a dot), in which case, again, there is nothing for us to do.
  528. // So for "this curve" to be calc'ed we need 3 points
  529. // A, B, C
  530. // and 2 lines:
  531. // pre-line (from points A to B),
  532. // this line (from points B to C)
  533. // Well, actually, we don't need to *know* the point A, just the vector A->B
  534. // so, we really need points B, C and AB vector.
  535. var positionInStroke = stroke.x.length - 1
  536. if (positionInStroke > 0){
  537. // there are at least 2 points in the stroke.we are in business.
  538. var Cpoint = new Point(stroke.x[positionInStroke], stroke.y[positionInStroke])
  539. , Bpoint = new Point(stroke.x[positionInStroke-1], stroke.y[positionInStroke-1])
  540. , BCvector = Bpoint.getVectorToPoint(Cpoint)
  541. , ABvector
  542. if (BCvector.getLength() > this.lineCurveThreshold){
  543. // yep. This one was left undrawn in prior callback. Have to draw it now.
  544. if (positionInStroke > 1){
  545. // we have at least 3 elems in stroke
  546. ABvector = (new Point(stroke.x[positionInStroke-2], stroke.y[positionInStroke-2])).getVectorToPoint(Bpoint)
  547. var BCP1vector = new Vector(ABvector.x + BCvector.x, ABvector.y + BCvector.y).resizeTo(BCvector.getLength() / 2)
  548. basicCurve(
  549. this.canvasContext
  550. , Bpoint.x
  551. , Bpoint.y
  552. , Cpoint.x
  553. , Cpoint.y
  554. , Bpoint.x + BCP1vector.x
  555. , Bpoint.y + BCP1vector.y
  556. , Cpoint.x
  557. , Cpoint.y
  558. )
  559. } else {
  560. // Since there is no AB leg, there is no curve to draw. This line is still "long" but no curve.
  561. basicLine(
  562. this.canvasContext
  563. , Bpoint.x
  564. , Bpoint.y
  565. , Cpoint.x
  566. , Cpoint.y
  567. )
  568. }
  569. }
  570. }
  571. }
  572. /*
  573. var getDataStats = function(){
  574. var strokecnt = strokes.length
  575. , stroke
  576. , pointid
  577. , pointcnt
  578. , x, y
  579. , maxX = Number.NEGATIVE_INFINITY
  580. , maxY = Number.NEGATIVE_INFINITY
  581. , minX = Number.POSITIVE_INFINITY
  582. , minY = Number.POSITIVE_INFINITY
  583. for(strokeid = 0; strokeid < strokecnt; strokeid++){
  584. stroke = strokes[strokeid]
  585. pointcnt = stroke.length
  586. for(pointid = 0; pointid < pointcnt; pointid++){
  587. x = stroke.x[pointid]
  588. y = stroke.y[pointid]
  589. if (x > maxX){
  590. maxX = x
  591. } else if (x < minX) {
  592. minX = x
  593. }
  594. if (y > maxY){
  595. maxY = y
  596. } else if (y < minY) {
  597. minY = y
  598. }
  599. }
  600. }
  601. return {'maxX': maxX, 'minX': minX, 'maxY': maxY, 'minY': minY}
  602. }
  603. */
  604. function conditionallyLinkCanvasResizeToWindowResize(jSignatureInstance, settingsWidth, apinamespace, globalEvents){
  605. 'use strict'
  606. if ( settingsWidth === 'ratio' || settingsWidth.split('')[settingsWidth.length - 1] === '%' ) {
  607. this.eventTokens[apinamespace + '.parentresized'] = globalEvents.subscribe(
  608. apinamespace + '.parentresized'
  609. , (function(eventTokens, $parent, originalParentWidth, sizeRatio){
  610. 'use strict'
  611. return function(){
  612. 'use strict'
  613. var w = $parent.width()
  614. if (w !== originalParentWidth) {
  615. // UNsubscribing this particular instance of signature pad only.
  616. // there is a separate `eventTokens` per each instance of signature pad
  617. for (var key in eventTokens){
  618. if (eventTokens.hasOwnProperty(key)) {
  619. globalEvents.unsubscribe(eventTokens[key])
  620. delete eventTokens[key]
  621. }
  622. }
  623. var settings = jSignatureInstance.settings
  624. jSignatureInstance.$parent.children().remove()
  625. for (var key in jSignatureInstance){
  626. if (jSignatureInstance.hasOwnProperty(key)) {
  627. delete jSignatureInstance[key]
  628. }
  629. }
  630. // scale data to new signature pad size
  631. settings.data = (function(data, scale){
  632. var newData = []
  633. var o, i, l, j, m, stroke
  634. for ( i = 0, l = data.length; i < l; i++) {
  635. stroke = data[i]
  636. o = {'x':[],'y':[]}
  637. for ( j = 0, m = stroke.x.length; j < m; j++) {
  638. o.x.push(stroke.x[j] * scale)
  639. o.y.push(stroke.y[j] * scale)
  640. }
  641. newData.push(o)
  642. }
  643. return newData
  644. })(
  645. settings.data
  646. , w * 1.0 / originalParentWidth
  647. )
  648. $parent[apinamespace](settings)
  649. }
  650. }
  651. })(
  652. this.eventTokens
  653. , this.$parent
  654. , this.$parent.width()
  655. , this.canvas.width * 1.0 / this.canvas.height
  656. )
  657. )
  658. }
  659. }
  660. function jSignatureClass(parent, options, instanceExtensions) {
  661. var $parent = this.$parent = $(parent)
  662. , eventTokens = this.eventTokens = {}
  663. , events = this.events = new PubSubClass(this)
  664. , globalEvents = $.fn[apinamespace]('globalEvents')
  665. , settings = {
  666. 'width' : 'ratio'
  667. ,'height' : 'ratio'
  668. ,'sizeRatio': 4 // only used when height = 'ratio'
  669. ,'color' : '#000'
  670. ,'background-color': '#fff'
  671. ,'decor-color': '#eee'
  672. ,'lineWidth' : 0
  673. ,'minFatFingerCompensation' : -10
  674. ,'showUndoButton': false
  675. ,'data': []
  676. }
  677. $.extend(settings, getColors($parent))
  678. if (options) {
  679. $.extend(settings, options)
  680. }
  681. this.settings = settings
  682. for (var extensionName in instanceExtensions){
  683. if (instanceExtensions.hasOwnProperty(extensionName)) {
  684. instanceExtensions[extensionName].call(this, extensionName)
  685. }
  686. }
  687. this.events.publish(apinamespace+'.initializing')
  688. // these, when enabled, will hover above the sig area. Hence we append them to DOM before canvas.
  689. this.$controlbarUpper = (function(){
  690. var controlbarstyle = 'padding:0 !important;margin:0 !important;'+
  691. 'width: 100% !important; height: 0 !important;'+
  692. 'margin-top:-1em !important;margin-bottom:1em !important;'
  693. return $('<div style="'+controlbarstyle+'"></div>').appendTo($parent)
  694. })();
  695. this.isCanvasEmulator = false // will be flipped by initializer when needed.
  696. var canvas = this.canvas = this.initializeCanvas(settings)
  697. , $canvas = $(canvas)
  698. this.$controlbarLower = (function(){
  699. var controlbarstyle = 'padding:0 !important;margin:0 !important;'+
  700. 'width: 100% !important; height: 0 !important;'+
  701. 'margin-top:-1.5em !important;margin-bottom:1.5em !important;'
  702. return $('<div style="'+controlbarstyle+'"></div>').appendTo($parent)
  703. })();
  704. this.canvasContext = canvas.getContext("2d")
  705. // Most of our exposed API will be looking for this:
  706. $canvas.data(apinamespace + '.this', this)
  707. settings.lineWidth = (function(defaultLineWidth, canvasWidth){
  708. if (!defaultLineWidth){
  709. return Math.max(
  710. Math.round(canvasWidth / 400) /*+1 pixel for every extra 300px of width.*/
  711. , 2 /* minimum line width */
  712. )
  713. } else {
  714. return defaultLineWidth
  715. }
  716. })(settings.lineWidth, canvas.width);
  717. this.lineCurveThreshold = settings.lineWidth * 3
  718. // Add custom class if defined
  719. if(settings.cssclass && $.trim(settings.cssclass) != "") {
  720. $canvas.addClass(settings.cssclass)
  721. }
  722. // used for shifting the drawing point up on touch devices, so one can see the drawing above the finger.
  723. this.fatFingerCompensation = 0
  724. var movementHandlers = (function(jSignatureInstance) {
  725. //================================
  726. // mouse down, move, up handlers:
  727. // shifts - adjustment values in viewport pixels drived from position of canvas on the page
  728. var shiftX
  729. , shiftY
  730. , setStartValues = function(){
  731. var tos = $(jSignatureInstance.canvas).offset()
  732. shiftX = tos.left * -1
  733. shiftY = tos.top * -1
  734. }
  735. , getPointFromEvent = function(e) {
  736. var firstEvent = (e.changedTouches && e.changedTouches.length > 0 ? e.changedTouches[0] : e)
  737. // All devices i tried report correct coordinates in pageX,Y
  738. // Android Chrome 2.3.x, 3.1, 3.2., Opera Mobile, safari iOS 4.x,
  739. // Windows: Chrome, FF, IE9, Safari
  740. // None of that scroll shift calc vs screenXY other sigs do is needed.
  741. // ... oh, yeah, the "fatFinger.." is for tablets so that people see what they draw.
  742. return new Point(
  743. Math.round(firstEvent.pageX + shiftX)
  744. , Math.round(firstEvent.pageY + shiftY) + jSignatureInstance.fatFingerCompensation
  745. )
  746. }
  747. , timer = new KickTimerClass(
  748. 750
  749. , function() { jSignatureInstance.dataEngine.endStroke() }
  750. )
  751. this.drawEndHandler = function(e) {
  752. try { e.preventDefault() } catch (ex) {}
  753. timer.clear()
  754. jSignatureInstance.dataEngine.endStroke()
  755. }
  756. this.drawStartHandler = function(e) {
  757. e.preventDefault()
  758. // for performance we cache the offsets
  759. // we recalc these only at the beginning the stroke
  760. setStartValues()
  761. jSignatureInstance.dataEngine.startStroke( getPointFromEvent(e) )
  762. timer.kick()
  763. }
  764. this.drawMoveHandler = function(e) {
  765. e.preventDefault()
  766. if (!jSignatureInstance.dataEngine.inStroke){
  767. return
  768. }
  769. jSignatureInstance.dataEngine.addToStroke( getPointFromEvent(e) )
  770. timer.kick()
  771. }
  772. return this
  773. }).call( {}, this )
  774. //
  775. //================================
  776. ;(function(drawEndHandler, drawStartHandler, drawMoveHandler) {
  777. var canvas = this.canvas
  778. , $canvas = $(canvas)
  779. , undef
  780. if (this.isCanvasEmulator){
  781. $canvas.bind('mousemove.'+apinamespace, drawMoveHandler)
  782. $canvas.bind('mouseup.'+apinamespace, drawEndHandler)
  783. $canvas.bind('mousedown.'+apinamespace, drawStartHandler)
  784. } else {
  785. canvas.ontouchstart = function(e) {
  786. canvas.onmousedown = undef
  787. canvas.onmouseup = undef
  788. canvas.onmousemove = undef
  789. this.fatFingerCompensation = (
  790. settings.minFatFingerCompensation &&
  791. settings.lineWidth * -3 > settings.minFatFingerCompensation
  792. ) ? settings.lineWidth * -3 : settings.minFatFingerCompensation
  793. drawStartHandler(e)
  794. canvas.ontouchend = drawEndHandler
  795. canvas.ontouchstart = drawStartHandler
  796. canvas.ontouchmove = drawMoveHandler
  797. }
  798. canvas.onmousedown = function(e) {
  799. canvas.ontouchstart = undef
  800. canvas.ontouchend = undef
  801. canvas.ontouchmove = undef
  802. drawStartHandler(e)
  803. canvas.onmousedown = drawStartHandler
  804. canvas.onmouseup = drawEndHandler
  805. canvas.onmousemove = drawMoveHandler
  806. }
  807. }
  808. }).call(
  809. this
  810. , movementHandlers.drawEndHandler
  811. , movementHandlers.drawStartHandler
  812. , movementHandlers.drawMoveHandler
  813. )
  814. //=========================================
  815. // various event handlers
  816. // on mouseout + mouseup canvas did not know that mouseUP fired. Continued to draw despite mouse UP.
  817. // it is bettr than
  818. // $canvas.bind('mouseout', drawEndHandler)
  819. // because we don't want to break the stroke where user accidentally gets ouside and wants to get back in quickly.
  820. eventTokens[apinamespace + '.windowmouseup'] = globalEvents.subscribe(
  821. apinamespace + '.windowmouseup'
  822. , movementHandlers.drawEndHandler
  823. )
  824. this.events.publish(apinamespace+'.attachingEventHandlers')
  825. // If we have proportional width, we sign up to events broadcasting "window resized" and checking if
  826. // parent's width changed. If so, we (1) extract settings + data from current signature pad,
  827. // (2) remove signature pad from parent, and (3) reinit new signature pad at new size with same settings, (rescaled) data.
  828. conditionallyLinkCanvasResizeToWindowResize.call(
  829. this
  830. , this
  831. , settings.width.toString(10)
  832. , apinamespace, globalEvents
  833. )
  834. // end of event handlers.
  835. // ===============================
  836. this.resetCanvas(settings.data)
  837. // resetCanvas renders the data on the screen and fires ONE "change" event
  838. // if there is data. If you have controls that rely on "change" firing
  839. // attach them to something that runs before this.resetCanvas, like
  840. // apinamespace+'.attachingEventHandlers' that fires a bit higher.
  841. this.events.publish(apinamespace+'.initialized')
  842. return this
  843. } // end of initBase
  844. //=========================================================================
  845. // jSignatureClass's methods and supporting fn's
  846. jSignatureClass.prototype.resetCanvas = function(data){
  847. var canvas = this.canvas
  848. , settings = this.settings
  849. , ctx = this.canvasContext
  850. , isCanvasEmulator = this.isCanvasEmulator
  851. , cw = canvas.width
  852. , ch = canvas.height
  853. // preparing colors, drawing area
  854. ctx.clearRect(0, 0, cw + 30, ch + 30)
  855. ctx.shadowColor = ctx.fillStyle = settings['background-color']
  856. if (isCanvasEmulator){
  857. // FLashCanvas fills with Black by default, covering up the parent div's background
  858. // hence we refill
  859. ctx.fillRect(0,0,cw + 30, ch + 30)
  860. }
  861. ctx.lineWidth = Math.ceil(parseInt(settings.lineWidth, 10))
  862. ctx.lineCap = ctx.lineJoin = "round"
  863. // signature line
  864. ctx.strokeStyle = settings['decor-color']
  865. ctx.shadowOffsetX = 0
  866. ctx.shadowOffsetY = 0
  867. var lineoffset = Math.round( ch / 5 )
  868. basicLine(ctx, lineoffset * 1.5, ch - lineoffset, cw - (lineoffset * 1.5), ch - lineoffset)
  869. ctx.strokeStyle = settings.color
  870. if (!isCanvasEmulator){
  871. ctx.shadowColor = ctx.strokeStyle
  872. ctx.shadowOffsetX = ctx.lineWidth * 0.5
  873. ctx.shadowOffsetY = ctx.lineWidth * -0.6
  874. ctx.shadowBlur = 0
  875. }
  876. // setting up new dataEngine
  877. if (!data) { data = [] }
  878. var dataEngine = this.dataEngine = new DataEngine(
  879. data
  880. , this
  881. , strokeStartCallback
  882. , strokeAddCallback
  883. , strokeEndCallback
  884. )
  885. settings.data = data // onwindowresize handler uses it, i think.
  886. $(canvas).data(apinamespace+'.data', data)
  887. .data(apinamespace+'.settings', settings)
  888. // we fire "change" event on every change in data.
  889. // setting this up:
  890. dataEngine.changed = (function(target, events, apinamespace) {
  891. 'use strict'
  892. return function() {
  893. events.publish(apinamespace+'.change')
  894. target.trigger('change')
  895. }
  896. })(this.$parent, this.events, apinamespace)
  897. // let's trigger change on all data reloads
  898. dataEngine.changed()
  899. // import filters will be passing this back as indication of "we rendered"
  900. return true
  901. }
  902. function initializeCanvasEmulator(canvas){
  903. if (canvas.getContext){
  904. return false
  905. } else {
  906. // for cases when jSignature, FlashCanvas is inserted
  907. // from one window into another (child iframe)
  908. // 'window' and 'FlashCanvas' may be stuck behind
  909. // in that other parent window.
  910. // we need to find it
  911. var window = canvas.ownerDocument.parentWindow
  912. var FC = window.FlashCanvas ?
  913. canvas.ownerDocument.parentWindow.FlashCanvas :
  914. (
  915. typeof FlashCanvas === "undefined" ?
  916. undefined :
  917. FlashCanvas
  918. )
  919. if (FC) {
  920. canvas = FC.initElement(canvas)
  921. var zoom = 1
  922. // FlashCanvas uses flash which has this annoying habit of NOT scaling with page zoom.
  923. // It matches pixel-to-pixel to screen instead.
  924. // Since we are targeting ONLY IE 7, 8 with FlashCanvas, we will test the zoom only the IE8, IE7 way
  925. if (window && window.screen && window.screen.deviceXDPI && window.screen.logicalXDPI){
  926. zoom = window.screen.deviceXDPI * 1.0 / window.screen.logicalXDPI
  927. }
  928. if (zoom !== 1){
  929. try {
  930. // We effectively abuse the brokenness of FlashCanvas and force the flash rendering surface to
  931. // occupy larger pixel dimensions than the wrapping, scaled up DIV and Canvas elems.
  932. $(canvas).children('object').get(0).resize(Math.ceil(canvas.width * zoom), Math.ceil(canvas.height * zoom))
  933. // And by applying "scale" transformation we can talk "browser pixels" to FlashCanvas
  934. // and have it translate the "browser pixels" to "screen pixels"
  935. canvas.getContext('2d').scale(zoom, zoom)
  936. // Note to self: don't reuse Canvas element. Repeated "scale" are cumulative.
  937. } catch (ex) {}
  938. }
  939. return true
  940. } else {
  941. throw new Error("Canvas element does not support 2d context. jSignature cannot proceed.")
  942. }
  943. }
  944. }
  945. jSignatureClass.prototype.initializeCanvas = function(settings) {
  946. // ===========
  947. // Init + Sizing code
  948. var canvas = document.createElement('canvas')
  949. , $canvas = $(canvas)
  950. // We cannot work with circular dependency
  951. if (settings.width === settings.height && settings.height === 'ratio') {
  952. settings.width = '100%'
  953. }
  954. $canvas.css(
  955. 'margin'
  956. , 0
  957. ).css(
  958. 'padding'
  959. , 0
  960. ).css(
  961. 'border'
  962. , 'none'
  963. ).css(
  964. 'height'
  965. , settings.height === 'ratio' || !settings.height ? 1 : settings.height.toString(10)
  966. ).css(
  967. 'width'
  968. , settings.width === 'ratio' || !settings.width ? 1 : settings.width.toString(10)
  969. )
  970. $canvas.appendTo(this.$parent)
  971. // we could not do this until canvas is rendered (appended to DOM)
  972. if (settings.height === 'ratio') {
  973. $canvas.css(
  974. 'height'
  975. , Math.round( $canvas.width() / settings.sizeRatio )
  976. )
  977. } else if (settings.width === 'ratio') {
  978. $canvas.css(
  979. 'width'
  980. , Math.round( $canvas.height() * settings.sizeRatio )
  981. )
  982. }
  983. $canvas.addClass(apinamespace)
  984. // canvas's drawing area resolution is independent from canvas's size.
  985. // pixels are just scaled up or down when internal resolution does not
  986. // match external size. So...
  987. canvas.width = $canvas.width()
  988. canvas.height = $canvas.height()
  989. // Special case Sizing code
  990. this.isCanvasEmulator = initializeCanvasEmulator(canvas)
  991. // End of Sizing Code
  992. // ===========
  993. // normally select preventer would be short, but
  994. // Canvas emulator on IE does NOT provide value for Event. Hence this convoluted line.
  995. canvas.onselectstart = function(e){if(e && e.preventDefault){e.preventDefault()}; if(e && e.stopPropagation){e.stopPropagation()}; return false;}
  996. return canvas
  997. }
  998. var GlobalJSignatureObjectInitializer = function(window){
  999. var globalEvents = new PubSubClass()
  1000. // common "window resized" event listener.
  1001. // jSignature instances will subscribe to this chanel.
  1002. // to resize themselves when needed.
  1003. ;(function(globalEvents, apinamespace, $, window){
  1004. 'use strict'
  1005. var resizetimer
  1006. , runner = function(){
  1007. globalEvents.publish(
  1008. apinamespace + '.parentresized'
  1009. )
  1010. }
  1011. // jSignature knows how to resize its content when its parent is resized
  1012. // window resize is the only way we can catch resize events though...
  1013. $(window).bind('resize.'+apinamespace, function(){
  1014. if (resizetimer) {
  1015. clearTimeout(resizetimer)
  1016. }
  1017. resizetimer = setTimeout(
  1018. runner
  1019. , 500
  1020. )
  1021. })
  1022. // when mouse exists canvas element and "up"s outside, we cannot catch it with
  1023. // callbacks attached to canvas. This catches it outside.
  1024. .bind('mouseup.'+apinamespace, function(e){
  1025. globalEvents.publish(
  1026. apinamespace + '.windowmouseup'
  1027. )
  1028. })
  1029. })(globalEvents, apinamespace, $, window)
  1030. var jSignatureInstanceExtensions = {
  1031. 'exampleExtension':function(extensionName){
  1032. // we are called very early in instance's life.
  1033. // right after the settings are resolved and
  1034. // jSignatureInstance.events is created
  1035. // and right before first ("jSignature.initializing") event is called.
  1036. // You don't really need to manupilate
  1037. // jSignatureInstance directly, just attach
  1038. // a bunch of events to jSignatureInstance.events
  1039. // (look at the source of jSignatureClass to see when these fire)
  1040. // and your special pieces of code will attach by themselves.
  1041. // this function runs every time a new instance is set up.
  1042. // this means every var you create will live only for one instance
  1043. // unless you attach it to something outside, like "window."
  1044. // and pick it up later from there.
  1045. // when globalEvents' events fire, 'this' is globalEvents object
  1046. // when jSignatureInstance's events fire, 'this' is jSignatureInstance
  1047. // Here,
  1048. // this = is new jSignatureClass's instance.
  1049. // The way you COULD approch setting this up is:
  1050. // if you have multistep set up, attach event to "jSignature.initializing"
  1051. // that attaches other events to be fired further lower the init stream.
  1052. // Or, if you know for sure you rely on only one jSignatureInstance's event,
  1053. // just attach to it directly
  1054. this.events.subscribe(
  1055. // name of the event
  1056. apinamespace + '.initializing'
  1057. // event handlers, can pass args too, but in majority of cases,
  1058. // 'this' which is jSignatureClass object instance pointer is enough to get by.
  1059. , function(){
  1060. if (this.settings.hasOwnProperty('non-existent setting category?')) {
  1061. console.log(extensionName + ' is here')
  1062. }
  1063. }
  1064. )
  1065. }
  1066. }
  1067. var exportplugins = {
  1068. 'default':function(data){return this.toDataURL()}
  1069. , 'native':function(data){return data}
  1070. , 'image':function(data){
  1071. /*this = canvas elem */
  1072. var imagestring = this.toDataURL()
  1073. if (typeof imagestring === 'string' &&
  1074. imagestring.length > 4 &&
  1075. imagestring.slice(0,5) === 'data:' &&
  1076. imagestring.indexOf(',') !== -1){
  1077. var splitterpos = imagestring.indexOf(',')
  1078. return [
  1079. imagestring.slice(5, splitterpos)
  1080. , imagestring.substr(splitterpos + 1)
  1081. ]
  1082. }
  1083. return []
  1084. }
  1085. }
  1086. // will be part of "importplugins"
  1087. function _renderImageOnCanvas( data, formattype, rerendercallable ) {
  1088. 'use strict'
  1089. // #1. Do NOT rely on this. No worky on IE
  1090. // (url max len + lack of base64 decoder + possibly other issues)
  1091. // #2. This does NOT affect what is captured as "signature" as far as vector data is
  1092. // concerned. This is treated same as "signature line" - i.e. completely ignored
  1093. // the only time you see imported image data exported is if you export as image.
  1094. // we do NOT call rerendercallable here (unlike in other import plugins)
  1095. // because importing image does absolutely nothing to the underlying vector data storage
  1096. // This could be a way to "import" old signatures stored as images
  1097. // This could also be a way to import extra decor into signature area.
  1098. // var img = new Image()
  1099. // // this = Canvas DOM elem. Not jQuery object. Not Canvas's parent div.
  1100. // , c = this
  1101. //
  1102. // img.onload = function() {
  1103. // var ctx = c.getContext("2d").drawImage(
  1104. // img, 0, 0
  1105. // , ( img.width < c.width) ? img.width : c.width
  1106. // , ( img.height < c.height) ? img.height : c.height
  1107. // )
  1108. // }
  1109. //
  1110. // img.src = 'data:' + formattype + ',' + data
  1111. var c = new Image,
  1112. e = this;
  1113. c.onload = function() {
  1114. var a = e.getContext("2d"),
  1115. b = a.shadowColor;
  1116. a.shadowColor = "transparent";
  1117. a.drawImage(c, 0, 0, c.width < e.width ? c.width : e.width, c.height <
  1118. e.height ? c.height : e.height);
  1119. a.shadowColor = b
  1120. };
  1121. c.src = "data:" + formattype + "," + data
  1122. }
  1123. var importplugins = {
  1124. 'native':function(data, formattype, rerendercallable){
  1125. // we expect data as Array of objects of arrays here - whatever 'default' EXPORT plugin spits out.
  1126. // returning Truthy to indicate we are good, all updated.
  1127. rerendercallable( data )
  1128. }
  1129. , 'image': _renderImageOnCanvas
  1130. , 'image/png;base64': _renderImageOnCanvas
  1131. , 'image/jpeg;base64': _renderImageOnCanvas
  1132. , 'image/jpg;base64': _renderImageOnCanvas
  1133. }
  1134. function _clearDrawingArea( data ) {
  1135. this.find('canvas.'+apinamespace)
  1136. .add(this.filter('canvas.'+apinamespace))
  1137. .data(apinamespace+'.this').resetCanvas( data )
  1138. return this
  1139. }
  1140. function _setDrawingData( data, formattype ) {
  1141. var undef
  1142. if (formattype === undef && typeof data === 'string' && data.substr(0,5) === 'data:') {
  1143. formattype = data.slice(5).split(',')[0]
  1144. // 5 chars of "data:" + mimetype len + 1 "," char = all skipped.
  1145. data = data.slice(6 + formattype.length)
  1146. if (formattype === data) return
  1147. }
  1148. var $canvas = this.find('canvas.'+apinamespace).add(this.filter('canvas.'+apinamespace))
  1149. if (!importplugins.hasOwnProperty(formattype)){
  1150. throw new Error(apinamespace + " is unable to find import plugin with for format '"+ String(formattype) +"'")
  1151. } else if ($canvas.length !== 0){
  1152. importplugins[formattype].call(
  1153. $canvas[0]
  1154. , data
  1155. , formattype
  1156. , (function(jSignatureInstance){
  1157. return function(){ return jSignatureInstance.resetCanvas.apply(jSignatureInstance, arguments) }
  1158. })($canvas.data(apinamespace+'.this'))
  1159. )
  1160. }
  1161. return this
  1162. }
  1163. var elementIsOrphan = function(e){
  1164. var topOfDOM = false
  1165. e = e.parentNode
  1166. while (e && !topOfDOM){
  1167. topOfDOM = $(e).find(".o_form_view")
  1168. e = e.parentNode
  1169. }
  1170. return !topOfDOM
  1171. }
  1172. //These are exposed as methods under $obj.jSignature('methodname', *args)
  1173. var plugins = {'export':exportplugins, 'import':importplugins, 'instance': jSignatureInstanceExtensions}
  1174. , methods = {
  1175. 'init' : function( options ) {
  1176. return this.each( function() {
  1177. if (!elementIsOrphan(this)) {
  1178. new jSignatureClass(this, options, jSignatureInstanceExtensions)
  1179. }
  1180. })
  1181. }
  1182. , 'getSettings' : function() {
  1183. return this.find('canvas.'+apinamespace)
  1184. .add(this.filter('canvas.'+apinamespace))
  1185. .data(apinamespace+'.this').settings
  1186. }
  1187. // around since v1
  1188. , 'clear' : _clearDrawingArea
  1189. // was mistakenly introduced instead of 'clear' in v2
  1190. , 'reset' : _clearDrawingArea
  1191. , 'addPlugin' : function(pluginType, pluginName, callable){
  1192. if (plugins.hasOwnProperty(pluginType)){
  1193. plugins[pluginType][pluginName] = callable
  1194. }
  1195. return this
  1196. }
  1197. , 'listPlugins' : function(pluginType){
  1198. var answer = []
  1199. if (plugins.hasOwnProperty(pluginType)){
  1200. var o = plugins[pluginType]
  1201. for (var k in o){
  1202. if (o.hasOwnProperty(k)){
  1203. answer.push(k)
  1204. }
  1205. }
  1206. }
  1207. return answer
  1208. }
  1209. , 'getData' : function( formattype ) {
  1210. var undef, $canvas=this.find('canvas.'+apinamespace).add(this.filter('canvas.'+apinamespace))
  1211. if (formattype === undef) formattype = 'default'
  1212. if ($canvas.length !== 0 && exportplugins.hasOwnProperty(formattype)){
  1213. return exportplugins[formattype].call(
  1214. $canvas.get(0) // canvas dom elem
  1215. , $canvas.data(apinamespace+'.data') // raw signature data as array of objects of arrays
  1216. )
  1217. }
  1218. }
  1219. // around since v1. Took only one arg - data-url-formatted string with (likely png of) signature image
  1220. , 'importData' : _setDrawingData
  1221. // was mistakenly introduced instead of 'importData' in v2
  1222. , 'setData' : _setDrawingData
  1223. // this is one and same instance for all jSignature.
  1224. , 'globalEvents' : function(){return globalEvents}
  1225. // there will be a separate one for each jSignature instance.
  1226. , 'events' : function() {
  1227. return this.find('canvas.'+apinamespace)
  1228. .add(this.filter('canvas.'+apinamespace))
  1229. .data(apinamespace+'.this').events
  1230. }
  1231. } // end of methods declaration.
  1232. $.fn[apinamespace] = function(method) {
  1233. 'use strict'
  1234. if ( !method || typeof method === 'object' ) {
  1235. return methods.init.apply( this, arguments )
  1236. } else if ( typeof method === 'string' && methods[method] ) {
  1237. return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ))
  1238. } else {
  1239. $.error( 'Method ' + String(method) + ' does not exist on jQuery.' + apinamespace )
  1240. }
  1241. }
  1242. } // end of GlobalJSignatureObjectInitializer
  1243. GlobalJSignatureObjectInitializer(window)
  1244. })();