vue2-client 1.16.25 → 1.16.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (327) hide show
  1. package/.cursorrules +19 -19
  2. package/.env.apply +19 -19
  3. package/.env.gaslink +19 -19
  4. package/.env.his +19 -19
  5. package/.env.liuli +20 -20
  6. package/.env.scada +19 -19
  7. package/.eslintrc.js +90 -90
  8. package/CHANGELOG.md +824 -824
  9. package/Components.md +60 -60
  10. package/docs/LowCode/lowcode.md +155 -155
  11. package/docs/LowCode/lowcodeForDeveloper.md +230 -230
  12. package/docs/index.md +30 -30
  13. package/index.js +31 -31
  14. package/jest-transform-stub.js +8 -8
  15. package/jest.setup.js +7 -7
  16. package/jsconfig.json +19 -19
  17. package/package.json +112 -112
  18. package/public/his/editor/editor.html +51 -51
  19. package/public/his/editor/mock/bind_data.html +779 -779
  20. package/public/his/editor/mock/data_table.html +40 -40
  21. package/public/his/editor/mock/sign.html +75 -75
  22. package/public/his/editor/vender/JsBarcode.all.js +3669 -3669
  23. package/public/his/editor/vender/date97/My97DatePicker.htm +65 -65
  24. package/public/his/editor/vender/date97/WdatePicker.js +677 -677
  25. package/public/his/editor/vender/date97/calendar.js +4 -4
  26. package/public/his/editor/vender/date97/lang/en.js +13 -13
  27. package/public/his/editor/vender/date97/lang/zh-cn.js +13 -13
  28. package/public/his/editor/vender/date97/lang/zh-tw.js +13 -13
  29. package/public/his/editor/vender/date97/skin/WdatePicker.css +10 -10
  30. package/public/his/editor/vender/date97/skin/default/datepicker.css +328 -328
  31. package/public/his/editor/vender/date97/skin/ext/datepicker.css +308 -308
  32. package/public/his/editor/vender/date97/skin/whyGreen/datepicker.css +255 -255
  33. package/public/his/editor/vender/diff.js +1627 -1627
  34. package/public/his/editor/vender/editor.js +1 -1
  35. package/public/his/editor/vender/fabric.js +31187 -31187
  36. package/public/his/editor/vender/jquery/jquery.base64.js +190 -190
  37. package/public/his/editor/vender/jquery/jquery.js +10872 -10872
  38. package/public/his/editor/vender/jquery/jquery.print.js +255 -255
  39. package/public/his/editor/vender/jquery/zTreeStyle/zTreeStyle.css +96 -96
  40. package/public/his/editor/vender/mui/mui.min.css +4 -4
  41. package/public/his/editor/vender/mui/mui.min.js +5 -5
  42. package/public/his/editor/vender/mui/mui.picker.min.css +6 -6
  43. package/public/his/editor/vender/mui/mui.picker.min.js +6 -6
  44. package/public/his/editor/vender/qrcode.js +7 -7
  45. package/public/his/editor/vender/requirejs/require.js +2145 -2145
  46. package/public/his/editor/vender/signature/jSignature.CompressorSVG.js +518 -518
  47. package/public/his/editor/vender/signature/jSignature.UndoButton.js +164 -164
  48. package/public/his/editor/vender/signature/jSignature.js +1486 -1486
  49. package/public/his/editor/vender/validator.js +5094 -5094
  50. package/public/his/editor/vender/weui/weui.css +5659 -5659
  51. package/public/his/editor/vender/weui/weui.min.css +4 -4
  52. package/public/his/editor/vender/weui/weui.min.js +11 -11
  53. package/src/assets/img/querySlotDemo.svg +15 -15
  54. package/src/assets/svg/badtwo.svg +1 -1
  55. package/src/assets/svg/goodtwo.svg +1 -1
  56. package/src/base-client/components/AI/AskAiBtn.vue +136 -136
  57. package/src/base-client/components/AI/demo.vue +31 -31
  58. package/src/base-client/components/common/AddressSearchCombobox/IcMapIcon.vue +16 -16
  59. package/src/base-client/components/common/AddressSearchCombobox/demo.vue +36 -36
  60. package/src/base-client/components/common/AddressSearchCombobox/ic_map.svg +6 -6
  61. package/src/base-client/components/common/AmapMarker/AmapPointRendering.vue +120 -120
  62. package/src/base-client/components/common/CitySelect/CitySelect.vue +1 -1
  63. package/src/base-client/components/common/CitySelect/index.js +3 -3
  64. package/src/base-client/components/common/CitySelect/index.md +109 -109
  65. package/src/base-client/components/common/CreateQuery/CreateQuery.vue +669 -669
  66. package/src/base-client/components/common/CreateQuery/CreateQueryItem.vue +1014 -1014
  67. package/src/base-client/components/common/CreateQuery/index.js +3 -3
  68. package/src/base-client/components/common/CreateQuery/index.md +42 -42
  69. package/src/base-client/components/common/CreateSimpleFormQuery/CreateSimpleFormQuery.vue +452 -452
  70. package/src/base-client/components/common/CreateSimpleFormQuery/CreateSimpleFormQueryItem.vue +511 -511
  71. package/src/base-client/components/common/CreateSimpleFormQuery/index.js +3 -3
  72. package/src/base-client/components/common/CreateSimpleFormQuery/index.md +42 -42
  73. package/src/base-client/components/common/FormGroupEdit/index.js +3 -3
  74. package/src/base-client/components/common/FormGroupEdit/index.md +43 -43
  75. package/src/base-client/components/common/FormGroupQuery/FormGroupQuery.vue +166 -166
  76. package/src/base-client/components/common/FormGroupQuery/index.js +3 -3
  77. package/src/base-client/components/common/FormGroupQuery/index.md +43 -43
  78. package/src/base-client/components/common/HIS/HForm/HForm.vue +133 -0
  79. package/src/base-client/components/common/HIS/HForm/index.js +3 -0
  80. package/src/base-client/components/common/JSONToTree/jsontotree.vue +271 -271
  81. package/src/base-client/components/common/LowCodeComponent/LowCodeEditorModal.vue +108 -108
  82. package/src/base-client/components/common/LowCodeComponent/LowCodeEditorPanel.vue +413 -413
  83. package/src/base-client/components/common/LowCodeComponent/LowCodePageOrganization.vue +502 -502
  84. package/src/base-client/components/common/LowCodeComponent/LowCodeRender.vue +728 -728
  85. package/src/base-client/components/common/LowCodeComponent/LowCodeRenderEnter.vue +29 -29
  86. package/src/base-client/components/common/LowCodeComponent/LowCodeUIStore.vue +219 -219
  87. package/src/base-client/components/common/LowCodeComponent/modal/lowCodeAddPageModal.vue +117 -117
  88. package/src/base-client/components/common/LowCodeComponent/modal/lowCodeCustomJSModal.vue +80 -80
  89. package/src/base-client/components/common/LowCodeComponent/modal/lowCodeEventEditorModal.vue +398 -398
  90. package/src/base-client/components/common/LowCodeComponent/modal/lowCodeLifeCycleModal.vue +65 -65
  91. package/src/base-client/components/common/LowCodeComponent/modal/lowCodeLogicCallbackModal.vue +64 -64
  92. package/src/base-client/components/common/LowCodeComponent/modal/lowCodeLogicParamModal.vue +73 -73
  93. package/src/base-client/components/common/LowCodeComponent/modal/lowCodeRunFunctionParamModal.vue +76 -76
  94. package/src/base-client/components/common/PersonSetting/PersonSetting.vue +208 -208
  95. package/src/base-client/components/common/PersonSetting/index.js +3 -3
  96. package/src/base-client/components/common/Recording/Recording.vue +243 -243
  97. package/src/base-client/components/common/Recording/index.js +3 -3
  98. package/src/base-client/components/common/Tree/Tree.vue +149 -149
  99. package/src/base-client/components/common/Tree/index.js +2 -2
  100. package/src/base-client/components/common/Upload/Upload.vue +333 -323
  101. package/src/base-client/components/common/Upload/index.js +3 -3
  102. package/src/base-client/components/common/XAddForm/XAddForm.vue +113 -113
  103. package/src/base-client/components/common/XAddNativeForm/index.md +146 -146
  104. package/src/base-client/components/common/XAddNativeFormOA/XAddNativeFormOA.vue +303 -303
  105. package/src/base-client/components/common/XAddNativeFormOA/index.js +3 -3
  106. package/src/base-client/components/common/XAddNativeFormOA/index.md +146 -146
  107. package/src/base-client/components/common/XAddReport/index.js +3 -3
  108. package/src/base-client/components/common/XAddReport/index.md +56 -56
  109. package/src/base-client/components/common/XBadge/XBadge.vue +94 -94
  110. package/src/base-client/components/common/XButtons/XButtonDemo.vue +28 -28
  111. package/src/base-client/components/common/XButtons/index.js +3 -3
  112. package/src/base-client/components/common/XButtons/index.md +61 -61
  113. package/src/base-client/components/common/XCard/XCard.vue +64 -64
  114. package/src/base-client/components/common/XCheckList/XCheckList.vue +106 -106
  115. package/src/base-client/components/common/XCheckList/XCheckListDemo.vue +41 -41
  116. package/src/base-client/components/common/XDataCard/index.js +3 -3
  117. package/src/base-client/components/common/XDataCard/index.md +1 -1
  118. package/src/base-client/components/common/XDataDrawer/XDataDrawer.vue +180 -180
  119. package/src/base-client/components/common/XDataDrawer/index.js +3 -3
  120. package/src/base-client/components/common/XDataDrawer/index.md +41 -41
  121. package/src/base-client/components/common/XDatePicker/demo.vue +153 -153
  122. package/src/base-client/components/common/XDescriptions/index.js +3 -3
  123. package/src/base-client/components/common/XDescriptions/index.md +83 -83
  124. package/src/base-client/components/common/XDetailsView/XDetailsView.vue +238 -238
  125. package/src/base-client/components/common/XDetailsView/index.js +3 -3
  126. package/src/base-client/components/common/XForm/XFormItem.vue +1504 -1503
  127. package/src/base-client/components/common/XForm/XStatusButton.vue +54 -54
  128. package/src/base-client/components/common/XForm/index.md +178 -178
  129. package/src/base-client/components/common/XForm/itemComponent/XClickChangeBtn/index.vue +49 -49
  130. package/src/base-client/components/common/XFormGroup/index.js +3 -3
  131. package/src/base-client/components/common/XFormGroup/index.md +38 -38
  132. package/src/base-client/components/common/XFormGroupDetails/XFormGroupDetails.vue +72 -72
  133. package/src/base-client/components/common/XFormGroupDetails/index.js +3 -3
  134. package/src/base-client/components/common/XFormTable/index.md +92 -92
  135. package/src/base-client/components/common/XLabelSelect/XLabelSelect.vue +110 -110
  136. package/src/base-client/components/common/XLabelSelect/XLabelSelectDemo.vue +35 -35
  137. package/src/base-client/components/common/XLicensePlate/XLicensePlate.vue +193 -193
  138. package/src/base-client/components/common/XLicensePlate/XLicensePlateDemo.vue +48 -48
  139. package/src/base-client/components/common/XPrint/OpenInvoice.vue +21 -21
  140. package/src/base-client/components/common/XPrint/PrintHtml.js +98 -98
  141. package/src/base-client/components/common/XPrint/css/hiPrintCss.js +359 -359
  142. package/src/base-client/components/common/XPrint/css/lodopCss.js +26 -26
  143. package/src/base-client/components/common/XPrint/css/print-lock.css +351 -351
  144. package/src/base-client/components/common/XPrint/index.vue +97 -97
  145. package/src/base-client/components/common/XReport/XReportDesign.vue +463 -463
  146. package/src/base-client/components/common/XReport/XReportJsonRender.vue +381 -381
  147. package/src/base-client/components/common/XReport/index.js +3 -3
  148. package/src/base-client/components/common/XReport/print.js +186 -186
  149. package/src/base-client/components/common/XReportDrawer/index.js +3 -3
  150. package/src/base-client/components/common/XReportGrid/XReportTrGroup.vue +4 -1
  151. package/src/base-client/components/common/XReportGrid/index.js +3 -3
  152. package/src/base-client/components/common/XReportGrid/index.md +44 -44
  153. package/src/base-client/components/common/XReportSlot/XReportSlot.vue +110 -110
  154. package/src/base-client/components/common/XReportSlot/index.js +3 -3
  155. package/src/base-client/components/common/XReportSlot/index.md +48 -48
  156. package/src/base-client/components/common/XSimpleDescriptions/XSimpleDescriptions.vue +166 -166
  157. package/src/base-client/components/common/XSimpleDescriptions/index.js +3 -3
  158. package/src/base-client/components/common/XSimpleDescriptions/index.md +7 -7
  159. package/src/base-client/components/common/XStepView/XStepView.vue +252 -252
  160. package/src/base-client/components/common/XStepView/index.js +3 -3
  161. package/src/base-client/components/common/XStepView/index.md +31 -31
  162. package/src/base-client/components/common/XTab/XTabDemo.vue +22 -22
  163. package/src/base-client/components/common/XTab/index.js +3 -3
  164. package/src/base-client/components/common/XTable/CustomFuncCel.vue +51 -51
  165. package/src/base-client/components/common/XTable/TableCellRenderer.vue +161 -161
  166. package/src/base-client/components/common/XTable/index.md +255 -255
  167. package/src/base-client/components/common/XTagGroup/index.vue +52 -52
  168. package/src/base-client/components/common/XTree/XTree.vue +424 -424
  169. package/src/base-client/components/common/XTree/index.js +3 -3
  170. package/src/base-client/components/common/XTree/index.md +36 -36
  171. package/src/base-client/components/common/XTreeOne/XTreeOne.vue +113 -113
  172. package/src/base-client/components/common/XTreeOne/XTreeOnePro.vue +128 -128
  173. package/src/base-client/components/common/richTextModal/index.vue +56 -56
  174. package/src/base-client/components/common/richTextModal/richDemo.vue +48 -48
  175. package/src/base-client/components/his/XHisEditor/index.js +3 -3
  176. package/src/base-client/components/index.js +51 -51
  177. package/src/base-client/components/layout/XTreeView/XTreeView.vue +130 -130
  178. package/src/base-client/components/layout/XTreeView/index.js +3 -3
  179. package/src/base-client/components/layout/XTreeView/index.md +46 -46
  180. package/src/base-client/components/system/DictionaryDetailsView/DictionaryDetailsView.vue +232 -232
  181. package/src/base-client/components/system/QueryParamsDetailsView/QueryParamsDetailsView.vue +281 -281
  182. package/src/base-client/plugins/Config.js +19 -19
  183. package/src/base-client/plugins/GetLoginInfoService.js +183 -183
  184. package/src/base-client/plugins/Recording.js +258 -258
  185. package/src/base-client/plugins/index.js +23 -23
  186. package/src/base-client/plugins/tabs-page-plugin.js +39 -39
  187. package/src/components/Charts/Bar.vue +62 -62
  188. package/src/components/Charts/ChartCard.vue +134 -134
  189. package/src/components/Charts/Liquid.vue +67 -67
  190. package/src/components/Charts/MiniArea.vue +39 -39
  191. package/src/components/Charts/MiniBar.vue +39 -39
  192. package/src/components/Charts/MiniProgress.vue +75 -75
  193. package/src/components/Charts/MiniSmoothArea.vue +40 -40
  194. package/src/components/Charts/Radar.vue +68 -68
  195. package/src/components/Charts/RankList.vue +77 -77
  196. package/src/components/Charts/TagCloud.vue +113 -113
  197. package/src/components/Charts/TransferBar.vue +64 -64
  198. package/src/components/Charts/Trend.vue +82 -82
  199. package/src/components/Charts/chart.less +12 -12
  200. package/src/components/Charts/smooth.area.less +13 -13
  201. package/src/components/CodeMirror/inedx.vue +118 -118
  202. package/src/components/CodeMirror/setting.js +40 -40
  203. package/src/components/FileImageItem/FileItem.vue +320 -320
  204. package/src/components/NumberInfo/NumberInfo.vue +54 -54
  205. package/src/components/NumberInfo/index.js +3 -3
  206. package/src/components/NumberInfo/index.less +54 -54
  207. package/src/components/NumberInfo/index.md +43 -43
  208. package/src/components/card/ChartCard.vue +79 -79
  209. package/src/components/chart/Bar.vue +60 -60
  210. package/src/components/chart/MiniArea.vue +67 -67
  211. package/src/components/chart/MiniBar.vue +59 -59
  212. package/src/components/chart/MiniProgress.vue +57 -57
  213. package/src/components/chart/Radar.vue +80 -80
  214. package/src/components/chart/RankingList.vue +60 -60
  215. package/src/components/chart/Trend.vue +79 -79
  216. package/src/components/chart/index.less +9 -9
  217. package/src/components/checkbox/ColorCheckbox.vue +157 -157
  218. package/src/components/checkbox/ImgCheckbox.vue +117 -117
  219. package/src/components/checkbox/ImgCheckboxGroup.vue +76 -76
  220. package/src/components/checkbox/index.js +9 -9
  221. package/src/components/exception/ExceptionPage.vue +70 -70
  222. package/src/components/g2Charts/constants.js +202 -202
  223. package/src/components/g2Charts/demo.vue +808 -808
  224. package/src/components/g2Charts/designer.vue +228 -228
  225. package/src/components/g2Charts/designerBaseConfig.vue +61 -61
  226. package/src/components/g2Charts/designerDataConfig.vue +259 -259
  227. package/src/components/g2Charts/designerStyleConfig.vue +16 -16
  228. package/src/components/g2Charts/index.vue +397 -397
  229. package/src/components/index.js +36 -36
  230. package/src/components/input/IInput.vue +66 -66
  231. package/src/components/menu/SideMenu.vue +75 -75
  232. package/src/components/menu/menu.js +273 -273
  233. package/src/components/setting/Setting.vue +234 -234
  234. package/src/components/tool/AStepItem.vue +60 -60
  235. package/src/config/CreateQueryConfig.js +325 -325
  236. package/src/config/default/antd.config.js +89 -89
  237. package/src/config/default/setting.config.js +55 -55
  238. package/src/font-style/font.css +60 -60
  239. package/src/layouts/CommonLayout.vue +56 -56
  240. package/src/layouts/PageLayout.vue +151 -151
  241. package/src/layouts/SinglePageView.vue +136 -136
  242. package/src/layouts/header/AdminHeader.vue +132 -132
  243. package/src/layouts/header/HeaderNotice.vue +177 -177
  244. package/src/layouts/header/InstitutionDetail.vue +181 -181
  245. package/src/layouts/tabs/TabsHead.vue +189 -189
  246. package/src/lib.js +1 -1
  247. package/src/mock/extend/index.js +84 -84
  248. package/src/mock/goods/index.js +108 -108
  249. package/src/pages/DefaultExample/index.vue +77 -77
  250. package/src/pages/DynamicStatistics/ChartSelector.vue +331 -331
  251. package/src/pages/DynamicStatistics/DataTabs.vue +83 -83
  252. package/src/pages/DynamicStatistics/DynamicTable.vue +128 -128
  253. package/src/pages/DynamicStatistics/EvaluationArea.vue +69 -69
  254. package/src/pages/DynamicStatistics/FavoriteList.vue +50 -50
  255. package/src/pages/DynamicStatistics/QuestionHistoryAndFavorites.vue +591 -591
  256. package/src/pages/DynamicStatistics/SearchBar.vue +192 -192
  257. package/src/pages/DynamicStatistics/index.vue +282 -282
  258. package/src/pages/Example/childIndex.vue +15 -15
  259. package/src/pages/Example/index.vue +30 -30
  260. package/src/pages/NewDynamicStatistics/ChartSelector.vue +331 -331
  261. package/src/pages/NewDynamicStatistics/DataTabs.vue +122 -122
  262. package/src/pages/NewDynamicStatistics/DynamicTable.vue +128 -128
  263. package/src/pages/NewDynamicStatistics/EvaluationArea.vue +69 -69
  264. package/src/pages/NewDynamicStatistics/FavoriteList.vue +50 -50
  265. package/src/pages/NewDynamicStatistics/QuestionHistoryAndFavorites.vue +289 -289
  266. package/src/pages/NewDynamicStatistics/SearchBar.vue +193 -193
  267. package/src/pages/NewDynamicStatistics/index.vue +258 -258
  268. package/src/pages/Recording/index.vue +77 -77
  269. package/src/pages/ServiceReview/index.vue +284 -284
  270. package/src/pages/SubExample/index.vue +26 -26
  271. package/src/pages/WorkflowDetail/WorkflowPageDetail/TrimTextTail.vue +23 -23
  272. package/src/pages/WorkflowDetail/WorkflowPageDetail/WorkFlowHandle.vue +1766 -1766
  273. package/src/pages/XReportView/index.vue +64 -64
  274. package/src/pages/XTreeOneProExample/index.vue +67 -67
  275. package/src/pages/dashboard/workplace/WorkPlace.vue +141 -141
  276. package/src/pages/login/Login.vue +379 -379
  277. package/src/pages/login/LoginV3.vue +389 -389
  278. package/src/pages/lowCode/lowCodeEditor.vue +1219 -1219
  279. package/src/pages/lowCode/lowCodeRenderPage.vue +43 -43
  280. package/src/pages/report/ReportTable.js +124 -124
  281. package/src/pages/resourceManage/orgListManage.vue +98 -98
  282. package/src/pages/system/dictionary/index.vue +44 -44
  283. package/src/pages/system/monitor/loginInfor/index.vue +37 -37
  284. package/src/pages/system/monitor/operLog/index.vue +37 -37
  285. package/src/pages/system/settings/modifyPassword.vue +117 -117
  286. package/src/pages/system/ticket/index.vue +480 -480
  287. package/src/pages/system/ticket/submitTicketSuccess.vue +484 -484
  288. package/src/pages/userInfoDetailManage/ChangeMeterRecordQuery/index.vue +64 -64
  289. package/src/pages/userInfoDetailManage/InfoChangeRecordQuery/index.vue +64 -64
  290. package/src/pages/userInfoDetailManage/InstructRecordQuery/index.vue +64 -64
  291. package/src/pages/userInfoDetailManage/MeterParamRecordQuery/index.vue +64 -64
  292. package/src/pages/userInfoDetailManage/TransferRecordQuery/index.vue +66 -66
  293. package/src/pages/userInfoDetailManage/WatchCollectionRecordQuery/index.vue +64 -64
  294. package/src/plugins/EventLogPlugin.js +33 -33
  295. package/src/plugins/FindParentsData.js +17 -17
  296. package/src/router/async/config.async.js +35 -35
  297. package/src/router/index.js +27 -27
  298. package/src/services/DataModel.js +30 -30
  299. package/src/services/LodopFuncs.js +137 -137
  300. package/src/services/api/TicketDetailsViewApi.js +46 -46
  301. package/src/services/api/cas.js +79 -79
  302. package/src/services/api/common.js +346 -346
  303. package/src/services/api/entity.js +18 -18
  304. package/src/services/api/index.js +17 -17
  305. package/src/store/modules/account.js +115 -115
  306. package/src/store/modules/index.js +5 -5
  307. package/src/store/modules/lowCode.js +33 -33
  308. package/src/store/modules/setting.js +119 -119
  309. package/src/theme/default/style.less +58 -58
  310. package/src/utils/authority-utils.js +85 -85
  311. package/src/utils/errorCode.js +6 -6
  312. package/src/utils/formatter.js +74 -74
  313. package/src/utils/htmlToPDF.js +108 -108
  314. package/src/utils/htmlToPDFApi.js +5 -5
  315. package/src/utils/login.js +188 -188
  316. package/src/utils/lowcode/lowcodeComponentMixin.js +120 -120
  317. package/src/utils/lowcode/lowcodeLog.js +29 -29
  318. package/src/utils/lowcode/lowcodeUtils.js +373 -373
  319. package/src/utils/lowcode/registerComponentForEditor.js +1 -1
  320. package/src/utils/lowcode/registerComponentForRender.js +11 -11
  321. package/src/utils/map-utils.js +47 -47
  322. package/src/utils/reg.js +95 -95
  323. package/src/utils/runEvalFunction.js +14 -14
  324. package/src/utils/theme-color-replacer-extend.js +92 -92
  325. package/src/utils/util.js +329 -329
  326. package/src/utils/waterMark.js +31 -31
  327. package//350/277/201/347/247/273/346/227/245/345/277/227.md +15 -15
@@ -1,1486 +1,1486 @@
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
- */
8
- ;(function() {
9
-
10
- var apinamespace = 'jSignature'
11
-
12
- /**
13
- Allows one to delay certain eventual action by setting up a timer for it and allowing one to delay it
14
- by "kick"ing it. Sorta like "kick the can down the road"
15
-
16
- @public
17
- @class
18
- @param
19
- @returns {Type}
20
- */
21
- var KickTimerClass = function(time, callback) {
22
- var timer;
23
- this.kick = function() {
24
- clearTimeout(timer);
25
- timer = setTimeout(
26
- callback
27
- , time
28
- );
29
- }
30
- this.clear = function() {
31
- clearTimeout(timer);
32
- }
33
- return this;
34
- }
35
-
36
- var PubSubClass = function(context){
37
- 'use strict'
38
- /* @preserve
39
- -----------------------------------------------------------------------------------------------
40
- JavaScript PubSub library
41
- 2012 (c) Willow Systems Corp (www.willow-systems.com)
42
- based on Peter Higgins (dante@dojotoolkit.org)
43
- Loosely based on Dojo publish/subscribe API, limited in scope. Rewritten blindly.
44
- Original is (c) Dojo Foundation 2004-2010. Released under either AFL or new BSD, see:
45
- http://dojofoundation.org/license for more information.
46
- -----------------------------------------------------------------------------------------------
47
- */
48
- this.topics = {};
49
- // here we choose what will be "this" for the called events.
50
- // if context is defined, it's context. Else, 'this' is this instance of PubSub
51
- this.context = context ? context : this;
52
- /**
53
- * Allows caller to emit an event and pass arguments to event listeners.
54
- * @public
55
- * @function
56
- * @param topic {String} Name of the channel on which to voice this event
57
- * @param **arguments Any number of arguments you want to pass to the listeners of this event.
58
- */
59
- this.publish = function(topic, arg1, arg2, etc) {
60
- 'use strict'
61
- if (this.topics[topic]) {
62
- var currentTopic = this.topics[topic]
63
- , args = Array.prototype.slice.call(arguments, 1)
64
- , toremove = []
65
- , torun = []
66
- , fn
67
- , i, l
68
- , pair;
69
-
70
- for (i = 0, l = currentTopic.length; i < l; i++) {
71
- pair = currentTopic[i]; // this is a [function, once_flag] array
72
- fn = pair[0];
73
- if (pair[1] /* 'run once' flag set */){
74
- pair[0] = function(){};
75
- toremove.push(i);
76
- }
77
- /* don't call the callback right now, it might decide to add or
78
- * remove subscribers which will wreak havoc on our index-based
79
- * iteration */
80
- torun.push(fn);
81
- }
82
- for (i = 0, l = toremove.length; i < l; i++) {
83
- currentTopic.splice(toremove[i], 1);
84
- }
85
- for (i = 0, l = torun.length; i < l; i++) {
86
- torun[i].apply(this.context, args);
87
- }
88
- }
89
- }
90
- /**
91
- * Allows listener code to subscribe to channel and be called when data is available
92
- * @public
93
- * @function
94
- * @param topic {String} Name of the channel on which to voice this event
95
- * @param callback {Function} Executable (function pointer) that will be ran when event is voiced on this channel.
96
- * @param once {Boolean} (optional. False by default) Flag indicating if the function is to be triggered only once.
97
- * @returns {Object} A token object that cen be used for unsubscribing.
98
- */
99
- this.subscribe = function(topic, callback, once) {
100
- 'use strict'
101
- if (!this.topics[topic]) {
102
- this.topics[topic] = [[callback, once]];
103
- } else {
104
- this.topics[topic].push([callback,once]);
105
- }
106
- return {
107
- "topic": topic,
108
- "callback": callback
109
- };
110
- };
111
- /**
112
- * Allows listener code to unsubscribe from a channel
113
- * @public
114
- * @function
115
- * @param token {Object} A token object that was returned by `subscribe` method
116
- */
117
- this.unsubscribe = function(token) {
118
- if (this.topics[token.topic]) {
119
- var currentTopic = this.topics[token.topic];
120
-
121
- for (var i = 0, l = currentTopic.length; i < l; i++) {
122
- if (currentTopic[i] && currentTopic[i][0] === token.callback) {
123
- currentTopic.splice(i, 1);
124
- }
125
- }
126
- }
127
- }
128
- }
129
-
130
- /// Returns front, back and "decor" colors derived from element (as jQuery obj)
131
- function getColors($e){
132
- var tmp
133
- , undef
134
- , frontcolor = $e.css('color')
135
- , backcolor
136
- , e = $e[0];
137
-
138
- var toOfDOM = false;
139
- while(e && !backcolor && !toOfDOM){
140
- try{
141
- tmp = $(e).css('background-color');
142
- } catch (ex) {
143
- tmp = 'transparent';
144
- }
145
- if (tmp !== 'transparent' && tmp !== 'rgba(0, 0, 0, 0)'){
146
- backcolor = tmp;
147
- }
148
- toOfDOM = e.body;
149
- e = e.parentNode;
150
- }
151
-
152
- var rgbaregex = /rgb[a]*\((\d+),\s*(\d+),\s*(\d+)/ // modern browsers
153
- , hexregex = /#([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})/ // IE 8 and less.
154
- , frontcolorcomponents;
155
-
156
- // Decomposing Front color into R, G, B ints
157
- tmp = undef;
158
- tmp = frontcolor.match(rgbaregex);
159
- if (tmp){
160
- frontcolorcomponents = {'r':parseInt(tmp[1],10),'g':parseInt(tmp[2],10),'b':parseInt(tmp[3],10)};
161
- } else {
162
- tmp = frontcolor.match(hexregex);
163
- if (tmp) {
164
- frontcolorcomponents = {'r':parseInt(tmp[1],16),'g':parseInt(tmp[2],16),'b':parseInt(tmp[3],16)};
165
- }
166
- }
167
- // if(!frontcolorcomponents){
168
- // frontcolorcomponents = {'r':255,'g':255,'b':255}
169
- // }
170
-
171
- var backcolorcomponents
172
- // Decomposing back color into R, G, B ints
173
- if(!backcolor){
174
- // HIghly unlikely since this means that no background styling was applied to any element from here to top of dom.
175
- // we'll pick up back color from front color
176
- if(frontcolorcomponents){
177
- if (Math.max.apply(null, [frontcolorcomponents.r, frontcolorcomponents.g, frontcolorcomponents.b]) > 127){
178
- backcolorcomponents = {'r':0,'g':0,'b':0};
179
- } else {
180
- backcolorcomponents = {'r':255,'g':255,'b':255};
181
- }
182
- } else {
183
- // arg!!! front color is in format we don't understand (hsl, named colors)
184
- // Let's just go with white background.
185
- backcolorcomponents = {'r':255,'g':255,'b':255};
186
- }
187
- } else {
188
- tmp = undef;
189
- tmp = backcolor.match(rgbaregex);
190
- if (tmp){
191
- backcolorcomponents = {'r':parseInt(tmp[1],10),'g':parseInt(tmp[2],10),'b':parseInt(tmp[3],10)};
192
- } else {
193
- tmp = backcolor.match(hexregex);
194
- if (tmp) {
195
- backcolorcomponents = {'r':parseInt(tmp[1],16),'g':parseInt(tmp[2],16),'b':parseInt(tmp[3],16)};
196
- }
197
- }
198
- // if(!backcolorcomponents){
199
- // backcolorcomponents = {'r':0,'g':0,'b':0}
200
- // }
201
- }
202
-
203
- // Deriving Decor color
204
- // THis is LAZY!!!! Better way would be to use HSL and adjust luminocity. However, that could be an overkill.
205
-
206
- var toRGBfn = function(o){return 'rgb(' + [o.r, o.g, o.b].join(', ') + ')'}
207
- , decorcolorcomponents
208
- , frontcolorbrightness
209
- , adjusted;
210
-
211
- if (frontcolorcomponents && backcolorcomponents){
212
- var backcolorbrightness = Math.max.apply(null, [frontcolorcomponents.r, frontcolorcomponents.g, frontcolorcomponents.b]);
213
-
214
- frontcolorbrightness = Math.max.apply(null, [backcolorcomponents.r, backcolorcomponents.g, backcolorcomponents.b]);
215
- adjusted = Math.round(frontcolorbrightness + (-1 * (frontcolorbrightness - backcolorbrightness) * 0.75)); // "dimming" the difference between pen and back.
216
- decorcolorcomponents = {'r':adjusted,'g':adjusted,'b':adjusted}; // always shade of gray
217
- } else if (frontcolorcomponents) {
218
- frontcolorbrightness = Math.max.apply(null, [frontcolorcomponents.r, frontcolorcomponents.g, frontcolorcomponents.b]);
219
- var polarity = +1;
220
- if (frontcolorbrightness > 127){
221
- polarity = -1;
222
- }
223
- // shifting by 25% (64 points on RGB scale)
224
- adjusted = Math.round(frontcolorbrightness + (polarity * 96)); // "dimming" the pen's color by 75% to get decor color.
225
- decorcolorcomponents = {'r':adjusted,'g':adjusted,'b':adjusted}; // always shade of gray
226
- } else {
227
- decorcolorcomponents = {'r':191,'g':191,'b':191}; // always shade of gray
228
- }
229
-
230
- return {
231
- 'color': frontcolor
232
- , 'background-color': backcolorcomponents? toRGBfn(backcolorcomponents) : backcolor
233
- , 'decor-color': toRGBfn(decorcolorcomponents)
234
- };
235
- }
236
-
237
- function Vector(x,y){
238
- this.x = x;
239
- this.y = y;
240
- this.reverse = function(){
241
- return new this.constructor(
242
- this.x * -1
243
- , this.y * -1
244
- );
245
- };
246
- this._length = null;
247
- this.getLength = function(){
248
- if (!this._length){
249
- this._length = Math.sqrt( Math.pow(this.x, 2) + Math.pow(this.y, 2) );
250
- }
251
- return this._length;
252
- };
253
-
254
- var polarity = function (e){
255
- return Math.round(e / Math.abs(e));
256
- };
257
- this.resizeTo = function(length){
258
- // proportionally changes x,y such that the hypotenuse (vector length) is = new length
259
- if (this.x === 0 && this.y === 0){
260
- this._length = 0;
261
- } else if (this.x === 0){
262
- this._length = length;
263
- this.y = length * polarity(this.y);
264
- } else if(this.y === 0){
265
- this._length = length;
266
- this.x = length * polarity(this.x);
267
- } else {
268
- var proportion = Math.abs(this.y / this.x)
269
- , x = Math.sqrt(Math.pow(length, 2) / (1 + Math.pow(proportion, 2)))
270
- , y = proportion * x;
271
- this._length = length;
272
- this.x = x * polarity(this.x);
273
- this.y = y * polarity(this.y);
274
- }
275
- return this;
276
- };
277
-
278
- /**
279
- * Calculates the angle between 'this' vector and another.
280
- * @public
281
- * @function
282
- * @returns {Number} The angle between the two vectors as measured in PI.
283
- */
284
- this.angleTo = function(vectorB) {
285
- var divisor = this.getLength() * vectorB.getLength();
286
- if (divisor === 0) {
287
- return 0;
288
- } else {
289
- // JavaScript floating point math is screwed up.
290
- // because of it, the core of the formula can, on occasion, have values
291
- // over 1.0 and below -1.0.
292
- return Math.acos(
293
- Math.min(
294
- Math.max(
295
- ( this.x * vectorB.x + this.y * vectorB.y ) / divisor
296
- , -1.0
297
- )
298
- , 1.0
299
- )
300
- ) / Math.PI;
301
- }
302
- };
303
- }
304
-
305
- function Point(x,y){
306
- this.x = x;
307
- this.y = y;
308
-
309
- this.getVectorToCoordinates = function (x, y) {
310
- return new Vector(x - this.x, y - this.y);
311
- };
312
- this.getVectorFromCoordinates = function (x, y) {
313
- return this.getVectorToCoordinates(x, y).reverse();
314
- };
315
- this.getVectorToPoint = function (point) {
316
- return new Vector(point.x - this.x, point.y - this.y);
317
- };
318
- this.getVectorFromPoint = function (point) {
319
- return this.getVectorToPoint(point).reverse();
320
- };
321
- }
322
-
323
- /*
324
- * About data structure:
325
- * We don't store / deal with "pictures" this signature capture code captures "vectors"
326
- *
327
- * We don't store bitmaps. We store "strokes" as arrays of arrays. (Actually, arrays of objects containing arrays of coordinates.
328
- *
329
- * Stroke = mousedown + mousemoved * n (+ mouseup but we don't record that as that was the "end / lack of movement" indicator)
330
- *
331
- * Vectors = not classical vectors where numbers indicated shift relative last position. Our vectors are actually coordinates against top left of canvas.
332
- * we could calc the classical vectors, but keeping the the actual coordinates allows us (through Math.max / min)
333
- * to calc the size of resulting drawing very quickly. If we want classical vectors later, we can always get them in backend code.
334
- *
335
- * So, the data structure:
336
- *
337
- * var data = [
338
- * { // stroke starts
339
- * x : [101, 98, 57, 43] // x points
340
- * , y : [1, 23, 65, 87] // y points
341
- * } // stroke ends
342
- * , { // stroke starts
343
- * x : [55, 56, 57, 58] // x points
344
- * , y : [101, 97, 54, 4] // y points
345
- * } // stroke ends
346
- * , { // stroke consisting of just a dot
347
- * x : [53] // x points
348
- * , y : [151] // y points
349
- * } // stroke ends
350
- * ]
351
- *
352
- * 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.
353
- *
354
- */
355
- function DataEngine(storageObject, context, startStrokeFn, addToStrokeFn, endStrokeFn){
356
- this.data = storageObject; // we expect this to be an instance of Array
357
- this.context = context;
358
-
359
- if (storageObject.length){
360
- // we have data to render
361
- var numofstrokes = storageObject.length
362
- , stroke
363
- , numofpoints;
364
-
365
- for (var i = 0; i < numofstrokes; i++){
366
- stroke = storageObject[i];
367
- numofpoints = stroke.x.length;
368
- startStrokeFn.call(context, stroke);
369
- for(var j = 1; j < numofpoints; j++){
370
- addToStrokeFn.call(context, stroke, j);
371
- }
372
- endStrokeFn.call(context, stroke);
373
- }
374
- }
375
-
376
- this.changed = function(){};
377
-
378
- this.startStrokeFn = startStrokeFn;
379
- this.addToStrokeFn = addToStrokeFn;
380
- this.endStrokeFn = endStrokeFn;
381
-
382
- this.inStroke = false;
383
-
384
- this._lastPoint = null;
385
- this._stroke = null;
386
- this.startStroke = function(point){
387
- if(point && typeof(point.x) == "number" && typeof(point.y) == "number"){
388
- this._stroke = {'x':[point.x], 'y':[point.y]};
389
- this.data.push(this._stroke);
390
- this._lastPoint = point;
391
- this.inStroke = true;
392
- // 'this' does not work same inside setTimeout(
393
- var stroke = this._stroke
394
- , fn = this.startStrokeFn
395
- , context = this.context;
396
- setTimeout(
397
- // some IE's don't support passing args per setTimeout API. Have to create closure every time instead.
398
- function() {fn.call(context, stroke)}
399
- , 3
400
- );
401
- return point;
402
- } else {
403
- return null;
404
- }
405
- };
406
- // that "5" at the very end of this if is important to explain.
407
- // we do NOT render links between two captured points (in the middle of the stroke) if the distance is shorter than that number.
408
- // not only do we NOT render it, we also do NOT capture (add) these intermediate points to storage.
409
- // when clustering of these is too tight, it produces noise on the line, which, because of smoothing, makes lines too curvy.
410
- // maybe, later, we can expose this as a configurable setting of some sort.
411
- this.addToStroke = function(point){
412
- if (this.inStroke &&
413
- typeof(point.x) === "number" &&
414
- typeof(point.y) === "number" &&
415
- // calculates absolute shift in diagonal pixels away from original point
416
- (Math.abs(point.x - this._lastPoint.x) + Math.abs(point.y - this._lastPoint.y)) > 4
417
- ){
418
- var positionInStroke = this._stroke.x.length;
419
- this._stroke.x.push(point.x);
420
- this._stroke.y.push(point.y);
421
- this._lastPoint = point;
422
-
423
- var stroke = this._stroke
424
- , fn = this.addToStrokeFn
425
- , context = this.context;
426
- setTimeout(
427
- // some IE's don't support passing args per setTimeout API. Have to create closure every time instead.
428
- function() {fn.call(context, stroke, positionInStroke)}
429
- , 3
430
- );
431
- return point;
432
- } else {
433
- return null;
434
- }
435
- };
436
- this.endStroke = function(){
437
- var c = this.inStroke;
438
- this.inStroke = false;
439
- this._lastPoint = null;
440
- if (c){
441
- var stroke = this._stroke
442
- , fn = this.endStrokeFn // 'this' does not work same inside setTimeout(
443
- , context = this.context
444
- , changedfn = this.changed;
445
- setTimeout(
446
- // some IE's don't support passing args per setTimeout API. Have to create closure every time instead.
447
- function(){
448
- fn.call(context, stroke);
449
- changedfn.call(context);
450
- }
451
- , 3
452
- );
453
- return true;
454
- } else {
455
- return null;
456
- }
457
- };
458
- }
459
-
460
- var basicDot = function(ctx, x, y, size){
461
- var fillStyle = ctx.fillStyle;
462
- ctx.fillStyle = ctx.strokeStyle;
463
- ctx.fillRect(x + size / -2 , y + size / -2, size, size);
464
- ctx.fillStyle = fillStyle;
465
- }
466
- , basicLine = function(ctx, startx, starty, endx, endy){
467
- ctx.beginPath();
468
- ctx.moveTo(startx, starty);
469
- ctx.lineTo(endx, endy);
470
- ctx.closePath();
471
- ctx.stroke();
472
- }
473
- , basicCurve = function(ctx, startx, starty, endx, endy, cp1x, cp1y, cp2x, cp2y){
474
- ctx.beginPath();
475
- ctx.moveTo(startx, starty);
476
- ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, endx, endy);
477
- ctx.closePath();
478
- ctx.stroke();
479
- }
480
- , strokeStartCallback = function(stroke) {
481
- // this = jSignatureClass instance
482
- basicDot(this.canvasContext, stroke.x[0], stroke.y[0], this.settings.lineWidth);
483
- }
484
- , strokeAddCallback = function(stroke, positionInStroke){
485
- // this = jSignatureClass instance
486
-
487
- // Because we are funky this way, here we draw TWO curves.
488
- // 1. POSSIBLY "this line" - spanning from point right before us, to this latest point.
489
- // 2. POSSIBLY "prior curve" - spanning from "latest point" to the one before it.
490
-
491
- // Why you ask?
492
- // long lines (ones with many pixels between them) do not look good when they are part of a large curvy stroke.
493
- // You know, the jaggedy crocodile spine instead of a pretty, smooth curve. Yuck!
494
- // We want to approximate pretty curves in-place of those ugly lines.
495
- // To approximate a very nice curve we need to know the direction of line before and after.
496
- // Hence, on long lines we actually wait for another point beyond it to come back from
497
- // mousemoved before we draw this curve.
498
-
499
- // So for "prior curve" to be calc'ed we need 4 points
500
- // A, B, C, D (we are on D now, A is 3 points in the past.)
501
- // and 3 lines:
502
- // pre-line (from points A to B),
503
- // 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.)
504
- // post-line (from points C to D) (even through D point is 'current' we don't know how we can draw it yet)
505
- //
506
- // Well, actually, we don't need to *know* the point A, just the vector A->B
507
- var Cpoint = new Point(stroke.x[positionInStroke-1], stroke.y[positionInStroke-1])
508
- , Dpoint = new Point(stroke.x[positionInStroke], stroke.y[positionInStroke])
509
- , CDvector = Cpoint.getVectorToPoint(Dpoint);
510
-
511
- // Again, we have a chance here to draw TWO things:
512
- // BC Curve (only if it's long, because if it was short, it was drawn by previous callback) and
513
- // CD Line (only if it's short)
514
-
515
- // So, let's start with BC curve.
516
- // if there is only 2 points in stroke array, we don't have "history" long enough to have point B, let alone point A.
517
- // Falling through to drawing line CD is proper, as that's the only line we have points for.
518
- if(positionInStroke > 1) {
519
- // we are here when there are at least 3 points in stroke array.
520
- var Bpoint = new Point(stroke.x[positionInStroke-2], stroke.y[positionInStroke-2])
521
- , BCvector = Bpoint.getVectorToPoint(Cpoint)
522
- , ABvector;
523
- if(BCvector.getLength() > this.lineCurveThreshold){
524
- // Yey! Pretty curves, here we come!
525
- if(positionInStroke > 2) {
526
- // we are here when at least 4 points in stroke array.
527
- ABvector = (new Point(stroke.x[positionInStroke-3], stroke.y[positionInStroke-3])).getVectorToPoint(Bpoint);
528
- } else {
529
- ABvector = new Vector(0,0);
530
- }
531
-
532
- var minlenfraction = 0.05
533
- , maxlen = BCvector.getLength() * 0.35
534
- , ABCangle = BCvector.angleTo(ABvector.reverse())
535
- , BCDangle = CDvector.angleTo(BCvector.reverse())
536
- , BCP1vector = new Vector(ABvector.x + BCvector.x, ABvector.y + BCvector.y).resizeTo(
537
- Math.max(minlenfraction, ABCangle) * maxlen
538
- )
539
- , CCP2vector = (new Vector(BCvector.x + CDvector.x, BCvector.y + CDvector.y)).reverse().resizeTo(
540
- Math.max(minlenfraction, BCDangle) * maxlen
541
- );
542
-
543
- basicCurve(
544
- this.canvasContext
545
- , Bpoint.x
546
- , Bpoint.y
547
- , Cpoint.x
548
- , Cpoint.y
549
- , Bpoint.x + BCP1vector.x
550
- , Bpoint.y + BCP1vector.y
551
- , Cpoint.x + CCP2vector.x
552
- , Cpoint.y + CCP2vector.y
553
- );
554
- }
555
- }
556
- if(CDvector.getLength() <= this.lineCurveThreshold){
557
- basicLine(
558
- this.canvasContext
559
- , Cpoint.x
560
- , Cpoint.y
561
- , Dpoint.x
562
- , Dpoint.y
563
- );
564
- }
565
- }
566
- , strokeEndCallback = function(stroke){
567
- // this = jSignatureClass instance
568
-
569
- // Here we tidy up things left unfinished in last strokeAddCallback run.
570
-
571
- // What's POTENTIALLY left unfinished there is the curve between the last points
572
- // in the stroke, if the len of that line is more than lineCurveThreshold
573
- // If the last line was shorter than lineCurveThreshold, it was drawn there, and there
574
- // is nothing for us here to do.
575
- // We can also be called when there is only one point in the stroke (meaning, the
576
- // stroke was just a dot), in which case, again, there is nothing for us to do.
577
-
578
- // So for "this curve" to be calc'ed we need 3 points
579
- // A, B, C
580
- // and 2 lines:
581
- // pre-line (from points A to B),
582
- // this line (from points B to C)
583
- // Well, actually, we don't need to *know* the point A, just the vector A->B
584
- // so, we really need points B, C and AB vector.
585
- var positionInStroke = stroke.x.length - 1;
586
-
587
- if (positionInStroke > 0){
588
- // there are at least 2 points in the stroke.we are in business.
589
- var Cpoint = new Point(stroke.x[positionInStroke], stroke.y[positionInStroke])
590
- , Bpoint = new Point(stroke.x[positionInStroke-1], stroke.y[positionInStroke-1])
591
- , BCvector = Bpoint.getVectorToPoint(Cpoint)
592
- , ABvector;
593
- if (BCvector.getLength() > this.lineCurveThreshold){
594
- // yep. This one was left undrawn in prior callback. Have to draw it now.
595
- if (positionInStroke > 1){
596
- // we have at least 3 elems in stroke
597
- ABvector = (new Point(stroke.x[positionInStroke-2], stroke.y[positionInStroke-2])).getVectorToPoint(Bpoint);
598
- var BCP1vector = new Vector(ABvector.x + BCvector.x, ABvector.y + BCvector.y).resizeTo(BCvector.getLength() / 2);
599
- basicCurve(
600
- this.canvasContext
601
- , Bpoint.x
602
- , Bpoint.y
603
- , Cpoint.x
604
- , Cpoint.y
605
- , Bpoint.x + BCP1vector.x
606
- , Bpoint.y + BCP1vector.y
607
- , Cpoint.x
608
- , Cpoint.y
609
- );
610
- } else {
611
- // Since there is no AB leg, there is no curve to draw. This line is still "long" but no curve.
612
- basicLine(
613
- this.canvasContext
614
- , Bpoint.x
615
- , Bpoint.y
616
- , Cpoint.x
617
- , Cpoint.y
618
- );
619
- }
620
- }
621
- }
622
- }
623
-
624
-
625
- /*
626
- var getDataStats = function(){
627
- var strokecnt = strokes.length
628
- , stroke
629
- , pointid
630
- , pointcnt
631
- , x, y
632
- , maxX = Number.NEGATIVE_INFINITY
633
- , maxY = Number.NEGATIVE_INFINITY
634
- , minX = Number.POSITIVE_INFINITY
635
- , minY = Number.POSITIVE_INFINITY
636
- for(strokeid = 0; strokeid < strokecnt; strokeid++){
637
- stroke = strokes[strokeid]
638
- pointcnt = stroke.length
639
- for(pointid = 0; pointid < pointcnt; pointid++){
640
- x = stroke.x[pointid]
641
- y = stroke.y[pointid]
642
- if (x > maxX){
643
- maxX = x
644
- } else if (x < minX) {
645
- minX = x
646
- }
647
- if (y > maxY){
648
- maxY = y
649
- } else if (y < minY) {
650
- minY = y
651
- }
652
- }
653
- }
654
- return {'maxX': maxX, 'minX': minX, 'maxY': maxY, 'minY': minY}
655
- }
656
- */
657
-
658
- function conditionallyLinkCanvasResizeToWindowResize(jSignatureInstance, settingsWidth, apinamespace, globalEvents){
659
- 'use strict'
660
- if ( settingsWidth === 'ratio' || settingsWidth.split('')[settingsWidth.length - 1] === '%' ) {
661
-
662
- this.eventTokens[apinamespace + '.parentresized'] = globalEvents.subscribe(
663
- apinamespace + '.parentresized'
664
- , (function(eventTokens, $parent, originalParentWidth, sizeRatio){
665
- 'use strict'
666
-
667
- return function(){
668
- 'use strict'
669
-
670
- var w = $parent.width();
671
- if (w !== originalParentWidth) {
672
-
673
- // UNsubscribing this particular instance of signature pad only.
674
- // there is a separate `eventTokens` per each instance of signature pad
675
- for (var key in eventTokens){
676
- if (eventTokens.hasOwnProperty(key)) {
677
- globalEvents.unsubscribe(eventTokens[key]);
678
- delete eventTokens[key];
679
- }
680
- }
681
-
682
- var settings = jSignatureInstance.settings;
683
- jSignatureInstance.$parent.children().remove();
684
- for (var key in jSignatureInstance){
685
- if (jSignatureInstance.hasOwnProperty(key)) {
686
- delete jSignatureInstance[key];
687
- }
688
- }
689
-
690
- // scale data to new signature pad size
691
- settings.data = (function(data, scale){
692
- var newData = [];
693
- var o, i, l, j, m, stroke;
694
- for ( i = 0, l = data.length; i < l; i++) {
695
- stroke = data[i];
696
-
697
- o = {'x':[],'y':[]};
698
-
699
- for ( j = 0, m = stroke.x.length; j < m; j++) {
700
- o.x.push(stroke.x[j] * scale);
701
- o.y.push(stroke.y[j] * scale);
702
- }
703
-
704
- newData.push(o);
705
- }
706
- return newData;
707
- })(
708
- settings.data
709
- , w * 1.0 / originalParentWidth
710
- )
711
-
712
- $parent[apinamespace](settings);
713
- }
714
- }
715
- })(
716
- this.eventTokens
717
- , this.$parent
718
- , this.$parent.width()
719
- , this.canvas.width * 1.0 / this.canvas.height
720
- )
721
- )
722
- }
723
- };
724
-
725
-
726
- function jSignatureClass(parent, options, instanceExtensions) {
727
-
728
- var $parent = this.$parent = $(parent)
729
- , eventTokens = this.eventTokens = {}
730
- , events = this.events = new PubSubClass(this)
731
- , globalEvents = $.fn[apinamespace]('globalEvents')
732
- , settings = {
733
- 'width' : 'ratio'
734
- ,'height' : 'ratio'
735
- ,'sizeRatio': 4 // only used when height = 'ratio'
736
- ,'color' : '#000'
737
- ,'background-color': '#fff'
738
- ,'decor-color': '#eee'
739
- ,'lineWidth' : 0
740
- ,'minFatFingerCompensation' : -10
741
- ,'showUndoButton': false
742
- ,'readOnly': false
743
- ,'data': []
744
- ,'signatureLine': false
745
- };
746
-
747
- $.extend(settings, getColors($parent));
748
- if (options) {
749
- $.extend(settings, options);
750
- }
751
- this.settings = settings;
752
-
753
- for (var extensionName in instanceExtensions){
754
- if (instanceExtensions.hasOwnProperty(extensionName)) {
755
- instanceExtensions[extensionName].call(this, extensionName);
756
- }
757
- }
758
-
759
- this.events.publish(apinamespace+'.initializing');
760
-
761
- // these, when enabled, will hover above the sig area. Hence we append them to DOM before canvas.
762
- this.$controlbarUpper = (function(){
763
- var controlbarstyle = 'padding:0 !important; margin:0 !important;'+
764
- 'width: 100% !important; height: 0 !important; -ms-touch-action: none; touch-action: none;'+
765
- 'margin-top:-1em !important; margin-bottom:1em !important;';
766
- return $('<div style="'+controlbarstyle+'"></div>').appendTo($parent);
767
- })();
768
-
769
- this.isCanvasEmulator = false; // will be flipped by initializer when needed.
770
- var canvas = this.canvas = this.initializeCanvas(settings)
771
- , $canvas = $(canvas);
772
-
773
- this.$controlbarLower = (function(){
774
- var controlbarstyle = 'padding:0 !important; margin:0 !important;'+
775
- 'width: 100% !important; height: 0 !important; -ms-touch-action: none; touch-action: none;'+
776
- 'margin-top:-1.5em !important; margin-bottom:1.5em !important; position: relative;';
777
- return $('<div style="'+controlbarstyle+'"></div>').appendTo($parent);
778
- })();
779
-
780
- this.canvasContext = canvas.getContext("2d");
781
-
782
- // Most of our exposed API will be looking for this:
783
- $canvas.data(apinamespace + '.this', this);
784
-
785
- settings.lineWidth = (function(defaultLineWidth, canvasWidth){
786
- if (!defaultLineWidth){
787
- return Math.max(
788
- Math.round(canvasWidth / 400) /*+1 pixel for every extra 300px of width.*/
789
- , 2 /* minimum line width */
790
- );
791
- } else {
792
- return defaultLineWidth;
793
- }
794
- })(settings.lineWidth, canvas.width);
795
-
796
- this.lineCurveThreshold = settings.lineWidth * 3;
797
-
798
- // Add custom class if defined
799
- if(settings.cssclass && $.trim(settings.cssclass) != "") {
800
- $canvas.addClass(settings.cssclass);
801
- }
802
-
803
- // used for shifting the drawing point up on touch devices, so one can see the drawing above the finger.
804
- this.fatFingerCompensation = 0;
805
-
806
- var movementHandlers = (function(jSignatureInstance) {
807
-
808
- //================================
809
- // mouse down, move, up handlers:
810
-
811
- // shifts - adjustment values in viewport pixels drived from position of canvas on the page
812
- var shiftX
813
- , shiftY
814
- , setStartValues = function(){
815
- var tos = $(jSignatureInstance.canvas).offset()
816
- shiftX = tos.left * -1
817
- shiftY = tos.top * -1
818
- }
819
- , getPointFromEvent = function(e) {
820
- var firstEvent = (e.changedTouches && e.changedTouches.length > 0 ? e.changedTouches[0] : e);
821
- // All devices i tried report correct coordinates in pageX,Y
822
- // Android Chrome 2.3.x, 3.1, 3.2., Opera Mobile, safari iOS 4.x,
823
- // Windows: Chrome, FF, IE9, Safari
824
- // None of that scroll shift calc vs screenXY other sigs do is needed.
825
- // ... oh, yeah, the "fatFinger.." is for tablets so that people see what they draw.
826
- return new Point(
827
- Math.round(firstEvent.pageX + shiftX)
828
- , Math.round(firstEvent.pageY + shiftY) + jSignatureInstance.fatFingerCompensation
829
- );
830
- }
831
- , timer = new KickTimerClass(
832
- 750
833
- , function() { jSignatureInstance.dataEngine.endStroke(); }
834
- );
835
-
836
- this.drawEndHandler = function(e) {
837
- if (!jSignatureInstance.settings.readOnly) {
838
- try { e.preventDefault(); } catch (ex) {}
839
- timer.clear();
840
- jSignatureInstance.dataEngine.endStroke();
841
- }
842
- };
843
- this.drawStartHandler = function(e) {
844
- if (!jSignatureInstance.settings.readOnly) {
845
- e.preventDefault();
846
- // for performance we cache the offsets
847
- // we recalc these only at the beginning the stroke
848
- setStartValues();
849
- jSignatureInstance.dataEngine.startStroke( getPointFromEvent(e) );
850
- timer.kick();
851
- }
852
- };
853
- this.drawMoveHandler = function(e) {
854
- if (!jSignatureInstance.settings.readOnly) {
855
- e.preventDefault();
856
- if (!jSignatureInstance.dataEngine.inStroke){
857
- return;
858
- }
859
- jSignatureInstance.dataEngine.addToStroke( getPointFromEvent(e) );
860
- timer.kick();
861
- }
862
- };
863
-
864
- return this;
865
-
866
- }).call( {}, this )
867
-
868
- //
869
- //================================
870
-
871
- ;(function(drawEndHandler, drawStartHandler, drawMoveHandler) {
872
- var canvas = this.canvas
873
- , $canvas = $(canvas)
874
- , undef;
875
- if (this.isCanvasEmulator){
876
- $canvas.bind('mousemove.'+apinamespace, drawMoveHandler);
877
- $canvas.bind('mouseup.'+apinamespace, drawEndHandler);
878
- $canvas.bind('mousedown.'+apinamespace, drawStartHandler);
879
- } else {
880
- var hasEventListener = typeof canvas.addEventListener === 'function';
881
- this.ontouchstart = function(e) {
882
- canvas.onmousedown = canvas.onmouseup = canvas.onmousemove = undef;
883
-
884
- this.fatFingerCompensation = (
885
- settings.minFatFingerCompensation &&
886
- settings.lineWidth * -3 > settings.minFatFingerCompensation
887
- ) ? settings.lineWidth * -3 : settings.minFatFingerCompensation;
888
-
889
- drawStartHandler(e);
890
-
891
- if (hasEventListener) {
892
- canvas.addEventListener('touchend', drawEndHandler);
893
- canvas.addEventListener('touchstart', drawStartHandler);
894
- canvas.addEventListener('touchmove', drawMoveHandler);
895
- } else {
896
- canvas.ontouchend = drawEndHandler;
897
- canvas.ontouchstart = drawStartHandler;
898
- canvas.ontouchmove = drawMoveHandler;
899
- }
900
- };
901
-
902
- if (hasEventListener) {
903
- canvas.addEventListener('touchstart', this.ontouchstart);
904
- } else {
905
- canvas.ontouchstart = ontouchstart;
906
- }
907
-
908
- canvas.onmousedown = function(e) {
909
- if (hasEventListener) {
910
- canvas.removeEventListener('touchstart', this.ontouchstart);
911
- } else {
912
- canvas.ontouchstart = canvas.ontouchend = canvas.ontouchmove = undef;
913
- }
914
-
915
- drawStartHandler(e);
916
-
917
- canvas.onmousedown = drawStartHandler;
918
- canvas.onmouseup = drawEndHandler;
919
- canvas.onmousemove = drawMoveHandler;
920
- }
921
- if (window.navigator.msPointerEnabled) {
922
- canvas.onmspointerdown = drawStartHandler;
923
- canvas.onmspointerup = drawEndHandler;
924
- canvas.onmspointermove = drawMoveHandler;
925
- }
926
- }
927
- }).call(
928
- this
929
- , movementHandlers.drawEndHandler
930
- , movementHandlers.drawStartHandler
931
- , movementHandlers.drawMoveHandler
932
- )
933
-
934
- //=========================================
935
- // various event handlers
936
-
937
- // on mouseout + mouseup canvas did not know that mouseUP fired. Continued to draw despite mouse UP.
938
- // it is bettr than
939
- // $canvas.bind('mouseout', drawEndHandler)
940
- // because we don't want to break the stroke where user accidentally gets ouside and wants to get back in quickly.
941
- eventTokens[apinamespace + '.windowmouseup'] = globalEvents.subscribe(
942
- apinamespace + '.windowmouseup'
943
- , movementHandlers.drawEndHandler
944
- );
945
-
946
- this.events.publish(apinamespace+'.attachingEventHandlers');
947
-
948
- // If we have proportional width, we sign up to events broadcasting "window resized" and checking if
949
- // parent's width changed. If so, we (1) extract settings + data from current signature pad,
950
- // (2) remove signature pad from parent, and (3) reinit new signature pad at new size with same settings, (rescaled) data.
951
- conditionallyLinkCanvasResizeToWindowResize.call(
952
- this
953
- , this
954
- , settings.width.toString(10)
955
- , apinamespace, globalEvents
956
- );
957
-
958
- // end of event handlers.
959
- // ===============================
960
-
961
- this.resetCanvas(settings.data);
962
-
963
- // resetCanvas renders the data on the screen and fires ONE "change" event
964
- // if there is data. If you have controls that rely on "change" firing
965
- // attach them to something that runs before this.resetCanvas, like
966
- // apinamespace+'.attachingEventHandlers' that fires a bit higher.
967
- this.events.publish(apinamespace+'.initialized');
968
-
969
- return this;
970
- } // end of initBase
971
-
972
- //=========================================================================
973
- // jSignatureClass's methods and supporting fn's
974
-
975
- jSignatureClass.prototype.resetCanvas = function(data, dontClear){
976
- var canvas = this.canvas
977
- , settings = this.settings
978
- , ctx = this.canvasContext
979
- , isCanvasEmulator = this.isCanvasEmulator
980
- , cw = canvas.width
981
- , ch = canvas.height;
982
-
983
- // preparing colors, drawing area
984
- if (!dontClear){
985
- ctx.clearRect(0, 0, cw + 30, ch + 30);
986
- }
987
-
988
- ctx.shadowColor = ctx.fillStyle = settings['background-color']
989
- if (isCanvasEmulator){
990
- // FLashCanvas fills with Black by default, covering up the parent div's background
991
- // hence we refill
992
- ctx.fillRect(0,0,cw + 30, ch + 30);
993
- }
994
-
995
- ctx.lineWidth = Math.ceil(parseInt(settings.lineWidth, 10));
996
- ctx.lineCap = ctx.lineJoin = "round";
997
-
998
- // signature line
999
- if(settings.signatureLine) {
1000
- if (null != settings['decor-color']) {
1001
- ctx.strokeStyle = settings['decor-color'];
1002
- ctx.shadowOffsetX = 0;
1003
- ctx.shadowOffsetY = 0;
1004
- var lineoffset = Math.round( ch / 5 );
1005
- basicLine(ctx, lineoffset * 1.5, ch - lineoffset, cw - (lineoffset * 1.5), ch - lineoffset);
1006
- }
1007
-
1008
- if (!isCanvasEmulator){
1009
- ctx.shadowColor = ctx.strokeStyle;
1010
- ctx.shadowOffsetX = ctx.lineWidth * 0.5;
1011
- ctx.shadowOffsetY = ctx.lineWidth * -0.6;
1012
- ctx.shadowBlur = 0;
1013
- }
1014
- }
1015
-
1016
- ctx.strokeStyle = settings.color;
1017
-
1018
- // setting up new dataEngine
1019
-
1020
- if (!data) { data = []; }
1021
-
1022
- var dataEngine = this.dataEngine = new DataEngine(
1023
- data
1024
- , this
1025
- , strokeStartCallback
1026
- , strokeAddCallback
1027
- , strokeEndCallback
1028
- );
1029
-
1030
- settings.data = data; // onwindowresize handler uses it, i think.
1031
- $(canvas).data(apinamespace+'.data', data)
1032
- .data(apinamespace+'.settings', settings);
1033
-
1034
- // we fire "change" event on every change in data.
1035
- // setting this up:
1036
- dataEngine.changed = (function(target, events, apinamespace) {
1037
- 'use strict'
1038
- return function() {
1039
- events.publish(apinamespace+'.change');
1040
- target.trigger('change');
1041
- }
1042
- })(this.$parent, this.events, apinamespace);
1043
- // let's trigger change on all data reloads
1044
- dataEngine.changed();
1045
-
1046
- // import filters will be passing this back as indication of "we rendered"
1047
- return true;
1048
- };
1049
-
1050
- function initializeCanvasEmulator(canvas){
1051
- if (canvas.getContext){
1052
- return false;
1053
- } else {
1054
- // for cases when jSignature, FlashCanvas is inserted
1055
- // from one window into another (child iframe)
1056
- // 'window' and 'FlashCanvas' may be stuck behind
1057
- // in that other parent window.
1058
- // we need to find it
1059
- var window = canvas.ownerDocument.parentWindow;
1060
- var FC = window.FlashCanvas ?
1061
- canvas.ownerDocument.parentWindow.FlashCanvas :
1062
- (
1063
- typeof FlashCanvas === "undefined" ?
1064
- undefined :
1065
- FlashCanvas
1066
- );
1067
-
1068
- if (FC) {
1069
- canvas = FC.initElement(canvas);
1070
-
1071
- var zoom = 1;
1072
- // FlashCanvas uses flash which has this annoying habit of NOT scaling with page zoom.
1073
- // It matches pixel-to-pixel to screen instead.
1074
- // Since we are targeting ONLY IE 7, 8 with FlashCanvas, we will test the zoom only the IE8, IE7 way
1075
- if (window && window.screen && window.screen.deviceXDPI && window.screen.logicalXDPI){
1076
- zoom = window.screen.deviceXDPI * 1.0 / window.screen.logicalXDPI;
1077
- }
1078
- if (zoom !== 1){
1079
- try {
1080
- // We effectively abuse the brokenness of FlashCanvas and force the flash rendering surface to
1081
- // occupy larger pixel dimensions than the wrapping, scaled up DIV and Canvas elems.
1082
- $(canvas).children('object').get(0).resize(Math.ceil(canvas.width * zoom), Math.ceil(canvas.height * zoom));
1083
- // And by applying "scale" transformation we can talk "browser pixels" to FlashCanvas
1084
- // and have it translate the "browser pixels" to "screen pixels"
1085
- canvas.getContext('2d').scale(zoom, zoom);
1086
- // Note to self: don't reuse Canvas element. Repeated "scale" are cumulative.
1087
- } catch (ex) {}
1088
- }
1089
- return true;
1090
- } else {
1091
- throw new Error("Canvas element does not support 2d context. jSignature cannot proceed.");
1092
- }
1093
- }
1094
-
1095
- }
1096
-
1097
- jSignatureClass.prototype.initializeCanvas = function(settings) {
1098
- // ===========
1099
- // Init + Sizing code
1100
-
1101
- var canvas = document.createElement('canvas')
1102
- , $canvas = $(canvas);
1103
-
1104
- // We cannot work with circular dependency
1105
- if (settings.width === settings.height && settings.height === 'ratio') {
1106
- settings.width = '100%';
1107
- }
1108
-
1109
- $canvas.css(
1110
- {
1111
- 'margin': 0,
1112
- 'padding': 0,
1113
- 'border': 'none',
1114
- 'height': settings.height === 'ratio' || !settings.height ? 1 : settings.height.toString(10),
1115
- 'width': settings.width === 'ratio' || !settings.width ? 1 : settings.width.toString(10),
1116
- '-ms-touch-action': 'none',
1117
- 'touch-action': 'none',
1118
- 'background-color': settings['background-color']
1119
- }
1120
- );
1121
-
1122
- $canvas.appendTo(this.$parent);
1123
-
1124
- // we could not do this until canvas is rendered (appended to DOM)
1125
- if (settings.height === 'ratio') {
1126
- $canvas.css(
1127
- 'height'
1128
- , Math.round( $canvas.width() / settings.sizeRatio )
1129
- );
1130
- } else if (settings.width === 'ratio') {
1131
- $canvas.css(
1132
- 'width'
1133
- , Math.round( $canvas.height() * settings.sizeRatio )
1134
- );
1135
- }
1136
-
1137
- $canvas.addClass(apinamespace);
1138
-
1139
- // canvas's drawing area resolution is independent from canvas's size.
1140
- // pixels are just scaled up or down when internal resolution does not
1141
- // match external size. So...
1142
-
1143
- canvas.width = $canvas.width();
1144
- canvas.height = $canvas.height();
1145
-
1146
- // Special case Sizing code
1147
-
1148
- this.isCanvasEmulator = initializeCanvasEmulator(canvas);
1149
-
1150
- // End of Sizing Code
1151
- // ===========
1152
-
1153
- // normally select preventer would be short, but
1154
- // Canvas emulator on IE does NOT provide value for Event. Hence this convoluted line.
1155
- canvas.onselectstart = function(e){if(e && e.preventDefault){e.preventDefault()}; if(e && e.stopPropagation){e.stopPropagation()}; return false;};
1156
-
1157
- return canvas;
1158
- }
1159
-
1160
-
1161
- var GlobalJSignatureObjectInitializer = function(window){
1162
-
1163
- var globalEvents = new PubSubClass();
1164
-
1165
- // common "window resized" event listener.
1166
- // jSignature instances will subscribe to this chanel.
1167
- // to resize themselves when needed.
1168
- ;(function(globalEvents, apinamespace, $, window){
1169
- 'use strict'
1170
-
1171
- var resizetimer
1172
- , runner = function(){
1173
- globalEvents.publish(
1174
- apinamespace + '.parentresized'
1175
- )
1176
- };
1177
-
1178
- // jSignature knows how to resize its content when its parent is resized
1179
- // window resize is the only way we can catch resize events though...
1180
- $(window).bind('resize.'+apinamespace, function(){
1181
- if (resizetimer) {
1182
- clearTimeout(resizetimer);
1183
- }
1184
- resizetimer = setTimeout(
1185
- runner
1186
- , 500
1187
- );
1188
- })
1189
- // when mouse exists canvas element and "up"s outside, we cannot catch it with
1190
- // callbacks attached to canvas. This catches it outside.
1191
- .bind('mouseup.'+apinamespace, function(e){
1192
- globalEvents.publish(
1193
- apinamespace + '.windowmouseup'
1194
- )
1195
- });
1196
-
1197
- })(globalEvents, apinamespace, $, window)
1198
-
1199
- var jSignatureInstanceExtensions = {
1200
- /*
1201
- 'exampleExtension':function(extensionName){
1202
- // we are called very early in instance's life.
1203
- // right after the settings are resolved and
1204
- // jSignatureInstance.events is created
1205
- // and right before first ("jSignature.initializing") event is called.
1206
- // You don't really need to manupilate
1207
- // jSignatureInstance directly, just attach
1208
- // a bunch of events to jSignatureInstance.events
1209
- // (look at the source of jSignatureClass to see when these fire)
1210
- // and your special pieces of code will attach by themselves.
1211
-
1212
- // this function runs every time a new instance is set up.
1213
- // this means every var you create will live only for one instance
1214
- // unless you attach it to something outside, like "window."
1215
- // and pick it up later from there.
1216
-
1217
- // when globalEvents' events fire, 'this' is globalEvents object
1218
- // when jSignatureInstance's events fire, 'this' is jSignatureInstance
1219
-
1220
- // Here,
1221
- // this = is new jSignatureClass's instance.
1222
-
1223
- // The way you COULD approch setting this up is:
1224
- // if you have multistep set up, attach event to "jSignature.initializing"
1225
- // that attaches other events to be fired further lower the init stream.
1226
- // Or, if you know for sure you rely on only one jSignatureInstance's event,
1227
- // just attach to it directly
1228
-
1229
- this.events.subscribe(
1230
- // name of the event
1231
- apinamespace + '.initializing'
1232
- // event handlers, can pass args too, but in majority of cases,
1233
- // 'this' which is jSignatureClass object instance pointer is enough to get by.
1234
- , function(){
1235
- if (this.settings.hasOwnProperty('non-existent setting category?')) {
1236
- console.log(extensionName + ' is here')
1237
- }
1238
- }
1239
- )
1240
- }
1241
- */
1242
- };
1243
-
1244
- var exportplugins = {
1245
- 'default':function(data){return this.toDataURL()}
1246
- , 'native':function(data){return data}
1247
- , 'image':function(data){
1248
- /*this = canvas elem */
1249
- var imagestring = this.toDataURL();
1250
-
1251
- if (typeof imagestring === 'string' &&
1252
- imagestring.length > 4 &&
1253
- imagestring.slice(0,5) === 'data:' &&
1254
- imagestring.indexOf(',') !== -1){
1255
-
1256
- var splitterpos = imagestring.indexOf(',');
1257
-
1258
- return [
1259
- imagestring.slice(5, splitterpos)
1260
- , imagestring.substr(splitterpos + 1)
1261
- ];
1262
- }
1263
- return [];
1264
- }
1265
- };
1266
-
1267
- // will be part of "importplugins"
1268
- function _renderImageOnCanvas( data, formattype, rerendercallable ) {
1269
- 'use strict'
1270
- // #1. Do NOT rely on this. No worky on IE
1271
- // (url max len + lack of base64 decoder + possibly other issues)
1272
- // #2. This does NOT affect what is captured as "signature" as far as vector data is
1273
- // concerned. This is treated same as "signature line" - i.e. completely ignored
1274
- // the only time you see imported image data exported is if you export as image.
1275
-
1276
- // we do NOT call rerendercallable here (unlike in other import plugins)
1277
- // because importing image does absolutely nothing to the underlying vector data storage
1278
- // This could be a way to "import" old signatures stored as images
1279
- // This could also be a way to import extra decor into signature area.
1280
-
1281
- var img = new Image()
1282
- // this = Canvas DOM elem. Not jQuery object. Not Canvas's parent div.
1283
- , c = this;
1284
-
1285
- img.onload = function () {
1286
- var ctx = c.getContext("2d");
1287
- var oldShadowColor = ctx.shadowColor;
1288
- ctx.shadowColor = "transparent";
1289
- ctx.drawImage(
1290
- img, 0, 0
1291
- , ( img.width < c.width) ? img.width : c.width
1292
- , ( img.height < c.height) ? img.height : c.height
1293
- );
1294
- ctx.shadowColor = oldShadowColor;
1295
- };
1296
-
1297
- img.src = 'data:' + formattype + ',' + data;
1298
- }
1299
-
1300
- var importplugins = {
1301
- 'native':function(data, formattype, rerendercallable){
1302
- // we expect data as Array of objects of arrays here - whatever 'default' EXPORT plugin spits out.
1303
- // returning Truthy to indicate we are good, all updated.
1304
- rerendercallable( data );
1305
- }
1306
- , 'image': _renderImageOnCanvas
1307
- , 'image/png;base64': _renderImageOnCanvas
1308
- , 'image/jpeg;base64': _renderImageOnCanvas
1309
- , 'image/jpg;base64': _renderImageOnCanvas
1310
- };
1311
-
1312
- function _clearDrawingArea( data, dontClear ) {
1313
- this.find('canvas.'+apinamespace)
1314
- .add(this.filter('canvas.'+apinamespace))
1315
- .data(apinamespace+'.this').resetCanvas( data, dontClear );
1316
- return this;
1317
- }
1318
-
1319
- function _setDrawingData( data, formattype ) {
1320
- var undef;
1321
-
1322
- if (formattype === undef && typeof data === 'string' && data.substr(0,5) === 'data:') {
1323
- formattype = data.slice(5).split(',')[0];
1324
- // 5 chars of "data:" + mimetype len + 1 "," char = all skipped.
1325
- data = data.slice(6 + formattype.length);
1326
- if (formattype === data) {
1327
- return;
1328
- }
1329
- }
1330
-
1331
- var $canvas = this.find('canvas.'+apinamespace).add(this.filter('canvas.'+apinamespace));
1332
-
1333
- if (!importplugins.hasOwnProperty(formattype)) {
1334
- throw new Error(apinamespace + " is unable to find import plugin with for format '"+ String(formattype) +"'");
1335
- } else if ($canvas.length !== 0) {
1336
- importplugins[formattype].call(
1337
- $canvas[0]
1338
- , data
1339
- , formattype
1340
- , (function(jSignatureInstance){
1341
- return function(){ return jSignatureInstance.resetCanvas.apply(jSignatureInstance, arguments) }
1342
- })($canvas.data(apinamespace+'.this'))
1343
- );
1344
- }
1345
-
1346
- return this;
1347
- }
1348
-
1349
- var elementIsOrphan = function(e){
1350
- var topOfDOM = false;
1351
- e = e.parentNode;
1352
- while (e && !topOfDOM){
1353
- topOfDOM = e.body;
1354
- e = e.parentNode;
1355
- }
1356
- return !topOfDOM;
1357
- }
1358
-
1359
- //These are exposed as methods under $obj.jSignature('methodname', *args)
1360
- var plugins = {'export':exportplugins, 'import':importplugins, 'instance': jSignatureInstanceExtensions}
1361
- , methods = {
1362
- 'init' : function( options ) {
1363
- return this.each( function() {
1364
- if (!elementIsOrphan(this)) {
1365
- new jSignatureClass(this, options, jSignatureInstanceExtensions);
1366
- }
1367
- })
1368
- }
1369
- , 'destroy': function() {
1370
- return this.each(function() {
1371
- if(!elementIsOrphan(this)) {
1372
- var sig = $(this).find('canvas').data(apinamespace + '.this');
1373
- if(sig) {
1374
- sig.$controlbarLower.remove();
1375
- sig.$controlbarUpper.remove();
1376
- $(sig.canvas).remove();
1377
- for (var e in sig.eventTokens){
1378
- if (sig.eventTokens.hasOwnProperty(e)){
1379
- globalEvents.unsubscribe(sig.eventTokens[e]);
1380
- }
1381
- }
1382
- }
1383
- }
1384
- });
1385
- }
1386
- , 'getSettings' : function() {
1387
- return this.find('canvas.'+apinamespace)
1388
- .add(this.filter('canvas.'+apinamespace))
1389
- .data(apinamespace+'.this').settings;
1390
- }
1391
- , 'isModified' : function() {
1392
- return this.find('canvas.'+apinamespace)
1393
- .add(this.filter('canvas.'+apinamespace))
1394
- .data(apinamespace+'.this')
1395
- .dataEngine
1396
- ._stroke !== null;
1397
- }
1398
- , 'updateSetting' : function(param, val, forFuture) {
1399
- var $canvas = this.find('canvas.'+apinamespace)
1400
- .add(this.filter('canvas.'+apinamespace))
1401
- .data(apinamespace+'.this');
1402
- $canvas.settings[param] = val;
1403
- $canvas.resetCanvas(( forFuture ? null : $canvas.settings.data ), true);
1404
- return $canvas.settings[param];
1405
- }
1406
- // around since v1
1407
- , 'clear' : _clearDrawingArea
1408
- // was mistakenly introduced instead of 'clear' in v2
1409
- , 'reset' : _clearDrawingArea
1410
- , 'addPlugin' : function(pluginType, pluginName, callable){
1411
- if (plugins.hasOwnProperty(pluginType)){
1412
- plugins[pluginType][pluginName] = callable;
1413
- }
1414
- return this;
1415
- }
1416
- , 'listPlugins' : function(pluginType){
1417
- var answer = [];
1418
- if (plugins.hasOwnProperty(pluginType)){
1419
- var o = plugins[pluginType];
1420
- for (var k in o){
1421
- if (o.hasOwnProperty(k)){
1422
- answer.push(k);
1423
- }
1424
- }
1425
- }
1426
- return answer;
1427
- }
1428
- , 'getData' : function( formattype ) {
1429
- var undef, $canvas=this.find('canvas.'+apinamespace).add(this.filter('canvas.'+apinamespace));
1430
- if (formattype === undef) {
1431
- formattype = 'default';
1432
- }
1433
- if ($canvas.length !== 0 && exportplugins.hasOwnProperty(formattype)){
1434
- return exportplugins[formattype].call(
1435
- $canvas.get(0) // canvas dom elem
1436
- , $canvas.data(apinamespace+'.data') // raw signature data as array of objects of arrays
1437
- , $canvas.data(apinamespace+'.settings')
1438
- );
1439
- }
1440
- }
1441
- // around since v1. Took only one arg - data-url-formatted string with (likely png of) signature image
1442
- , 'importData' : _setDrawingData
1443
- // was mistakenly introduced instead of 'importData' in v2
1444
- , 'setData' : _setDrawingData
1445
- // this is one and same instance for all jSignature.
1446
- , 'globalEvents' : function(){return globalEvents}
1447
- , 'disable' : function() {
1448
- this.find("input").attr("disabled", 1);
1449
- this.find('canvas.'+apinamespace)
1450
- .addClass("disabled")
1451
- .data(apinamespace+'.this')
1452
- .settings
1453
- .readOnly=true;
1454
- }
1455
- , 'enable' : function() {
1456
- this.find("input").removeAttr("disabled");
1457
- this.find('canvas.'+apinamespace)
1458
- .removeClass("disabled")
1459
- .data(apinamespace+'.this')
1460
- .settings
1461
- .readOnly=false;
1462
- }
1463
- // there will be a separate one for each jSignature instance.
1464
- , 'events' : function() {
1465
- return this.find('canvas.'+apinamespace)
1466
- .add(this.filter('canvas.'+apinamespace))
1467
- .data(apinamespace+'.this').events;
1468
- }
1469
- } // end of methods declaration.
1470
-
1471
- $.fn[apinamespace] = function(method) {
1472
- 'use strict'
1473
- if ( !method || typeof method === 'object' ) {
1474
- return methods.init.apply( this, arguments );
1475
- } else if ( typeof method === 'string' && methods[method] ) {
1476
- return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
1477
- } else {
1478
- $.error( 'Method ' + String(method) + ' does not exist on jQuery.' + apinamespace );
1479
- }
1480
- }
1481
-
1482
- } // end of GlobalJSignatureObjectInitializer
1483
-
1484
- GlobalJSignatureObjectInitializer(window)
1485
-
1486
- })();
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
+ */
8
+ ;(function() {
9
+
10
+ var apinamespace = 'jSignature'
11
+
12
+ /**
13
+ Allows one to delay certain eventual action by setting up a timer for it and allowing one to delay it
14
+ by "kick"ing it. Sorta like "kick the can down the road"
15
+
16
+ @public
17
+ @class
18
+ @param
19
+ @returns {Type}
20
+ */
21
+ var KickTimerClass = function(time, callback) {
22
+ var timer;
23
+ this.kick = function() {
24
+ clearTimeout(timer);
25
+ timer = setTimeout(
26
+ callback
27
+ , time
28
+ );
29
+ }
30
+ this.clear = function() {
31
+ clearTimeout(timer);
32
+ }
33
+ return this;
34
+ }
35
+
36
+ var PubSubClass = function(context){
37
+ 'use strict'
38
+ /* @preserve
39
+ -----------------------------------------------------------------------------------------------
40
+ JavaScript PubSub library
41
+ 2012 (c) Willow Systems Corp (www.willow-systems.com)
42
+ based on Peter Higgins (dante@dojotoolkit.org)
43
+ Loosely based on Dojo publish/subscribe API, limited in scope. Rewritten blindly.
44
+ Original is (c) Dojo Foundation 2004-2010. Released under either AFL or new BSD, see:
45
+ http://dojofoundation.org/license for more information.
46
+ -----------------------------------------------------------------------------------------------
47
+ */
48
+ this.topics = {};
49
+ // here we choose what will be "this" for the called events.
50
+ // if context is defined, it's context. Else, 'this' is this instance of PubSub
51
+ this.context = context ? context : this;
52
+ /**
53
+ * Allows caller to emit an event and pass arguments to event listeners.
54
+ * @public
55
+ * @function
56
+ * @param topic {String} Name of the channel on which to voice this event
57
+ * @param **arguments Any number of arguments you want to pass to the listeners of this event.
58
+ */
59
+ this.publish = function(topic, arg1, arg2, etc) {
60
+ 'use strict'
61
+ if (this.topics[topic]) {
62
+ var currentTopic = this.topics[topic]
63
+ , args = Array.prototype.slice.call(arguments, 1)
64
+ , toremove = []
65
+ , torun = []
66
+ , fn
67
+ , i, l
68
+ , pair;
69
+
70
+ for (i = 0, l = currentTopic.length; i < l; i++) {
71
+ pair = currentTopic[i]; // this is a [function, once_flag] array
72
+ fn = pair[0];
73
+ if (pair[1] /* 'run once' flag set */){
74
+ pair[0] = function(){};
75
+ toremove.push(i);
76
+ }
77
+ /* don't call the callback right now, it might decide to add or
78
+ * remove subscribers which will wreak havoc on our index-based
79
+ * iteration */
80
+ torun.push(fn);
81
+ }
82
+ for (i = 0, l = toremove.length; i < l; i++) {
83
+ currentTopic.splice(toremove[i], 1);
84
+ }
85
+ for (i = 0, l = torun.length; i < l; i++) {
86
+ torun[i].apply(this.context, args);
87
+ }
88
+ }
89
+ }
90
+ /**
91
+ * Allows listener code to subscribe to channel and be called when data is available
92
+ * @public
93
+ * @function
94
+ * @param topic {String} Name of the channel on which to voice this event
95
+ * @param callback {Function} Executable (function pointer) that will be ran when event is voiced on this channel.
96
+ * @param once {Boolean} (optional. False by default) Flag indicating if the function is to be triggered only once.
97
+ * @returns {Object} A token object that cen be used for unsubscribing.
98
+ */
99
+ this.subscribe = function(topic, callback, once) {
100
+ 'use strict'
101
+ if (!this.topics[topic]) {
102
+ this.topics[topic] = [[callback, once]];
103
+ } else {
104
+ this.topics[topic].push([callback,once]);
105
+ }
106
+ return {
107
+ "topic": topic,
108
+ "callback": callback
109
+ };
110
+ };
111
+ /**
112
+ * Allows listener code to unsubscribe from a channel
113
+ * @public
114
+ * @function
115
+ * @param token {Object} A token object that was returned by `subscribe` method
116
+ */
117
+ this.unsubscribe = function(token) {
118
+ if (this.topics[token.topic]) {
119
+ var currentTopic = this.topics[token.topic];
120
+
121
+ for (var i = 0, l = currentTopic.length; i < l; i++) {
122
+ if (currentTopic[i] && currentTopic[i][0] === token.callback) {
123
+ currentTopic.splice(i, 1);
124
+ }
125
+ }
126
+ }
127
+ }
128
+ }
129
+
130
+ /// Returns front, back and "decor" colors derived from element (as jQuery obj)
131
+ function getColors($e){
132
+ var tmp
133
+ , undef
134
+ , frontcolor = $e.css('color')
135
+ , backcolor
136
+ , e = $e[0];
137
+
138
+ var toOfDOM = false;
139
+ while(e && !backcolor && !toOfDOM){
140
+ try{
141
+ tmp = $(e).css('background-color');
142
+ } catch (ex) {
143
+ tmp = 'transparent';
144
+ }
145
+ if (tmp !== 'transparent' && tmp !== 'rgba(0, 0, 0, 0)'){
146
+ backcolor = tmp;
147
+ }
148
+ toOfDOM = e.body;
149
+ e = e.parentNode;
150
+ }
151
+
152
+ var rgbaregex = /rgb[a]*\((\d+),\s*(\d+),\s*(\d+)/ // modern browsers
153
+ , hexregex = /#([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})/ // IE 8 and less.
154
+ , frontcolorcomponents;
155
+
156
+ // Decomposing Front color into R, G, B ints
157
+ tmp = undef;
158
+ tmp = frontcolor.match(rgbaregex);
159
+ if (tmp){
160
+ frontcolorcomponents = {'r':parseInt(tmp[1],10),'g':parseInt(tmp[2],10),'b':parseInt(tmp[3],10)};
161
+ } else {
162
+ tmp = frontcolor.match(hexregex);
163
+ if (tmp) {
164
+ frontcolorcomponents = {'r':parseInt(tmp[1],16),'g':parseInt(tmp[2],16),'b':parseInt(tmp[3],16)};
165
+ }
166
+ }
167
+ // if(!frontcolorcomponents){
168
+ // frontcolorcomponents = {'r':255,'g':255,'b':255}
169
+ // }
170
+
171
+ var backcolorcomponents
172
+ // Decomposing back color into R, G, B ints
173
+ if(!backcolor){
174
+ // HIghly unlikely since this means that no background styling was applied to any element from here to top of dom.
175
+ // we'll pick up back color from front color
176
+ if(frontcolorcomponents){
177
+ if (Math.max.apply(null, [frontcolorcomponents.r, frontcolorcomponents.g, frontcolorcomponents.b]) > 127){
178
+ backcolorcomponents = {'r':0,'g':0,'b':0};
179
+ } else {
180
+ backcolorcomponents = {'r':255,'g':255,'b':255};
181
+ }
182
+ } else {
183
+ // arg!!! front color is in format we don't understand (hsl, named colors)
184
+ // Let's just go with white background.
185
+ backcolorcomponents = {'r':255,'g':255,'b':255};
186
+ }
187
+ } else {
188
+ tmp = undef;
189
+ tmp = backcolor.match(rgbaregex);
190
+ if (tmp){
191
+ backcolorcomponents = {'r':parseInt(tmp[1],10),'g':parseInt(tmp[2],10),'b':parseInt(tmp[3],10)};
192
+ } else {
193
+ tmp = backcolor.match(hexregex);
194
+ if (tmp) {
195
+ backcolorcomponents = {'r':parseInt(tmp[1],16),'g':parseInt(tmp[2],16),'b':parseInt(tmp[3],16)};
196
+ }
197
+ }
198
+ // if(!backcolorcomponents){
199
+ // backcolorcomponents = {'r':0,'g':0,'b':0}
200
+ // }
201
+ }
202
+
203
+ // Deriving Decor color
204
+ // THis is LAZY!!!! Better way would be to use HSL and adjust luminocity. However, that could be an overkill.
205
+
206
+ var toRGBfn = function(o){return 'rgb(' + [o.r, o.g, o.b].join(', ') + ')'}
207
+ , decorcolorcomponents
208
+ , frontcolorbrightness
209
+ , adjusted;
210
+
211
+ if (frontcolorcomponents && backcolorcomponents){
212
+ var backcolorbrightness = Math.max.apply(null, [frontcolorcomponents.r, frontcolorcomponents.g, frontcolorcomponents.b]);
213
+
214
+ frontcolorbrightness = Math.max.apply(null, [backcolorcomponents.r, backcolorcomponents.g, backcolorcomponents.b]);
215
+ adjusted = Math.round(frontcolorbrightness + (-1 * (frontcolorbrightness - backcolorbrightness) * 0.75)); // "dimming" the difference between pen and back.
216
+ decorcolorcomponents = {'r':adjusted,'g':adjusted,'b':adjusted}; // always shade of gray
217
+ } else if (frontcolorcomponents) {
218
+ frontcolorbrightness = Math.max.apply(null, [frontcolorcomponents.r, frontcolorcomponents.g, frontcolorcomponents.b]);
219
+ var polarity = +1;
220
+ if (frontcolorbrightness > 127){
221
+ polarity = -1;
222
+ }
223
+ // shifting by 25% (64 points on RGB scale)
224
+ adjusted = Math.round(frontcolorbrightness + (polarity * 96)); // "dimming" the pen's color by 75% to get decor color.
225
+ decorcolorcomponents = {'r':adjusted,'g':adjusted,'b':adjusted}; // always shade of gray
226
+ } else {
227
+ decorcolorcomponents = {'r':191,'g':191,'b':191}; // always shade of gray
228
+ }
229
+
230
+ return {
231
+ 'color': frontcolor
232
+ , 'background-color': backcolorcomponents? toRGBfn(backcolorcomponents) : backcolor
233
+ , 'decor-color': toRGBfn(decorcolorcomponents)
234
+ };
235
+ }
236
+
237
+ function Vector(x,y){
238
+ this.x = x;
239
+ this.y = y;
240
+ this.reverse = function(){
241
+ return new this.constructor(
242
+ this.x * -1
243
+ , this.y * -1
244
+ );
245
+ };
246
+ this._length = null;
247
+ this.getLength = function(){
248
+ if (!this._length){
249
+ this._length = Math.sqrt( Math.pow(this.x, 2) + Math.pow(this.y, 2) );
250
+ }
251
+ return this._length;
252
+ };
253
+
254
+ var polarity = function (e){
255
+ return Math.round(e / Math.abs(e));
256
+ };
257
+ this.resizeTo = function(length){
258
+ // proportionally changes x,y such that the hypotenuse (vector length) is = new length
259
+ if (this.x === 0 && this.y === 0){
260
+ this._length = 0;
261
+ } else if (this.x === 0){
262
+ this._length = length;
263
+ this.y = length * polarity(this.y);
264
+ } else if(this.y === 0){
265
+ this._length = length;
266
+ this.x = length * polarity(this.x);
267
+ } else {
268
+ var proportion = Math.abs(this.y / this.x)
269
+ , x = Math.sqrt(Math.pow(length, 2) / (1 + Math.pow(proportion, 2)))
270
+ , y = proportion * x;
271
+ this._length = length;
272
+ this.x = x * polarity(this.x);
273
+ this.y = y * polarity(this.y);
274
+ }
275
+ return this;
276
+ };
277
+
278
+ /**
279
+ * Calculates the angle between 'this' vector and another.
280
+ * @public
281
+ * @function
282
+ * @returns {Number} The angle between the two vectors as measured in PI.
283
+ */
284
+ this.angleTo = function(vectorB) {
285
+ var divisor = this.getLength() * vectorB.getLength();
286
+ if (divisor === 0) {
287
+ return 0;
288
+ } else {
289
+ // JavaScript floating point math is screwed up.
290
+ // because of it, the core of the formula can, on occasion, have values
291
+ // over 1.0 and below -1.0.
292
+ return Math.acos(
293
+ Math.min(
294
+ Math.max(
295
+ ( this.x * vectorB.x + this.y * vectorB.y ) / divisor
296
+ , -1.0
297
+ )
298
+ , 1.0
299
+ )
300
+ ) / Math.PI;
301
+ }
302
+ };
303
+ }
304
+
305
+ function Point(x,y){
306
+ this.x = x;
307
+ this.y = y;
308
+
309
+ this.getVectorToCoordinates = function (x, y) {
310
+ return new Vector(x - this.x, y - this.y);
311
+ };
312
+ this.getVectorFromCoordinates = function (x, y) {
313
+ return this.getVectorToCoordinates(x, y).reverse();
314
+ };
315
+ this.getVectorToPoint = function (point) {
316
+ return new Vector(point.x - this.x, point.y - this.y);
317
+ };
318
+ this.getVectorFromPoint = function (point) {
319
+ return this.getVectorToPoint(point).reverse();
320
+ };
321
+ }
322
+
323
+ /*
324
+ * About data structure:
325
+ * We don't store / deal with "pictures" this signature capture code captures "vectors"
326
+ *
327
+ * We don't store bitmaps. We store "strokes" as arrays of arrays. (Actually, arrays of objects containing arrays of coordinates.
328
+ *
329
+ * Stroke = mousedown + mousemoved * n (+ mouseup but we don't record that as that was the "end / lack of movement" indicator)
330
+ *
331
+ * Vectors = not classical vectors where numbers indicated shift relative last position. Our vectors are actually coordinates against top left of canvas.
332
+ * we could calc the classical vectors, but keeping the the actual coordinates allows us (through Math.max / min)
333
+ * to calc the size of resulting drawing very quickly. If we want classical vectors later, we can always get them in backend code.
334
+ *
335
+ * So, the data structure:
336
+ *
337
+ * var data = [
338
+ * { // stroke starts
339
+ * x : [101, 98, 57, 43] // x points
340
+ * , y : [1, 23, 65, 87] // y points
341
+ * } // stroke ends
342
+ * , { // stroke starts
343
+ * x : [55, 56, 57, 58] // x points
344
+ * , y : [101, 97, 54, 4] // y points
345
+ * } // stroke ends
346
+ * , { // stroke consisting of just a dot
347
+ * x : [53] // x points
348
+ * , y : [151] // y points
349
+ * } // stroke ends
350
+ * ]
351
+ *
352
+ * 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.
353
+ *
354
+ */
355
+ function DataEngine(storageObject, context, startStrokeFn, addToStrokeFn, endStrokeFn){
356
+ this.data = storageObject; // we expect this to be an instance of Array
357
+ this.context = context;
358
+
359
+ if (storageObject.length){
360
+ // we have data to render
361
+ var numofstrokes = storageObject.length
362
+ , stroke
363
+ , numofpoints;
364
+
365
+ for (var i = 0; i < numofstrokes; i++){
366
+ stroke = storageObject[i];
367
+ numofpoints = stroke.x.length;
368
+ startStrokeFn.call(context, stroke);
369
+ for(var j = 1; j < numofpoints; j++){
370
+ addToStrokeFn.call(context, stroke, j);
371
+ }
372
+ endStrokeFn.call(context, stroke);
373
+ }
374
+ }
375
+
376
+ this.changed = function(){};
377
+
378
+ this.startStrokeFn = startStrokeFn;
379
+ this.addToStrokeFn = addToStrokeFn;
380
+ this.endStrokeFn = endStrokeFn;
381
+
382
+ this.inStroke = false;
383
+
384
+ this._lastPoint = null;
385
+ this._stroke = null;
386
+ this.startStroke = function(point){
387
+ if(point && typeof(point.x) == "number" && typeof(point.y) == "number"){
388
+ this._stroke = {'x':[point.x], 'y':[point.y]};
389
+ this.data.push(this._stroke);
390
+ this._lastPoint = point;
391
+ this.inStroke = true;
392
+ // 'this' does not work same inside setTimeout(
393
+ var stroke = this._stroke
394
+ , fn = this.startStrokeFn
395
+ , context = this.context;
396
+ setTimeout(
397
+ // some IE's don't support passing args per setTimeout API. Have to create closure every time instead.
398
+ function() {fn.call(context, stroke)}
399
+ , 3
400
+ );
401
+ return point;
402
+ } else {
403
+ return null;
404
+ }
405
+ };
406
+ // that "5" at the very end of this if is important to explain.
407
+ // we do NOT render links between two captured points (in the middle of the stroke) if the distance is shorter than that number.
408
+ // not only do we NOT render it, we also do NOT capture (add) these intermediate points to storage.
409
+ // when clustering of these is too tight, it produces noise on the line, which, because of smoothing, makes lines too curvy.
410
+ // maybe, later, we can expose this as a configurable setting of some sort.
411
+ this.addToStroke = function(point){
412
+ if (this.inStroke &&
413
+ typeof(point.x) === "number" &&
414
+ typeof(point.y) === "number" &&
415
+ // calculates absolute shift in diagonal pixels away from original point
416
+ (Math.abs(point.x - this._lastPoint.x) + Math.abs(point.y - this._lastPoint.y)) > 4
417
+ ){
418
+ var positionInStroke = this._stroke.x.length;
419
+ this._stroke.x.push(point.x);
420
+ this._stroke.y.push(point.y);
421
+ this._lastPoint = point;
422
+
423
+ var stroke = this._stroke
424
+ , fn = this.addToStrokeFn
425
+ , context = this.context;
426
+ setTimeout(
427
+ // some IE's don't support passing args per setTimeout API. Have to create closure every time instead.
428
+ function() {fn.call(context, stroke, positionInStroke)}
429
+ , 3
430
+ );
431
+ return point;
432
+ } else {
433
+ return null;
434
+ }
435
+ };
436
+ this.endStroke = function(){
437
+ var c = this.inStroke;
438
+ this.inStroke = false;
439
+ this._lastPoint = null;
440
+ if (c){
441
+ var stroke = this._stroke
442
+ , fn = this.endStrokeFn // 'this' does not work same inside setTimeout(
443
+ , context = this.context
444
+ , changedfn = this.changed;
445
+ setTimeout(
446
+ // some IE's don't support passing args per setTimeout API. Have to create closure every time instead.
447
+ function(){
448
+ fn.call(context, stroke);
449
+ changedfn.call(context);
450
+ }
451
+ , 3
452
+ );
453
+ return true;
454
+ } else {
455
+ return null;
456
+ }
457
+ };
458
+ }
459
+
460
+ var basicDot = function(ctx, x, y, size){
461
+ var fillStyle = ctx.fillStyle;
462
+ ctx.fillStyle = ctx.strokeStyle;
463
+ ctx.fillRect(x + size / -2 , y + size / -2, size, size);
464
+ ctx.fillStyle = fillStyle;
465
+ }
466
+ , basicLine = function(ctx, startx, starty, endx, endy){
467
+ ctx.beginPath();
468
+ ctx.moveTo(startx, starty);
469
+ ctx.lineTo(endx, endy);
470
+ ctx.closePath();
471
+ ctx.stroke();
472
+ }
473
+ , basicCurve = function(ctx, startx, starty, endx, endy, cp1x, cp1y, cp2x, cp2y){
474
+ ctx.beginPath();
475
+ ctx.moveTo(startx, starty);
476
+ ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, endx, endy);
477
+ ctx.closePath();
478
+ ctx.stroke();
479
+ }
480
+ , strokeStartCallback = function(stroke) {
481
+ // this = jSignatureClass instance
482
+ basicDot(this.canvasContext, stroke.x[0], stroke.y[0], this.settings.lineWidth);
483
+ }
484
+ , strokeAddCallback = function(stroke, positionInStroke){
485
+ // this = jSignatureClass instance
486
+
487
+ // Because we are funky this way, here we draw TWO curves.
488
+ // 1. POSSIBLY "this line" - spanning from point right before us, to this latest point.
489
+ // 2. POSSIBLY "prior curve" - spanning from "latest point" to the one before it.
490
+
491
+ // Why you ask?
492
+ // long lines (ones with many pixels between them) do not look good when they are part of a large curvy stroke.
493
+ // You know, the jaggedy crocodile spine instead of a pretty, smooth curve. Yuck!
494
+ // We want to approximate pretty curves in-place of those ugly lines.
495
+ // To approximate a very nice curve we need to know the direction of line before and after.
496
+ // Hence, on long lines we actually wait for another point beyond it to come back from
497
+ // mousemoved before we draw this curve.
498
+
499
+ // So for "prior curve" to be calc'ed we need 4 points
500
+ // A, B, C, D (we are on D now, A is 3 points in the past.)
501
+ // and 3 lines:
502
+ // pre-line (from points A to B),
503
+ // 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.)
504
+ // post-line (from points C to D) (even through D point is 'current' we don't know how we can draw it yet)
505
+ //
506
+ // Well, actually, we don't need to *know* the point A, just the vector A->B
507
+ var Cpoint = new Point(stroke.x[positionInStroke-1], stroke.y[positionInStroke-1])
508
+ , Dpoint = new Point(stroke.x[positionInStroke], stroke.y[positionInStroke])
509
+ , CDvector = Cpoint.getVectorToPoint(Dpoint);
510
+
511
+ // Again, we have a chance here to draw TWO things:
512
+ // BC Curve (only if it's long, because if it was short, it was drawn by previous callback) and
513
+ // CD Line (only if it's short)
514
+
515
+ // So, let's start with BC curve.
516
+ // if there is only 2 points in stroke array, we don't have "history" long enough to have point B, let alone point A.
517
+ // Falling through to drawing line CD is proper, as that's the only line we have points for.
518
+ if(positionInStroke > 1) {
519
+ // we are here when there are at least 3 points in stroke array.
520
+ var Bpoint = new Point(stroke.x[positionInStroke-2], stroke.y[positionInStroke-2])
521
+ , BCvector = Bpoint.getVectorToPoint(Cpoint)
522
+ , ABvector;
523
+ if(BCvector.getLength() > this.lineCurveThreshold){
524
+ // Yey! Pretty curves, here we come!
525
+ if(positionInStroke > 2) {
526
+ // we are here when at least 4 points in stroke array.
527
+ ABvector = (new Point(stroke.x[positionInStroke-3], stroke.y[positionInStroke-3])).getVectorToPoint(Bpoint);
528
+ } else {
529
+ ABvector = new Vector(0,0);
530
+ }
531
+
532
+ var minlenfraction = 0.05
533
+ , maxlen = BCvector.getLength() * 0.35
534
+ , ABCangle = BCvector.angleTo(ABvector.reverse())
535
+ , BCDangle = CDvector.angleTo(BCvector.reverse())
536
+ , BCP1vector = new Vector(ABvector.x + BCvector.x, ABvector.y + BCvector.y).resizeTo(
537
+ Math.max(minlenfraction, ABCangle) * maxlen
538
+ )
539
+ , CCP2vector = (new Vector(BCvector.x + CDvector.x, BCvector.y + CDvector.y)).reverse().resizeTo(
540
+ Math.max(minlenfraction, BCDangle) * maxlen
541
+ );
542
+
543
+ basicCurve(
544
+ this.canvasContext
545
+ , Bpoint.x
546
+ , Bpoint.y
547
+ , Cpoint.x
548
+ , Cpoint.y
549
+ , Bpoint.x + BCP1vector.x
550
+ , Bpoint.y + BCP1vector.y
551
+ , Cpoint.x + CCP2vector.x
552
+ , Cpoint.y + CCP2vector.y
553
+ );
554
+ }
555
+ }
556
+ if(CDvector.getLength() <= this.lineCurveThreshold){
557
+ basicLine(
558
+ this.canvasContext
559
+ , Cpoint.x
560
+ , Cpoint.y
561
+ , Dpoint.x
562
+ , Dpoint.y
563
+ );
564
+ }
565
+ }
566
+ , strokeEndCallback = function(stroke){
567
+ // this = jSignatureClass instance
568
+
569
+ // Here we tidy up things left unfinished in last strokeAddCallback run.
570
+
571
+ // What's POTENTIALLY left unfinished there is the curve between the last points
572
+ // in the stroke, if the len of that line is more than lineCurveThreshold
573
+ // If the last line was shorter than lineCurveThreshold, it was drawn there, and there
574
+ // is nothing for us here to do.
575
+ // We can also be called when there is only one point in the stroke (meaning, the
576
+ // stroke was just a dot), in which case, again, there is nothing for us to do.
577
+
578
+ // So for "this curve" to be calc'ed we need 3 points
579
+ // A, B, C
580
+ // and 2 lines:
581
+ // pre-line (from points A to B),
582
+ // this line (from points B to C)
583
+ // Well, actually, we don't need to *know* the point A, just the vector A->B
584
+ // so, we really need points B, C and AB vector.
585
+ var positionInStroke = stroke.x.length - 1;
586
+
587
+ if (positionInStroke > 0){
588
+ // there are at least 2 points in the stroke.we are in business.
589
+ var Cpoint = new Point(stroke.x[positionInStroke], stroke.y[positionInStroke])
590
+ , Bpoint = new Point(stroke.x[positionInStroke-1], stroke.y[positionInStroke-1])
591
+ , BCvector = Bpoint.getVectorToPoint(Cpoint)
592
+ , ABvector;
593
+ if (BCvector.getLength() > this.lineCurveThreshold){
594
+ // yep. This one was left undrawn in prior callback. Have to draw it now.
595
+ if (positionInStroke > 1){
596
+ // we have at least 3 elems in stroke
597
+ ABvector = (new Point(stroke.x[positionInStroke-2], stroke.y[positionInStroke-2])).getVectorToPoint(Bpoint);
598
+ var BCP1vector = new Vector(ABvector.x + BCvector.x, ABvector.y + BCvector.y).resizeTo(BCvector.getLength() / 2);
599
+ basicCurve(
600
+ this.canvasContext
601
+ , Bpoint.x
602
+ , Bpoint.y
603
+ , Cpoint.x
604
+ , Cpoint.y
605
+ , Bpoint.x + BCP1vector.x
606
+ , Bpoint.y + BCP1vector.y
607
+ , Cpoint.x
608
+ , Cpoint.y
609
+ );
610
+ } else {
611
+ // Since there is no AB leg, there is no curve to draw. This line is still "long" but no curve.
612
+ basicLine(
613
+ this.canvasContext
614
+ , Bpoint.x
615
+ , Bpoint.y
616
+ , Cpoint.x
617
+ , Cpoint.y
618
+ );
619
+ }
620
+ }
621
+ }
622
+ }
623
+
624
+
625
+ /*
626
+ var getDataStats = function(){
627
+ var strokecnt = strokes.length
628
+ , stroke
629
+ , pointid
630
+ , pointcnt
631
+ , x, y
632
+ , maxX = Number.NEGATIVE_INFINITY
633
+ , maxY = Number.NEGATIVE_INFINITY
634
+ , minX = Number.POSITIVE_INFINITY
635
+ , minY = Number.POSITIVE_INFINITY
636
+ for(strokeid = 0; strokeid < strokecnt; strokeid++){
637
+ stroke = strokes[strokeid]
638
+ pointcnt = stroke.length
639
+ for(pointid = 0; pointid < pointcnt; pointid++){
640
+ x = stroke.x[pointid]
641
+ y = stroke.y[pointid]
642
+ if (x > maxX){
643
+ maxX = x
644
+ } else if (x < minX) {
645
+ minX = x
646
+ }
647
+ if (y > maxY){
648
+ maxY = y
649
+ } else if (y < minY) {
650
+ minY = y
651
+ }
652
+ }
653
+ }
654
+ return {'maxX': maxX, 'minX': minX, 'maxY': maxY, 'minY': minY}
655
+ }
656
+ */
657
+
658
+ function conditionallyLinkCanvasResizeToWindowResize(jSignatureInstance, settingsWidth, apinamespace, globalEvents){
659
+ 'use strict'
660
+ if ( settingsWidth === 'ratio' || settingsWidth.split('')[settingsWidth.length - 1] === '%' ) {
661
+
662
+ this.eventTokens[apinamespace + '.parentresized'] = globalEvents.subscribe(
663
+ apinamespace + '.parentresized'
664
+ , (function(eventTokens, $parent, originalParentWidth, sizeRatio){
665
+ 'use strict'
666
+
667
+ return function(){
668
+ 'use strict'
669
+
670
+ var w = $parent.width();
671
+ if (w !== originalParentWidth) {
672
+
673
+ // UNsubscribing this particular instance of signature pad only.
674
+ // there is a separate `eventTokens` per each instance of signature pad
675
+ for (var key in eventTokens){
676
+ if (eventTokens.hasOwnProperty(key)) {
677
+ globalEvents.unsubscribe(eventTokens[key]);
678
+ delete eventTokens[key];
679
+ }
680
+ }
681
+
682
+ var settings = jSignatureInstance.settings;
683
+ jSignatureInstance.$parent.children().remove();
684
+ for (var key in jSignatureInstance){
685
+ if (jSignatureInstance.hasOwnProperty(key)) {
686
+ delete jSignatureInstance[key];
687
+ }
688
+ }
689
+
690
+ // scale data to new signature pad size
691
+ settings.data = (function(data, scale){
692
+ var newData = [];
693
+ var o, i, l, j, m, stroke;
694
+ for ( i = 0, l = data.length; i < l; i++) {
695
+ stroke = data[i];
696
+
697
+ o = {'x':[],'y':[]};
698
+
699
+ for ( j = 0, m = stroke.x.length; j < m; j++) {
700
+ o.x.push(stroke.x[j] * scale);
701
+ o.y.push(stroke.y[j] * scale);
702
+ }
703
+
704
+ newData.push(o);
705
+ }
706
+ return newData;
707
+ })(
708
+ settings.data
709
+ , w * 1.0 / originalParentWidth
710
+ )
711
+
712
+ $parent[apinamespace](settings);
713
+ }
714
+ }
715
+ })(
716
+ this.eventTokens
717
+ , this.$parent
718
+ , this.$parent.width()
719
+ , this.canvas.width * 1.0 / this.canvas.height
720
+ )
721
+ )
722
+ }
723
+ };
724
+
725
+
726
+ function jSignatureClass(parent, options, instanceExtensions) {
727
+
728
+ var $parent = this.$parent = $(parent)
729
+ , eventTokens = this.eventTokens = {}
730
+ , events = this.events = new PubSubClass(this)
731
+ , globalEvents = $.fn[apinamespace]('globalEvents')
732
+ , settings = {
733
+ 'width' : 'ratio'
734
+ ,'height' : 'ratio'
735
+ ,'sizeRatio': 4 // only used when height = 'ratio'
736
+ ,'color' : '#000'
737
+ ,'background-color': '#fff'
738
+ ,'decor-color': '#eee'
739
+ ,'lineWidth' : 0
740
+ ,'minFatFingerCompensation' : -10
741
+ ,'showUndoButton': false
742
+ ,'readOnly': false
743
+ ,'data': []
744
+ ,'signatureLine': false
745
+ };
746
+
747
+ $.extend(settings, getColors($parent));
748
+ if (options) {
749
+ $.extend(settings, options);
750
+ }
751
+ this.settings = settings;
752
+
753
+ for (var extensionName in instanceExtensions){
754
+ if (instanceExtensions.hasOwnProperty(extensionName)) {
755
+ instanceExtensions[extensionName].call(this, extensionName);
756
+ }
757
+ }
758
+
759
+ this.events.publish(apinamespace+'.initializing');
760
+
761
+ // these, when enabled, will hover above the sig area. Hence we append them to DOM before canvas.
762
+ this.$controlbarUpper = (function(){
763
+ var controlbarstyle = 'padding:0 !important; margin:0 !important;'+
764
+ 'width: 100% !important; height: 0 !important; -ms-touch-action: none; touch-action: none;'+
765
+ 'margin-top:-1em !important; margin-bottom:1em !important;';
766
+ return $('<div style="'+controlbarstyle+'"></div>').appendTo($parent);
767
+ })();
768
+
769
+ this.isCanvasEmulator = false; // will be flipped by initializer when needed.
770
+ var canvas = this.canvas = this.initializeCanvas(settings)
771
+ , $canvas = $(canvas);
772
+
773
+ this.$controlbarLower = (function(){
774
+ var controlbarstyle = 'padding:0 !important; margin:0 !important;'+
775
+ 'width: 100% !important; height: 0 !important; -ms-touch-action: none; touch-action: none;'+
776
+ 'margin-top:-1.5em !important; margin-bottom:1.5em !important; position: relative;';
777
+ return $('<div style="'+controlbarstyle+'"></div>').appendTo($parent);
778
+ })();
779
+
780
+ this.canvasContext = canvas.getContext("2d");
781
+
782
+ // Most of our exposed API will be looking for this:
783
+ $canvas.data(apinamespace + '.this', this);
784
+
785
+ settings.lineWidth = (function(defaultLineWidth, canvasWidth){
786
+ if (!defaultLineWidth){
787
+ return Math.max(
788
+ Math.round(canvasWidth / 400) /*+1 pixel for every extra 300px of width.*/
789
+ , 2 /* minimum line width */
790
+ );
791
+ } else {
792
+ return defaultLineWidth;
793
+ }
794
+ })(settings.lineWidth, canvas.width);
795
+
796
+ this.lineCurveThreshold = settings.lineWidth * 3;
797
+
798
+ // Add custom class if defined
799
+ if(settings.cssclass && $.trim(settings.cssclass) != "") {
800
+ $canvas.addClass(settings.cssclass);
801
+ }
802
+
803
+ // used for shifting the drawing point up on touch devices, so one can see the drawing above the finger.
804
+ this.fatFingerCompensation = 0;
805
+
806
+ var movementHandlers = (function(jSignatureInstance) {
807
+
808
+ //================================
809
+ // mouse down, move, up handlers:
810
+
811
+ // shifts - adjustment values in viewport pixels drived from position of canvas on the page
812
+ var shiftX
813
+ , shiftY
814
+ , setStartValues = function(){
815
+ var tos = $(jSignatureInstance.canvas).offset()
816
+ shiftX = tos.left * -1
817
+ shiftY = tos.top * -1
818
+ }
819
+ , getPointFromEvent = function(e) {
820
+ var firstEvent = (e.changedTouches && e.changedTouches.length > 0 ? e.changedTouches[0] : e);
821
+ // All devices i tried report correct coordinates in pageX,Y
822
+ // Android Chrome 2.3.x, 3.1, 3.2., Opera Mobile, safari iOS 4.x,
823
+ // Windows: Chrome, FF, IE9, Safari
824
+ // None of that scroll shift calc vs screenXY other sigs do is needed.
825
+ // ... oh, yeah, the "fatFinger.." is for tablets so that people see what they draw.
826
+ return new Point(
827
+ Math.round(firstEvent.pageX + shiftX)
828
+ , Math.round(firstEvent.pageY + shiftY) + jSignatureInstance.fatFingerCompensation
829
+ );
830
+ }
831
+ , timer = new KickTimerClass(
832
+ 750
833
+ , function() { jSignatureInstance.dataEngine.endStroke(); }
834
+ );
835
+
836
+ this.drawEndHandler = function(e) {
837
+ if (!jSignatureInstance.settings.readOnly) {
838
+ try { e.preventDefault(); } catch (ex) {}
839
+ timer.clear();
840
+ jSignatureInstance.dataEngine.endStroke();
841
+ }
842
+ };
843
+ this.drawStartHandler = function(e) {
844
+ if (!jSignatureInstance.settings.readOnly) {
845
+ e.preventDefault();
846
+ // for performance we cache the offsets
847
+ // we recalc these only at the beginning the stroke
848
+ setStartValues();
849
+ jSignatureInstance.dataEngine.startStroke( getPointFromEvent(e) );
850
+ timer.kick();
851
+ }
852
+ };
853
+ this.drawMoveHandler = function(e) {
854
+ if (!jSignatureInstance.settings.readOnly) {
855
+ e.preventDefault();
856
+ if (!jSignatureInstance.dataEngine.inStroke){
857
+ return;
858
+ }
859
+ jSignatureInstance.dataEngine.addToStroke( getPointFromEvent(e) );
860
+ timer.kick();
861
+ }
862
+ };
863
+
864
+ return this;
865
+
866
+ }).call( {}, this )
867
+
868
+ //
869
+ //================================
870
+
871
+ ;(function(drawEndHandler, drawStartHandler, drawMoveHandler) {
872
+ var canvas = this.canvas
873
+ , $canvas = $(canvas)
874
+ , undef;
875
+ if (this.isCanvasEmulator){
876
+ $canvas.bind('mousemove.'+apinamespace, drawMoveHandler);
877
+ $canvas.bind('mouseup.'+apinamespace, drawEndHandler);
878
+ $canvas.bind('mousedown.'+apinamespace, drawStartHandler);
879
+ } else {
880
+ var hasEventListener = typeof canvas.addEventListener === 'function';
881
+ this.ontouchstart = function(e) {
882
+ canvas.onmousedown = canvas.onmouseup = canvas.onmousemove = undef;
883
+
884
+ this.fatFingerCompensation = (
885
+ settings.minFatFingerCompensation &&
886
+ settings.lineWidth * -3 > settings.minFatFingerCompensation
887
+ ) ? settings.lineWidth * -3 : settings.minFatFingerCompensation;
888
+
889
+ drawStartHandler(e);
890
+
891
+ if (hasEventListener) {
892
+ canvas.addEventListener('touchend', drawEndHandler);
893
+ canvas.addEventListener('touchstart', drawStartHandler);
894
+ canvas.addEventListener('touchmove', drawMoveHandler);
895
+ } else {
896
+ canvas.ontouchend = drawEndHandler;
897
+ canvas.ontouchstart = drawStartHandler;
898
+ canvas.ontouchmove = drawMoveHandler;
899
+ }
900
+ };
901
+
902
+ if (hasEventListener) {
903
+ canvas.addEventListener('touchstart', this.ontouchstart);
904
+ } else {
905
+ canvas.ontouchstart = ontouchstart;
906
+ }
907
+
908
+ canvas.onmousedown = function(e) {
909
+ if (hasEventListener) {
910
+ canvas.removeEventListener('touchstart', this.ontouchstart);
911
+ } else {
912
+ canvas.ontouchstart = canvas.ontouchend = canvas.ontouchmove = undef;
913
+ }
914
+
915
+ drawStartHandler(e);
916
+
917
+ canvas.onmousedown = drawStartHandler;
918
+ canvas.onmouseup = drawEndHandler;
919
+ canvas.onmousemove = drawMoveHandler;
920
+ }
921
+ if (window.navigator.msPointerEnabled) {
922
+ canvas.onmspointerdown = drawStartHandler;
923
+ canvas.onmspointerup = drawEndHandler;
924
+ canvas.onmspointermove = drawMoveHandler;
925
+ }
926
+ }
927
+ }).call(
928
+ this
929
+ , movementHandlers.drawEndHandler
930
+ , movementHandlers.drawStartHandler
931
+ , movementHandlers.drawMoveHandler
932
+ )
933
+
934
+ //=========================================
935
+ // various event handlers
936
+
937
+ // on mouseout + mouseup canvas did not know that mouseUP fired. Continued to draw despite mouse UP.
938
+ // it is bettr than
939
+ // $canvas.bind('mouseout', drawEndHandler)
940
+ // because we don't want to break the stroke where user accidentally gets ouside and wants to get back in quickly.
941
+ eventTokens[apinamespace + '.windowmouseup'] = globalEvents.subscribe(
942
+ apinamespace + '.windowmouseup'
943
+ , movementHandlers.drawEndHandler
944
+ );
945
+
946
+ this.events.publish(apinamespace+'.attachingEventHandlers');
947
+
948
+ // If we have proportional width, we sign up to events broadcasting "window resized" and checking if
949
+ // parent's width changed. If so, we (1) extract settings + data from current signature pad,
950
+ // (2) remove signature pad from parent, and (3) reinit new signature pad at new size with same settings, (rescaled) data.
951
+ conditionallyLinkCanvasResizeToWindowResize.call(
952
+ this
953
+ , this
954
+ , settings.width.toString(10)
955
+ , apinamespace, globalEvents
956
+ );
957
+
958
+ // end of event handlers.
959
+ // ===============================
960
+
961
+ this.resetCanvas(settings.data);
962
+
963
+ // resetCanvas renders the data on the screen and fires ONE "change" event
964
+ // if there is data. If you have controls that rely on "change" firing
965
+ // attach them to something that runs before this.resetCanvas, like
966
+ // apinamespace+'.attachingEventHandlers' that fires a bit higher.
967
+ this.events.publish(apinamespace+'.initialized');
968
+
969
+ return this;
970
+ } // end of initBase
971
+
972
+ //=========================================================================
973
+ // jSignatureClass's methods and supporting fn's
974
+
975
+ jSignatureClass.prototype.resetCanvas = function(data, dontClear){
976
+ var canvas = this.canvas
977
+ , settings = this.settings
978
+ , ctx = this.canvasContext
979
+ , isCanvasEmulator = this.isCanvasEmulator
980
+ , cw = canvas.width
981
+ , ch = canvas.height;
982
+
983
+ // preparing colors, drawing area
984
+ if (!dontClear){
985
+ ctx.clearRect(0, 0, cw + 30, ch + 30);
986
+ }
987
+
988
+ ctx.shadowColor = ctx.fillStyle = settings['background-color']
989
+ if (isCanvasEmulator){
990
+ // FLashCanvas fills with Black by default, covering up the parent div's background
991
+ // hence we refill
992
+ ctx.fillRect(0,0,cw + 30, ch + 30);
993
+ }
994
+
995
+ ctx.lineWidth = Math.ceil(parseInt(settings.lineWidth, 10));
996
+ ctx.lineCap = ctx.lineJoin = "round";
997
+
998
+ // signature line
999
+ if(settings.signatureLine) {
1000
+ if (null != settings['decor-color']) {
1001
+ ctx.strokeStyle = settings['decor-color'];
1002
+ ctx.shadowOffsetX = 0;
1003
+ ctx.shadowOffsetY = 0;
1004
+ var lineoffset = Math.round( ch / 5 );
1005
+ basicLine(ctx, lineoffset * 1.5, ch - lineoffset, cw - (lineoffset * 1.5), ch - lineoffset);
1006
+ }
1007
+
1008
+ if (!isCanvasEmulator){
1009
+ ctx.shadowColor = ctx.strokeStyle;
1010
+ ctx.shadowOffsetX = ctx.lineWidth * 0.5;
1011
+ ctx.shadowOffsetY = ctx.lineWidth * -0.6;
1012
+ ctx.shadowBlur = 0;
1013
+ }
1014
+ }
1015
+
1016
+ ctx.strokeStyle = settings.color;
1017
+
1018
+ // setting up new dataEngine
1019
+
1020
+ if (!data) { data = []; }
1021
+
1022
+ var dataEngine = this.dataEngine = new DataEngine(
1023
+ data
1024
+ , this
1025
+ , strokeStartCallback
1026
+ , strokeAddCallback
1027
+ , strokeEndCallback
1028
+ );
1029
+
1030
+ settings.data = data; // onwindowresize handler uses it, i think.
1031
+ $(canvas).data(apinamespace+'.data', data)
1032
+ .data(apinamespace+'.settings', settings);
1033
+
1034
+ // we fire "change" event on every change in data.
1035
+ // setting this up:
1036
+ dataEngine.changed = (function(target, events, apinamespace) {
1037
+ 'use strict'
1038
+ return function() {
1039
+ events.publish(apinamespace+'.change');
1040
+ target.trigger('change');
1041
+ }
1042
+ })(this.$parent, this.events, apinamespace);
1043
+ // let's trigger change on all data reloads
1044
+ dataEngine.changed();
1045
+
1046
+ // import filters will be passing this back as indication of "we rendered"
1047
+ return true;
1048
+ };
1049
+
1050
+ function initializeCanvasEmulator(canvas){
1051
+ if (canvas.getContext){
1052
+ return false;
1053
+ } else {
1054
+ // for cases when jSignature, FlashCanvas is inserted
1055
+ // from one window into another (child iframe)
1056
+ // 'window' and 'FlashCanvas' may be stuck behind
1057
+ // in that other parent window.
1058
+ // we need to find it
1059
+ var window = canvas.ownerDocument.parentWindow;
1060
+ var FC = window.FlashCanvas ?
1061
+ canvas.ownerDocument.parentWindow.FlashCanvas :
1062
+ (
1063
+ typeof FlashCanvas === "undefined" ?
1064
+ undefined :
1065
+ FlashCanvas
1066
+ );
1067
+
1068
+ if (FC) {
1069
+ canvas = FC.initElement(canvas);
1070
+
1071
+ var zoom = 1;
1072
+ // FlashCanvas uses flash which has this annoying habit of NOT scaling with page zoom.
1073
+ // It matches pixel-to-pixel to screen instead.
1074
+ // Since we are targeting ONLY IE 7, 8 with FlashCanvas, we will test the zoom only the IE8, IE7 way
1075
+ if (window && window.screen && window.screen.deviceXDPI && window.screen.logicalXDPI){
1076
+ zoom = window.screen.deviceXDPI * 1.0 / window.screen.logicalXDPI;
1077
+ }
1078
+ if (zoom !== 1){
1079
+ try {
1080
+ // We effectively abuse the brokenness of FlashCanvas and force the flash rendering surface to
1081
+ // occupy larger pixel dimensions than the wrapping, scaled up DIV and Canvas elems.
1082
+ $(canvas).children('object').get(0).resize(Math.ceil(canvas.width * zoom), Math.ceil(canvas.height * zoom));
1083
+ // And by applying "scale" transformation we can talk "browser pixels" to FlashCanvas
1084
+ // and have it translate the "browser pixels" to "screen pixels"
1085
+ canvas.getContext('2d').scale(zoom, zoom);
1086
+ // Note to self: don't reuse Canvas element. Repeated "scale" are cumulative.
1087
+ } catch (ex) {}
1088
+ }
1089
+ return true;
1090
+ } else {
1091
+ throw new Error("Canvas element does not support 2d context. jSignature cannot proceed.");
1092
+ }
1093
+ }
1094
+
1095
+ }
1096
+
1097
+ jSignatureClass.prototype.initializeCanvas = function(settings) {
1098
+ // ===========
1099
+ // Init + Sizing code
1100
+
1101
+ var canvas = document.createElement('canvas')
1102
+ , $canvas = $(canvas);
1103
+
1104
+ // We cannot work with circular dependency
1105
+ if (settings.width === settings.height && settings.height === 'ratio') {
1106
+ settings.width = '100%';
1107
+ }
1108
+
1109
+ $canvas.css(
1110
+ {
1111
+ 'margin': 0,
1112
+ 'padding': 0,
1113
+ 'border': 'none',
1114
+ 'height': settings.height === 'ratio' || !settings.height ? 1 : settings.height.toString(10),
1115
+ 'width': settings.width === 'ratio' || !settings.width ? 1 : settings.width.toString(10),
1116
+ '-ms-touch-action': 'none',
1117
+ 'touch-action': 'none',
1118
+ 'background-color': settings['background-color']
1119
+ }
1120
+ );
1121
+
1122
+ $canvas.appendTo(this.$parent);
1123
+
1124
+ // we could not do this until canvas is rendered (appended to DOM)
1125
+ if (settings.height === 'ratio') {
1126
+ $canvas.css(
1127
+ 'height'
1128
+ , Math.round( $canvas.width() / settings.sizeRatio )
1129
+ );
1130
+ } else if (settings.width === 'ratio') {
1131
+ $canvas.css(
1132
+ 'width'
1133
+ , Math.round( $canvas.height() * settings.sizeRatio )
1134
+ );
1135
+ }
1136
+
1137
+ $canvas.addClass(apinamespace);
1138
+
1139
+ // canvas's drawing area resolution is independent from canvas's size.
1140
+ // pixels are just scaled up or down when internal resolution does not
1141
+ // match external size. So...
1142
+
1143
+ canvas.width = $canvas.width();
1144
+ canvas.height = $canvas.height();
1145
+
1146
+ // Special case Sizing code
1147
+
1148
+ this.isCanvasEmulator = initializeCanvasEmulator(canvas);
1149
+
1150
+ // End of Sizing Code
1151
+ // ===========
1152
+
1153
+ // normally select preventer would be short, but
1154
+ // Canvas emulator on IE does NOT provide value for Event. Hence this convoluted line.
1155
+ canvas.onselectstart = function(e){if(e && e.preventDefault){e.preventDefault()}; if(e && e.stopPropagation){e.stopPropagation()}; return false;};
1156
+
1157
+ return canvas;
1158
+ }
1159
+
1160
+
1161
+ var GlobalJSignatureObjectInitializer = function(window){
1162
+
1163
+ var globalEvents = new PubSubClass();
1164
+
1165
+ // common "window resized" event listener.
1166
+ // jSignature instances will subscribe to this chanel.
1167
+ // to resize themselves when needed.
1168
+ ;(function(globalEvents, apinamespace, $, window){
1169
+ 'use strict'
1170
+
1171
+ var resizetimer
1172
+ , runner = function(){
1173
+ globalEvents.publish(
1174
+ apinamespace + '.parentresized'
1175
+ )
1176
+ };
1177
+
1178
+ // jSignature knows how to resize its content when its parent is resized
1179
+ // window resize is the only way we can catch resize events though...
1180
+ $(window).bind('resize.'+apinamespace, function(){
1181
+ if (resizetimer) {
1182
+ clearTimeout(resizetimer);
1183
+ }
1184
+ resizetimer = setTimeout(
1185
+ runner
1186
+ , 500
1187
+ );
1188
+ })
1189
+ // when mouse exists canvas element and "up"s outside, we cannot catch it with
1190
+ // callbacks attached to canvas. This catches it outside.
1191
+ .bind('mouseup.'+apinamespace, function(e){
1192
+ globalEvents.publish(
1193
+ apinamespace + '.windowmouseup'
1194
+ )
1195
+ });
1196
+
1197
+ })(globalEvents, apinamespace, $, window)
1198
+
1199
+ var jSignatureInstanceExtensions = {
1200
+ /*
1201
+ 'exampleExtension':function(extensionName){
1202
+ // we are called very early in instance's life.
1203
+ // right after the settings are resolved and
1204
+ // jSignatureInstance.events is created
1205
+ // and right before first ("jSignature.initializing") event is called.
1206
+ // You don't really need to manupilate
1207
+ // jSignatureInstance directly, just attach
1208
+ // a bunch of events to jSignatureInstance.events
1209
+ // (look at the source of jSignatureClass to see when these fire)
1210
+ // and your special pieces of code will attach by themselves.
1211
+
1212
+ // this function runs every time a new instance is set up.
1213
+ // this means every var you create will live only for one instance
1214
+ // unless you attach it to something outside, like "window."
1215
+ // and pick it up later from there.
1216
+
1217
+ // when globalEvents' events fire, 'this' is globalEvents object
1218
+ // when jSignatureInstance's events fire, 'this' is jSignatureInstance
1219
+
1220
+ // Here,
1221
+ // this = is new jSignatureClass's instance.
1222
+
1223
+ // The way you COULD approch setting this up is:
1224
+ // if you have multistep set up, attach event to "jSignature.initializing"
1225
+ // that attaches other events to be fired further lower the init stream.
1226
+ // Or, if you know for sure you rely on only one jSignatureInstance's event,
1227
+ // just attach to it directly
1228
+
1229
+ this.events.subscribe(
1230
+ // name of the event
1231
+ apinamespace + '.initializing'
1232
+ // event handlers, can pass args too, but in majority of cases,
1233
+ // 'this' which is jSignatureClass object instance pointer is enough to get by.
1234
+ , function(){
1235
+ if (this.settings.hasOwnProperty('non-existent setting category?')) {
1236
+ console.log(extensionName + ' is here')
1237
+ }
1238
+ }
1239
+ )
1240
+ }
1241
+ */
1242
+ };
1243
+
1244
+ var exportplugins = {
1245
+ 'default':function(data){return this.toDataURL()}
1246
+ , 'native':function(data){return data}
1247
+ , 'image':function(data){
1248
+ /*this = canvas elem */
1249
+ var imagestring = this.toDataURL();
1250
+
1251
+ if (typeof imagestring === 'string' &&
1252
+ imagestring.length > 4 &&
1253
+ imagestring.slice(0,5) === 'data:' &&
1254
+ imagestring.indexOf(',') !== -1){
1255
+
1256
+ var splitterpos = imagestring.indexOf(',');
1257
+
1258
+ return [
1259
+ imagestring.slice(5, splitterpos)
1260
+ , imagestring.substr(splitterpos + 1)
1261
+ ];
1262
+ }
1263
+ return [];
1264
+ }
1265
+ };
1266
+
1267
+ // will be part of "importplugins"
1268
+ function _renderImageOnCanvas( data, formattype, rerendercallable ) {
1269
+ 'use strict'
1270
+ // #1. Do NOT rely on this. No worky on IE
1271
+ // (url max len + lack of base64 decoder + possibly other issues)
1272
+ // #2. This does NOT affect what is captured as "signature" as far as vector data is
1273
+ // concerned. This is treated same as "signature line" - i.e. completely ignored
1274
+ // the only time you see imported image data exported is if you export as image.
1275
+
1276
+ // we do NOT call rerendercallable here (unlike in other import plugins)
1277
+ // because importing image does absolutely nothing to the underlying vector data storage
1278
+ // This could be a way to "import" old signatures stored as images
1279
+ // This could also be a way to import extra decor into signature area.
1280
+
1281
+ var img = new Image()
1282
+ // this = Canvas DOM elem. Not jQuery object. Not Canvas's parent div.
1283
+ , c = this;
1284
+
1285
+ img.onload = function () {
1286
+ var ctx = c.getContext("2d");
1287
+ var oldShadowColor = ctx.shadowColor;
1288
+ ctx.shadowColor = "transparent";
1289
+ ctx.drawImage(
1290
+ img, 0, 0
1291
+ , ( img.width < c.width) ? img.width : c.width
1292
+ , ( img.height < c.height) ? img.height : c.height
1293
+ );
1294
+ ctx.shadowColor = oldShadowColor;
1295
+ };
1296
+
1297
+ img.src = 'data:' + formattype + ',' + data;
1298
+ }
1299
+
1300
+ var importplugins = {
1301
+ 'native':function(data, formattype, rerendercallable){
1302
+ // we expect data as Array of objects of arrays here - whatever 'default' EXPORT plugin spits out.
1303
+ // returning Truthy to indicate we are good, all updated.
1304
+ rerendercallable( data );
1305
+ }
1306
+ , 'image': _renderImageOnCanvas
1307
+ , 'image/png;base64': _renderImageOnCanvas
1308
+ , 'image/jpeg;base64': _renderImageOnCanvas
1309
+ , 'image/jpg;base64': _renderImageOnCanvas
1310
+ };
1311
+
1312
+ function _clearDrawingArea( data, dontClear ) {
1313
+ this.find('canvas.'+apinamespace)
1314
+ .add(this.filter('canvas.'+apinamespace))
1315
+ .data(apinamespace+'.this').resetCanvas( data, dontClear );
1316
+ return this;
1317
+ }
1318
+
1319
+ function _setDrawingData( data, formattype ) {
1320
+ var undef;
1321
+
1322
+ if (formattype === undef && typeof data === 'string' && data.substr(0,5) === 'data:') {
1323
+ formattype = data.slice(5).split(',')[0];
1324
+ // 5 chars of "data:" + mimetype len + 1 "," char = all skipped.
1325
+ data = data.slice(6 + formattype.length);
1326
+ if (formattype === data) {
1327
+ return;
1328
+ }
1329
+ }
1330
+
1331
+ var $canvas = this.find('canvas.'+apinamespace).add(this.filter('canvas.'+apinamespace));
1332
+
1333
+ if (!importplugins.hasOwnProperty(formattype)) {
1334
+ throw new Error(apinamespace + " is unable to find import plugin with for format '"+ String(formattype) +"'");
1335
+ } else if ($canvas.length !== 0) {
1336
+ importplugins[formattype].call(
1337
+ $canvas[0]
1338
+ , data
1339
+ , formattype
1340
+ , (function(jSignatureInstance){
1341
+ return function(){ return jSignatureInstance.resetCanvas.apply(jSignatureInstance, arguments) }
1342
+ })($canvas.data(apinamespace+'.this'))
1343
+ );
1344
+ }
1345
+
1346
+ return this;
1347
+ }
1348
+
1349
+ var elementIsOrphan = function(e){
1350
+ var topOfDOM = false;
1351
+ e = e.parentNode;
1352
+ while (e && !topOfDOM){
1353
+ topOfDOM = e.body;
1354
+ e = e.parentNode;
1355
+ }
1356
+ return !topOfDOM;
1357
+ }
1358
+
1359
+ //These are exposed as methods under $obj.jSignature('methodname', *args)
1360
+ var plugins = {'export':exportplugins, 'import':importplugins, 'instance': jSignatureInstanceExtensions}
1361
+ , methods = {
1362
+ 'init' : function( options ) {
1363
+ return this.each( function() {
1364
+ if (!elementIsOrphan(this)) {
1365
+ new jSignatureClass(this, options, jSignatureInstanceExtensions);
1366
+ }
1367
+ })
1368
+ }
1369
+ , 'destroy': function() {
1370
+ return this.each(function() {
1371
+ if(!elementIsOrphan(this)) {
1372
+ var sig = $(this).find('canvas').data(apinamespace + '.this');
1373
+ if(sig) {
1374
+ sig.$controlbarLower.remove();
1375
+ sig.$controlbarUpper.remove();
1376
+ $(sig.canvas).remove();
1377
+ for (var e in sig.eventTokens){
1378
+ if (sig.eventTokens.hasOwnProperty(e)){
1379
+ globalEvents.unsubscribe(sig.eventTokens[e]);
1380
+ }
1381
+ }
1382
+ }
1383
+ }
1384
+ });
1385
+ }
1386
+ , 'getSettings' : function() {
1387
+ return this.find('canvas.'+apinamespace)
1388
+ .add(this.filter('canvas.'+apinamespace))
1389
+ .data(apinamespace+'.this').settings;
1390
+ }
1391
+ , 'isModified' : function() {
1392
+ return this.find('canvas.'+apinamespace)
1393
+ .add(this.filter('canvas.'+apinamespace))
1394
+ .data(apinamespace+'.this')
1395
+ .dataEngine
1396
+ ._stroke !== null;
1397
+ }
1398
+ , 'updateSetting' : function(param, val, forFuture) {
1399
+ var $canvas = this.find('canvas.'+apinamespace)
1400
+ .add(this.filter('canvas.'+apinamespace))
1401
+ .data(apinamespace+'.this');
1402
+ $canvas.settings[param] = val;
1403
+ $canvas.resetCanvas(( forFuture ? null : $canvas.settings.data ), true);
1404
+ return $canvas.settings[param];
1405
+ }
1406
+ // around since v1
1407
+ , 'clear' : _clearDrawingArea
1408
+ // was mistakenly introduced instead of 'clear' in v2
1409
+ , 'reset' : _clearDrawingArea
1410
+ , 'addPlugin' : function(pluginType, pluginName, callable){
1411
+ if (plugins.hasOwnProperty(pluginType)){
1412
+ plugins[pluginType][pluginName] = callable;
1413
+ }
1414
+ return this;
1415
+ }
1416
+ , 'listPlugins' : function(pluginType){
1417
+ var answer = [];
1418
+ if (plugins.hasOwnProperty(pluginType)){
1419
+ var o = plugins[pluginType];
1420
+ for (var k in o){
1421
+ if (o.hasOwnProperty(k)){
1422
+ answer.push(k);
1423
+ }
1424
+ }
1425
+ }
1426
+ return answer;
1427
+ }
1428
+ , 'getData' : function( formattype ) {
1429
+ var undef, $canvas=this.find('canvas.'+apinamespace).add(this.filter('canvas.'+apinamespace));
1430
+ if (formattype === undef) {
1431
+ formattype = 'default';
1432
+ }
1433
+ if ($canvas.length !== 0 && exportplugins.hasOwnProperty(formattype)){
1434
+ return exportplugins[formattype].call(
1435
+ $canvas.get(0) // canvas dom elem
1436
+ , $canvas.data(apinamespace+'.data') // raw signature data as array of objects of arrays
1437
+ , $canvas.data(apinamespace+'.settings')
1438
+ );
1439
+ }
1440
+ }
1441
+ // around since v1. Took only one arg - data-url-formatted string with (likely png of) signature image
1442
+ , 'importData' : _setDrawingData
1443
+ // was mistakenly introduced instead of 'importData' in v2
1444
+ , 'setData' : _setDrawingData
1445
+ // this is one and same instance for all jSignature.
1446
+ , 'globalEvents' : function(){return globalEvents}
1447
+ , 'disable' : function() {
1448
+ this.find("input").attr("disabled", 1);
1449
+ this.find('canvas.'+apinamespace)
1450
+ .addClass("disabled")
1451
+ .data(apinamespace+'.this')
1452
+ .settings
1453
+ .readOnly=true;
1454
+ }
1455
+ , 'enable' : function() {
1456
+ this.find("input").removeAttr("disabled");
1457
+ this.find('canvas.'+apinamespace)
1458
+ .removeClass("disabled")
1459
+ .data(apinamespace+'.this')
1460
+ .settings
1461
+ .readOnly=false;
1462
+ }
1463
+ // there will be a separate one for each jSignature instance.
1464
+ , 'events' : function() {
1465
+ return this.find('canvas.'+apinamespace)
1466
+ .add(this.filter('canvas.'+apinamespace))
1467
+ .data(apinamespace+'.this').events;
1468
+ }
1469
+ } // end of methods declaration.
1470
+
1471
+ $.fn[apinamespace] = function(method) {
1472
+ 'use strict'
1473
+ if ( !method || typeof method === 'object' ) {
1474
+ return methods.init.apply( this, arguments );
1475
+ } else if ( typeof method === 'string' && methods[method] ) {
1476
+ return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
1477
+ } else {
1478
+ $.error( 'Method ' + String(method) + ' does not exist on jQuery.' + apinamespace );
1479
+ }
1480
+ }
1481
+
1482
+ } // end of GlobalJSignatureObjectInitializer
1483
+
1484
+ GlobalJSignatureObjectInitializer(window)
1485
+
1486
+ })();