p5.svg.js 87 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583
  1. /*!!
  2. * p5.svg v0.5.2
  3. * SVG Runtime for p5.js.
  4. *
  5. * Copyright (C) 2015-2016 Zeno Zeng
  6. * Licensed under the LGPL license.
  7. */
  8. (function (root, factory) {
  9. if (typeof define === 'function' && define.amd) {
  10. define('p5.svg', ['p5'], function (p5) {
  11. factory(p5);
  12. });
  13. }
  14. else if (typeof exports === 'object') {
  15. module.exports = factory;
  16. }
  17. else {
  18. factory(root['p5']);
  19. }
  20. })(this, function (p5) {
  21. (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
  22. /*!!
  23. * Canvas 2 Svg v1.0.9
  24. * A low level canvas to SVG converter. Uses a mock canvas context to build an SVG document.
  25. *
  26. * Licensed under the MIT license:
  27. * http://www.opensource.org/licenses/mit-license.php
  28. *
  29. * Author:
  30. * Kerry Liu
  31. *
  32. * Copyright (c) 2014 Gliffy Inc.
  33. */
  34. ;(function() {
  35. "use strict";
  36. var STYLES, ctx, CanvasGradient, CanvasPattern, namedEntities;
  37. //helper function to format a string
  38. function format(str, args) {
  39. var keys = Object.keys(args), i;
  40. for (i=0; i<keys.length; i++) {
  41. str = str.replace(new RegExp("\\{" + keys[i] + "\\}", "gi"), args[keys[i]]);
  42. }
  43. return str;
  44. }
  45. //helper function that generates a random string
  46. function randomString(holder) {
  47. var chars, randomstring, i;
  48. if (!holder) {
  49. throw new Error("cannot create a random attribute name for an undefined object");
  50. }
  51. chars = "ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
  52. randomstring = "";
  53. do {
  54. randomstring = "";
  55. for (i = 0; i < 12; i++) {
  56. randomstring += chars[Math.floor(Math.random() * chars.length)];
  57. }
  58. } while (holder[randomstring]);
  59. return randomstring;
  60. }
  61. //helper function to map named to numbered entities
  62. function createNamedToNumberedLookup(items, radix) {
  63. var i, entity, lookup = {}, base10, base16;
  64. items = items.split(',');
  65. radix = radix || 10;
  66. // Map from named to numbered entities.
  67. for (i = 0; i < items.length; i += 2) {
  68. entity = '&' + items[i + 1] + ';';
  69. base10 = parseInt(items[i], radix);
  70. lookup[entity] = '&#'+base10+';';
  71. }
  72. //FF and IE need to create a regex from hex values ie &nbsp; == \xa0
  73. lookup["\\xa0"] = '&#160;';
  74. return lookup;
  75. }
  76. //helper function to map canvas-textAlign to svg-textAnchor
  77. function getTextAnchor(textAlign) {
  78. //TODO: support rtl languages
  79. var mapping = {"left":"start", "right":"end", "center":"middle", "start":"start", "end":"end"};
  80. return mapping[textAlign] || mapping.start;
  81. }
  82. //helper function to map canvas-textBaseline to svg-dominantBaseline
  83. function getDominantBaseline(textBaseline) {
  84. //INFO: not supported in all browsers
  85. var mapping = {"alphabetic": "alphabetic", "hanging": "hanging", "top":"text-before-edge", "bottom":"text-after-edge", "middle":"central"};
  86. return mapping[textBaseline] || mapping.alphabetic;
  87. }
  88. // Unpack entities lookup where the numbers are in radix 32 to reduce the size
  89. // entity mapping courtesy of tinymce
  90. namedEntities = createNamedToNumberedLookup(
  91. '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
  92. '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
  93. '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
  94. '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
  95. '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
  96. '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
  97. '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
  98. '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
  99. '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
  100. '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
  101. 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
  102. 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
  103. 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
  104. 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
  105. 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
  106. '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
  107. '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
  108. '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
  109. '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
  110. '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
  111. 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
  112. 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
  113. 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
  114. '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
  115. '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);
  116. //Some basic mappings for attributes and default values.
  117. STYLES = {
  118. "strokeStyle":{
  119. svgAttr : "stroke", //corresponding svg attribute
  120. canvas : "#000000", //canvas default
  121. svg : "none", //svg default
  122. apply : "stroke" //apply on stroke() or fill()
  123. },
  124. "fillStyle":{
  125. svgAttr : "fill",
  126. canvas : "#000000",
  127. svg : null, //svg default is black, but we need to special case this to handle canvas stroke without fill
  128. apply : "fill"
  129. },
  130. "lineCap":{
  131. svgAttr : "stroke-linecap",
  132. canvas : "butt",
  133. svg : "butt",
  134. apply : "stroke"
  135. },
  136. "lineJoin":{
  137. svgAttr : "stroke-linejoin",
  138. canvas : "miter",
  139. svg : "miter",
  140. apply : "stroke"
  141. },
  142. "miterLimit":{
  143. svgAttr : "stroke-miterlimit",
  144. canvas : 10,
  145. svg : 4,
  146. apply : "stroke"
  147. },
  148. "lineWidth":{
  149. svgAttr : "stroke-width",
  150. canvas : 1,
  151. svg : 1,
  152. apply : "stroke"
  153. },
  154. "globalAlpha": {
  155. svgAttr : "opacity",
  156. canvas : 1,
  157. svg : 1,
  158. apply : "fill stroke"
  159. },
  160. "font":{
  161. //font converts to multiple svg attributes, there is custom logic for this
  162. canvas : "10px sans-serif"
  163. },
  164. "shadowColor":{
  165. canvas : "#000000"
  166. },
  167. "shadowOffsetX":{
  168. canvas : 0
  169. },
  170. "shadowOffsetY":{
  171. canvas : 0
  172. },
  173. "shadowBlur":{
  174. canvas : 0
  175. },
  176. "textAlign":{
  177. canvas : "start"
  178. },
  179. "textBaseline":{
  180. canvas : "alphabetic"
  181. }
  182. };
  183. /**
  184. *
  185. * @param gradientNode - reference to the gradient
  186. * @constructor
  187. */
  188. CanvasGradient = function(gradientNode) {
  189. this.__root = gradientNode;
  190. };
  191. /**
  192. * Adds a color stop to the gradient root
  193. */
  194. CanvasGradient.prototype.addColorStop = function(offset, color) {
  195. var stop = this.__createElement("stop"), regex, matches;
  196. stop.setAttribute("offset", offset);
  197. if(color.indexOf("rgba") !== -1) {
  198. //separate alpha value, since webkit can't handle it
  199. regex = /rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d?\.?\d*)\s*\)/gi;
  200. matches = regex.exec(color);
  201. stop.setAttribute("stop-color", format("rgb({r},{g},{b})", {r:matches[1], g:matches[2], b:matches[3]}));
  202. stop.setAttribute("stop-opacity", matches[4]);
  203. } else {
  204. stop.setAttribute("stop-color", color);
  205. }
  206. this.__root.appendChild(stop);
  207. };
  208. CanvasPattern = function(pattern, ctx) {
  209. this.__root = pattern;
  210. this.__ctx = ctx;
  211. };
  212. /**
  213. * The mock canvas context
  214. * @param o - options include:
  215. * width - width of your canvas (defaults to 500)
  216. * height - height of your canvas (defaults to 500)
  217. * enableMirroring - enables canvas mirroring (get image data) (defaults to false)
  218. */
  219. ctx = function(o) {
  220. var defaultOptions = { width:500, height:500, enableMirroring : false }, options;
  221. //keep support for this way of calling C2S: new C2S(width,height)
  222. if(arguments.length > 1) {
  223. options = defaultOptions;
  224. options.width = arguments[0];
  225. options.height = arguments[1];
  226. } else if( !o ) {
  227. options = defaultOptions;
  228. } else {
  229. options = o;
  230. }
  231. if(!(this instanceof ctx)) {
  232. //did someone call this without new?
  233. return new ctx(options);
  234. }
  235. //setup options
  236. this.width = options.width || defaultOptions.width;
  237. this.height = options.height || defaultOptions.height;
  238. this.enableMirroring = options.enableMirroring !== undefined ? options.enableMirroring : defaultOptions.enableMirroring;
  239. this.canvas = this; ///point back to this instance!
  240. this.__canvas = document.createElement("canvas");
  241. this.__ctx = this.__canvas.getContext("2d");
  242. this.__setDefaultStyles();
  243. this.__stack = [this.__getStyleState()];
  244. this.__groupStack = [];
  245. //the root svg element
  246. this.__root = document.createElementNS("http://www.w3.org/2000/svg", "svg");
  247. this.__root.setAttribute("version", 1.1);
  248. this.__root.setAttribute("xmlns", "http://www.w3.org/2000/svg");
  249. this.__root.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink");
  250. this.__root.setAttribute("width", this.width);
  251. this.__root.setAttribute("height", this.height);
  252. //make sure we don't generate the same ids in defs
  253. this.__ids = {};
  254. //defs tag
  255. this.__defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
  256. this.__root.appendChild(this.__defs);
  257. //also add a group child. the svg element can't use the transform attribute
  258. this.__currentElement = document.createElementNS("http://www.w3.org/2000/svg", "g");
  259. this.__root.appendChild(this.__currentElement);
  260. };
  261. /**
  262. * Creates the specified svg element
  263. * @private
  264. */
  265. ctx.prototype.__createElement = function(elementName, properties, resetFill) {
  266. if (typeof properties === "undefined") {
  267. properties = {};
  268. }
  269. var element = document.createElementNS("http://www.w3.org/2000/svg", elementName),
  270. keys = Object.keys(properties), i, key;
  271. if(resetFill) {
  272. //if fill or stroke is not specified, the svg element should not display. By default SVG's fill is black.
  273. element.setAttribute("fill", "none");
  274. element.setAttribute("stroke", "none");
  275. }
  276. for(i=0; i<keys.length; i++) {
  277. key = keys[i];
  278. element.setAttribute(key, properties[key]);
  279. }
  280. return element;
  281. };
  282. /**
  283. * Applies default canvas styles to the context
  284. * @private
  285. */
  286. ctx.prototype.__setDefaultStyles = function() {
  287. //default 2d canvas context properties see:http://www.w3.org/TR/2dcontext/
  288. var keys = Object.keys(STYLES), i, key;
  289. for(i=0; i<keys.length; i++) {
  290. key = keys[i];
  291. this[key] = STYLES[key].canvas;
  292. }
  293. };
  294. /**
  295. * Applies styles on restore
  296. * @param styleState
  297. * @private
  298. */
  299. ctx.prototype.__applyStyleState = function(styleState) {
  300. var keys = Object.keys(styleState), i, key;
  301. for(i=0; i<keys.length; i++) {
  302. key = keys[i];
  303. this[key] = styleState[key];
  304. }
  305. };
  306. /**
  307. * Gets the current style state
  308. * @return {Object}
  309. * @private
  310. */
  311. ctx.prototype.__getStyleState = function() {
  312. var i, styleState = {}, keys = Object.keys(STYLES), key;
  313. for(i=0; i<keys.length; i++) {
  314. key = keys[i];
  315. styleState[key] = this[key];
  316. }
  317. return styleState;
  318. };
  319. /**
  320. * Apples the current styles to the current SVG element. On "ctx.fill" or "ctx.stroke"
  321. * @param type
  322. * @private
  323. */
  324. ctx.prototype.__applyStyleToCurrentElement = function(type) {
  325. var keys = Object.keys(STYLES), i, style, value, id, regex, matches;
  326. for(i=0; i<keys.length; i++) {
  327. style = STYLES[keys[i]];
  328. value = this[keys[i]];
  329. if(style.apply) {
  330. //is this a gradient or pattern?
  331. if(style.apply.indexOf("fill")!==-1 && value instanceof CanvasPattern) {
  332. //pattern
  333. if(value.__ctx) {
  334. //copy over defs
  335. while(value.__ctx.__defs.childNodes.length) {
  336. id = value.__ctx.__defs.childNodes[0].getAttribute("id");
  337. this.__ids[id] = id;
  338. this.__defs.appendChild(value.__ctx.__defs.childNodes[0]);
  339. }
  340. }
  341. this.__currentElement.setAttribute("fill", format("url(#{id})", {id:value.__root.getAttribute("id")}));
  342. }
  343. else if(style.apply.indexOf("fill")!==-1 && value instanceof CanvasGradient) {
  344. //gradient
  345. this.__currentElement.setAttribute("fill", format("url(#{id})", {id:value.__root.getAttribute("id")}));
  346. } else if(style.apply.indexOf(type)!==-1 && style.svg !== value) {
  347. if((style.svgAttr === "stroke" || style.svgAttr === "fill") && value.indexOf("rgba") !== -1) {
  348. //separate alpha value, since illustrator can't handle it
  349. regex = /rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d?\.?\d*)\s*\)/gi;
  350. matches = regex.exec(value);
  351. this.__currentElement.setAttribute(style.svgAttr, format("rgb({r},{g},{b})", {r:matches[1], g:matches[2], b:matches[3]}));
  352. this.__currentElement.setAttribute(style.svgAttr+"-opacity", matches[4]);
  353. } else {
  354. //otherwise only update attribute if right type, and not svg default
  355. this.__currentElement.setAttribute(style.svgAttr, value);
  356. }
  357. }
  358. }
  359. }
  360. };
  361. /**
  362. * Will return the closest group or svg node. May return the current element.
  363. * @private
  364. */
  365. ctx.prototype.__closestGroupOrSvg = function(node) {
  366. node = node || this.__currentElement;
  367. if(node.nodeName === "g" || node.nodeName === "svg") {
  368. return node;
  369. } else {
  370. return this.__closestGroupOrSvg(node.parentNode);
  371. }
  372. };
  373. /**
  374. * Returns the serialized value of the svg so far
  375. * @param fixNamedEntities - Standalone SVG doesn't support named entities, which document.createTextNode encodes.
  376. * If true, we attempt to find all named entities and encode it as a numeric entity.
  377. * @return serialized svg
  378. */
  379. ctx.prototype.getSerializedSvg = function(fixNamedEntities) {
  380. var serialized = new XMLSerializer().serializeToString(this.__root),
  381. keys, i, key, value, regexp, xmlns;
  382. //IE search for a duplicate xmnls because they didn't implement setAttributeNS correctly
  383. xmlns = /xmlns="http:\/\/www\.w3\.org\/2000\/svg".+xmlns="http:\/\/www\.w3\.org\/2000\/svg/gi;
  384. if(xmlns.test(serialized)) {
  385. serialized = serialized.replace('xmlns="http://www.w3.org/2000/svg','xmlns:xlink="http://www.w3.org/1999/xlink');
  386. }
  387. if(fixNamedEntities) {
  388. keys = Object.keys(namedEntities);
  389. //loop over each named entity and replace with the proper equivalent.
  390. for(i=0; i<keys.length; i++) {
  391. key = keys[i];
  392. value = namedEntities[key];
  393. regexp = new RegExp(key, "gi");
  394. if(regexp.test(serialized)) {
  395. serialized = serialized.replace(regexp, value);
  396. }
  397. }
  398. }
  399. return serialized;
  400. };
  401. /**
  402. * Returns the root svg
  403. * @return
  404. */
  405. ctx.prototype.getSvg = function() {
  406. return this.__root;
  407. };
  408. /**
  409. * Will generate a group tag.
  410. */
  411. ctx.prototype.save = function() {
  412. var group = this.__createElement("g"), parent = this.__closestGroupOrSvg();
  413. this.__groupStack.push(parent);
  414. parent.appendChild(group);
  415. this.__currentElement = group;
  416. this.__stack.push(this.__getStyleState());
  417. };
  418. /**
  419. * Sets current element to parent, or just root if already root
  420. */
  421. ctx.prototype.restore = function(){
  422. this.__currentElement = this.__groupStack.pop();
  423. var state = this.__stack.pop();
  424. this.__applyStyleState(state);
  425. };
  426. /**
  427. * Helper method to add transform
  428. * @private
  429. */
  430. ctx.prototype.__addTransform = function(t) {
  431. //if the current element has siblings, add another group
  432. var parent = this.__closestGroupOrSvg();
  433. if(parent.childNodes.length > 0) {
  434. var group = this.__createElement("g");
  435. parent.appendChild(group);
  436. this.__currentElement = group;
  437. }
  438. var transform = this.__currentElement.getAttribute("transform");
  439. if(transform) {
  440. transform += " ";
  441. } else {
  442. transform = "";
  443. }
  444. transform += t;
  445. this.__currentElement.setAttribute("transform", transform);
  446. };
  447. /**
  448. * scales the current element
  449. */
  450. ctx.prototype.scale = function(x, y) {
  451. if(y === undefined) {
  452. y = x;
  453. }
  454. this.__addTransform(format("scale({x},{y})", {x:x, y:y}));
  455. };
  456. /**
  457. * rotates the current element
  458. */
  459. ctx.prototype.rotate = function(angle){
  460. var degrees = (angle * 180 / Math.PI);
  461. this.__addTransform(format("rotate({angle},{cx},{cy})", {angle:degrees, cx:0, cy:0}));
  462. };
  463. /**
  464. * translates the current element
  465. */
  466. ctx.prototype.translate = function(x, y){
  467. this.__addTransform(format("translate({x},{y})", {x:x,y:y}));
  468. };
  469. /**
  470. * applies a transform to the current element
  471. */
  472. ctx.prototype.transform = function(a, b, c, d, e, f){
  473. this.__addTransform(format("matrix({a},{b},{c},{d},{e},{f})", {a:a, b:b, c:c, d:d, e:e, f:f}));
  474. };
  475. /**
  476. * Create a new Path Element
  477. */
  478. ctx.prototype.beginPath = function(){
  479. var path, parent;
  480. // Note that there is only one current default path, it is not part of the drawing state.
  481. // See also: https://html.spec.whatwg.org/multipage/scripting.html#current-default-path
  482. this.__currentDefaultPath = "";
  483. this.__currentPosition = {};
  484. path = this.__createElement("path", {}, true);
  485. parent = this.__closestGroupOrSvg();
  486. parent.appendChild(path);
  487. this.__currentElement = path;
  488. };
  489. /**
  490. * Helper function to apply currentDefaultPath to current path element
  491. * @private
  492. */
  493. ctx.prototype.__applyCurrentDefaultPath = function() {
  494. if(this.__currentElement.nodeName === "path") {
  495. var d = this.__currentDefaultPath;
  496. this.__currentElement.setAttribute("d", d);
  497. } else {
  498. throw new Error("Attempted to apply path command to node " + this.__currentElement.nodeName);
  499. }
  500. };
  501. /**
  502. * Helper function to add path command
  503. * @private
  504. */
  505. ctx.prototype.__addPathCommand = function(command){
  506. this.__currentDefaultPath += " ";
  507. this.__currentDefaultPath += command;
  508. };
  509. /**
  510. * Adds the move command to the current path element,
  511. * if the currentPathElement is not empty create a new path element
  512. */
  513. ctx.prototype.moveTo = function(x,y){
  514. if(this.__currentElement.nodeName !== "path") {
  515. this.beginPath();
  516. }
  517. // creates a new subpath with the given point
  518. this.__currentPosition = {x: x, y: y};
  519. this.__addPathCommand(format("M {x} {y}", {x:x, y:y}));
  520. };
  521. /**
  522. * Closes the current path
  523. */
  524. ctx.prototype.closePath = function(){
  525. this.__addPathCommand("Z");
  526. };
  527. /**
  528. * Adds a line to command
  529. */
  530. ctx.prototype.lineTo = function(x, y){
  531. this.__currentPosition = {x: x, y: y};
  532. if (this.__currentDefaultPath.indexOf('M') > -1) {
  533. this.__addPathCommand(format("L {x} {y}", {x:x, y:y}));
  534. } else {
  535. this.__addPathCommand(format("M {x} {y}", {x:x, y:y}));
  536. }
  537. };
  538. /**
  539. * Add a bezier command
  540. */
  541. ctx.prototype.bezierCurveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {
  542. this.__currentPosition = {x: x, y: y};
  543. this.__addPathCommand(format("C {cp1x} {cp1y} {cp2x} {cp2y} {x} {y}",
  544. {cp1x:cp1x, cp1y:cp1y, cp2x:cp2x, cp2y:cp2y, x:x, y:y}));
  545. };
  546. /**
  547. * Adds a quadratic curve to command
  548. */
  549. ctx.prototype.quadraticCurveTo = function(cpx, cpy, x, y){
  550. this.__currentPosition = {x: x, y: y};
  551. this.__addPathCommand(format("Q {cpx} {cpy} {x} {y}", {cpx:cpx, cpy:cpy, x:x, y:y}));
  552. };
  553. /**
  554. * Return a new normalized vector of given vector
  555. */
  556. var normalize = function(vector) {
  557. var len = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1]);
  558. return [vector[0] / len, vector[1] / len];
  559. };
  560. /**
  561. * Adds the arcTo to the current path
  562. *
  563. * @see http://www.w3.org/TR/2015/WD-2dcontext-20150514/#dom-context-2d-arcto
  564. */
  565. ctx.prototype.arcTo = function(x1, y1, x2, y2, radius) {
  566. // Let the point (x0, y0) be the last point in the subpath.
  567. var x0 = this.__currentPosition && this.__currentPosition.x;
  568. var y0 = this.__currentPosition && this.__currentPosition.y;
  569. // First ensure there is a subpath for (x1, y1).
  570. if (typeof x0 == "undefined" || typeof y0 == "undefined") {
  571. return;
  572. }
  573. // Negative values for radius must cause the implementation to throw an IndexSizeError exception.
  574. if (radius < 0) {
  575. throw new Error("IndexSizeError: The radius provided (" + radius + ") is negative.");
  576. }
  577. // If the point (x0, y0) is equal to the point (x1, y1),
  578. // or if the point (x1, y1) is equal to the point (x2, y2),
  579. // or if the radius radius is zero,
  580. // then the method must add the point (x1, y1) to the subpath,
  581. // and connect that point to the previous point (x0, y0) by a straight line.
  582. if (((x0 === x1) && (y0 === y1))
  583. || ((x1 === x2) && (y1 === y2))
  584. || (radius === 0)) {
  585. this.lineTo(x1, y1);
  586. return;
  587. }
  588. // Otherwise, if the points (x0, y0), (x1, y1), and (x2, y2) all lie on a single straight line,
  589. // then the method must add the point (x1, y1) to the subpath,
  590. // and connect that point to the previous point (x0, y0) by a straight line.
  591. var unit_vec_p1_p0 = normalize([x0 - x1, y0 - y1]);
  592. var unit_vec_p1_p2 = normalize([x2 - x1, y2 - y1]);
  593. if (unit_vec_p1_p0[0] * unit_vec_p1_p2[1] === unit_vec_p1_p0[1] * unit_vec_p1_p2[0]) {
  594. this.lineTo(x1, y1);
  595. return;
  596. }
  597. // Otherwise, let The Arc be the shortest arc given by circumference of the circle that has radius radius,
  598. // and that has one point tangent to the half-infinite line that crosses the point (x0, y0) and ends at the point (x1, y1),
  599. // and that has a different point tangent to the half-infinite line that ends at the point (x1, y1), and crosses the point (x2, y2).
  600. // The points at which this circle touches these two lines are called the start and end tangent points respectively.
  601. // note that both vectors are unit vectors, so the length is 1
  602. var cos = (unit_vec_p1_p0[0] * unit_vec_p1_p2[0] + unit_vec_p1_p0[1] * unit_vec_p1_p2[1]);
  603. var theta = Math.acos(Math.abs(cos));
  604. // Calculate origin
  605. var unit_vec_p1_origin = normalize([
  606. unit_vec_p1_p0[0] + unit_vec_p1_p2[0],
  607. unit_vec_p1_p0[1] + unit_vec_p1_p2[1]
  608. ]);
  609. var len_p1_origin = radius / Math.sin(theta / 2);
  610. var x = x1 + len_p1_origin * unit_vec_p1_origin[0];
  611. var y = y1 + len_p1_origin * unit_vec_p1_origin[1];
  612. // Calculate start angle and end angle
  613. // rotate 90deg clockwise (note that y axis points to its down)
  614. var unit_vec_origin_start_tangent = [
  615. -unit_vec_p1_p0[1],
  616. unit_vec_p1_p0[0]
  617. ];
  618. // rotate 90deg counter clockwise (note that y axis points to its down)
  619. var unit_vec_origin_end_tangent = [
  620. unit_vec_p1_p2[1],
  621. -unit_vec_p1_p2[0]
  622. ];
  623. var getAngle = function(vector) {
  624. // get angle (clockwise) between vector and (1, 0)
  625. var x = vector[0];
  626. var y = vector[1];
  627. if (y >= 0) { // note that y axis points to its down
  628. return Math.acos(x);
  629. } else {
  630. return -Math.acos(x);
  631. }
  632. };
  633. var startAngle = getAngle(unit_vec_origin_start_tangent);
  634. var endAngle = getAngle(unit_vec_origin_end_tangent);
  635. // Connect the point (x0, y0) to the start tangent point by a straight line
  636. this.lineTo(x + unit_vec_origin_start_tangent[0] * radius,
  637. y + unit_vec_origin_start_tangent[1] * radius);
  638. // Connect the start tangent point to the end tangent point by arc
  639. // and adding the end tangent point to the subpath.
  640. this.arc(x, y, radius, startAngle, endAngle);
  641. };
  642. /**
  643. * Sets the stroke property on the current element
  644. */
  645. ctx.prototype.stroke = function(){
  646. if(this.__currentElement.nodeName === "path") {
  647. this.__currentElement.setAttribute("paint-order", "fill stroke markers");
  648. }
  649. this.__applyCurrentDefaultPath();
  650. this.__applyStyleToCurrentElement("stroke");
  651. };
  652. /**
  653. * Sets fill properties on the current element
  654. */
  655. ctx.prototype.fill = function(){
  656. if(this.__currentElement.nodeName === "path") {
  657. this.__currentElement.setAttribute("paint-order", "stroke fill markers");
  658. }
  659. this.__applyCurrentDefaultPath();
  660. this.__applyStyleToCurrentElement("fill");
  661. };
  662. /**
  663. * Adds a rectangle to the path.
  664. */
  665. ctx.prototype.rect = function(x, y, width, height){
  666. if(this.__currentElement.nodeName !== "path") {
  667. this.beginPath();
  668. }
  669. this.moveTo(x, y);
  670. this.lineTo(x+width, y);
  671. this.lineTo(x+width, y+height);
  672. this.lineTo(x, y+height);
  673. this.lineTo(x, y);
  674. this.closePath();
  675. };
  676. /**
  677. * adds a rectangle element
  678. */
  679. ctx.prototype.fillRect = function(x, y, width, height){
  680. var rect, parent;
  681. rect = this.__createElement("rect", {
  682. x : x,
  683. y : y,
  684. width : width,
  685. height : height
  686. }, true);
  687. parent = this.__closestGroupOrSvg();
  688. parent.appendChild(rect);
  689. this.__currentElement = rect;
  690. this.__applyStyleToCurrentElement("fill");
  691. };
  692. /**
  693. * Draws a rectangle with no fill
  694. * @param x
  695. * @param y
  696. * @param width
  697. * @param height
  698. */
  699. ctx.prototype.strokeRect = function(x, y, width, height){
  700. var rect, parent;
  701. rect = this.__createElement("rect", {
  702. x : x,
  703. y : y,
  704. width : width,
  705. height : height
  706. }, true);
  707. parent = this.__closestGroupOrSvg();
  708. parent.appendChild(rect);
  709. this.__currentElement = rect;
  710. this.__applyStyleToCurrentElement("stroke");
  711. };
  712. /**
  713. * "Clears" a canvas by just drawing a white rectangle in the current group.
  714. */
  715. ctx.prototype.clearRect = function(x, y, width, height) {
  716. var rect, parent = this.__closestGroupOrSvg();
  717. rect = this.__createElement("rect", {
  718. x : x,
  719. y : y,
  720. width : width,
  721. height : height,
  722. fill : "#FFFFFF"
  723. }, true);
  724. parent.appendChild(rect);
  725. };
  726. /**
  727. * Adds a linear gradient to a defs tag.
  728. * Returns a canvas gradient object that has a reference to it's parent def
  729. */
  730. ctx.prototype.createLinearGradient = function(x1, y1, x2, y2){
  731. var grad = this.__createElement("linearGradient", {
  732. id : randomString(this.__ids),
  733. x1 : x1+"px",
  734. x2 : x2+"px",
  735. y1 : y1+"px",
  736. y2 : y2+"px",
  737. "gradientUnits" : "userSpaceOnUse"
  738. }, false);
  739. this.__defs.appendChild(grad);
  740. return new CanvasGradient(grad);
  741. };
  742. /**
  743. * Adds a radial gradient to a defs tag.
  744. * Returns a canvas gradient object that has a reference to it's parent def
  745. */
  746. ctx.prototype.createRadialGradient = function(x0, y0, r0, x1, y1, r1){
  747. var grad = this.__createElement("radialGradient", {
  748. id : randomString(this.__ids),
  749. cx : x1+"px",
  750. cy : y1+"px",
  751. r : r1+"px",
  752. fx : x0+"px",
  753. fy : y0+"px",
  754. "gradientUnits" : "userSpaceOnUse"
  755. }, false);
  756. this.__defs.appendChild(grad);
  757. return new CanvasGradient(grad);
  758. };
  759. /**
  760. * Parses the font string and returns svg mapping
  761. * @private
  762. */
  763. ctx.prototype.__parseFont = function() {
  764. var regex = /^\s*(?=(?:(?:[-a-z]+\s*){0,2}(italic|oblique))?)(?=(?:(?:[-a-z]+\s*){0,2}(small-caps))?)(?=(?:(?:[-a-z]+\s*){0,2}(bold(?:er)?|lighter|[1-9]00))?)(?:(?:normal|\1|\2|\3)\s*){0,3}((?:xx?-)?(?:small|large)|medium|smaller|larger|[.\d]+(?:\%|in|[cem]m|ex|p[ctx]))(?:\s*\/\s*(normal|[.\d]+(?:\%|in|[cem]m|ex|p[ctx])))?\s*([-,\"\sa-z]+?)\s*$/i;
  765. var fontPart = regex.exec( this.font );
  766. var data = {
  767. style : fontPart[1] || 'normal',
  768. size : fontPart[4] || '10px',
  769. family : fontPart[6] || 'sans-serif',
  770. weight: fontPart[3] || 'normal',
  771. decoration : fontPart[2] || 'normal',
  772. href : null
  773. };
  774. //canvas doesn't support underline natively, but we can pass this attribute
  775. if(this.__fontUnderline === "underline") {
  776. data.decoration = "underline";
  777. }
  778. //canvas also doesn't support linking, but we can pass this as well
  779. if(this.__fontHref) {
  780. data.href = this.__fontHref;
  781. }
  782. return data;
  783. };
  784. /**
  785. * Helper to link text fragments
  786. * @param font
  787. * @param element
  788. * @return {*}
  789. * @private
  790. */
  791. ctx.prototype.__wrapTextLink = function(font, element) {
  792. if(font.href) {
  793. var a = this.__createElement("a");
  794. a.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", font.href);
  795. a.appendChild(element);
  796. return a;
  797. }
  798. return element;
  799. };
  800. /**
  801. * Fills or strokes text
  802. * @param text
  803. * @param x
  804. * @param y
  805. * @param action - stroke or fill
  806. * @private
  807. */
  808. ctx.prototype.__applyText = function(text, x, y, action) {
  809. var font = this.__parseFont(),
  810. parent = this.__closestGroupOrSvg(),
  811. textElement = this.__createElement("text", {
  812. "font-family" : font.family,
  813. "font-size" : font.size,
  814. "font-style" : font.style,
  815. "font-weight" : font.weight,
  816. "text-decoration" : font.decoration,
  817. "x" : x,
  818. "y" : y,
  819. "text-anchor": getTextAnchor(this.textAlign),
  820. "dominant-baseline": getDominantBaseline(this.textBaseline)
  821. }, true);
  822. textElement.appendChild(document.createTextNode(text));
  823. this.__currentElement = textElement;
  824. this.__applyStyleToCurrentElement(action);
  825. parent.appendChild(this.__wrapTextLink(font,textElement));
  826. };
  827. /**
  828. * Creates a text element
  829. * @param text
  830. * @param x
  831. * @param y
  832. */
  833. ctx.prototype.fillText = function(text, x, y){
  834. this.__applyText(text, x, y, "fill");
  835. };
  836. /**
  837. * Strokes text
  838. * @param text
  839. * @param x
  840. * @param y
  841. */
  842. ctx.prototype.strokeText = function(text, x, y){
  843. this.__applyText(text, x, y, "stroke");
  844. };
  845. /**
  846. * No need to implement this for svg.
  847. * @param text
  848. * @return {TextMetrics}
  849. */
  850. ctx.prototype.measureText = function(text){
  851. this.__ctx.font = this.font;
  852. return this.__ctx.measureText(text);
  853. };
  854. /**
  855. * Arc command!
  856. */
  857. ctx.prototype.arc = function(x, y, radius, startAngle, endAngle, counterClockwise) {
  858. startAngle = startAngle % (2*Math.PI);
  859. endAngle = endAngle % (2*Math.PI);
  860. if(startAngle === endAngle) {
  861. //circle time! subtract some of the angle so svg is happy (svg elliptical arc can't draw a full circle)
  862. endAngle = ((endAngle + (2*Math.PI)) - 0.001 * (counterClockwise ? -1 : 1)) % (2*Math.PI);
  863. }
  864. var endX = x+radius*Math.cos(endAngle),
  865. endY = y+radius*Math.sin(endAngle),
  866. startX = x+radius*Math.cos(startAngle),
  867. startY = y+radius*Math.sin(startAngle),
  868. sweepFlag = counterClockwise ? 0 : 1,
  869. largeArcFlag = 0,
  870. diff = endAngle - startAngle;
  871. // https://github.com/gliffy/canvas2svg/issues/4
  872. if(diff < 0) {
  873. diff += 2*Math.PI;
  874. }
  875. if(counterClockwise) {
  876. largeArcFlag = diff > Math.PI ? 0 : 1;
  877. } else {
  878. largeArcFlag = diff > Math.PI ? 1 : 0;
  879. }
  880. this.lineTo(startX, startY);
  881. this.__addPathCommand(format("A {rx} {ry} {xAxisRotation} {largeArcFlag} {sweepFlag} {endX} {endY}",
  882. {rx:radius, ry:radius, xAxisRotation:0, largeArcFlag:largeArcFlag, sweepFlag:sweepFlag, endX:endX, endY:endY}));
  883. this.__currentPosition = {x: endX, y: endY};
  884. };
  885. /**
  886. * Generates a ClipPath from the clip command.
  887. */
  888. ctx.prototype.clip = function(){
  889. var group = this.__closestGroupOrSvg(),
  890. clipPath = this.__createElement("clipPath"),
  891. id = randomString(this.__ids),
  892. newGroup = this.__createElement("g");
  893. group.removeChild(this.__currentElement);
  894. clipPath.setAttribute("id", id);
  895. clipPath.appendChild(this.__currentElement);
  896. this.__defs.appendChild(clipPath);
  897. //set the clip path to this group
  898. group.setAttribute("clip-path", format("url(#{id})", {id:id}));
  899. //clip paths can be scaled and transformed, we need to add another wrapper group to avoid later transformations
  900. // to this path
  901. group.appendChild(newGroup);
  902. this.__currentElement = newGroup;
  903. };
  904. /**
  905. * Draws a canvas, image or mock context to this canvas.
  906. * Note that all svg dom manipulation uses node.childNodes rather than node.children for IE support.
  907. * http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-drawimage
  908. */
  909. ctx.prototype.drawImage = function(){
  910. //convert arguments to a real array
  911. var args = Array.prototype.slice.call(arguments),
  912. image=args[0],
  913. dx, dy, dw, dh, sx=0, sy=0, sw, sh, parent, svg, defs, group,
  914. currentElement, svgImage, canvas, context, id;
  915. if(args.length === 3) {
  916. dx = args[1];
  917. dy = args[2];
  918. sw = image.width;
  919. sh = image.height;
  920. dw = sw;
  921. dh = sh;
  922. } else if(args.length === 5) {
  923. dx = args[1];
  924. dy = args[2];
  925. dw = args[3];
  926. dh = args[4];
  927. sw = image.width;
  928. sh = image.height;
  929. } else if(args.length === 9) {
  930. sx = args[1];
  931. sy = args[2];
  932. sw = args[3];
  933. sh = args[4];
  934. dx = args[5];
  935. dy = args[6];
  936. dw = args[7];
  937. dh = args[8];
  938. } else {
  939. throw new Error("Inavlid number of arguments passed to drawImage: " + arguments.length);
  940. }
  941. parent = this.__closestGroupOrSvg();
  942. currentElement = this.__currentElement;
  943. if(image instanceof ctx) {
  944. //canvas2svg mock canvas context. In the future we may want to clone nodes instead.
  945. //also I'm currently ignoring dw, dh, sw, sh, sx, sy for a mock context.
  946. svg = image.getSvg();
  947. defs = svg.childNodes[0];
  948. while(defs.childNodes.length) {
  949. id = defs.childNodes[0].getAttribute("id");
  950. this.__ids[id] = id;
  951. this.__defs.appendChild(defs.childNodes[0]);
  952. }
  953. group = svg.childNodes[1];
  954. parent.appendChild(group);
  955. this.__currentElement = group;
  956. this.translate(dx, dy);
  957. this.__currentElement = currentElement;
  958. } else if(image.nodeName === "CANVAS" || image.nodeName === "IMG") {
  959. //canvas or image
  960. svgImage = this.__createElement("image");
  961. svgImage.setAttribute("width", dw);
  962. svgImage.setAttribute("height", dh);
  963. svgImage.setAttribute("preserveAspectRatio", "none");
  964. if(sx || sy || sw !== image.width || sh !== image.height) {
  965. //crop the image using a temporary canvas
  966. canvas = document.createElement("canvas");
  967. canvas.width = dw;
  968. canvas.height = dh;
  969. context = canvas.getContext("2d");
  970. context.drawImage(image, sx, sy, sw, sh, 0, 0, dw, dh);
  971. image = canvas;
  972. }
  973. svgImage.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href",
  974. image.nodeName === "CANVAS" ? image.toDataURL() : image.getAttribute("src"));
  975. parent.appendChild(svgImage);
  976. this.__currentElement = svgImage;
  977. this.translate(dx, dy);
  978. this.__currentElement = currentElement;
  979. }
  980. };
  981. /**
  982. * Generates a pattern tag
  983. */
  984. ctx.prototype.createPattern = function(image, repetition){
  985. var pattern = document.createElementNS("http://www.w3.org/2000/svg", "pattern"), id = randomString(this.__ids),
  986. img;
  987. pattern.setAttribute("id", id);
  988. pattern.setAttribute("width", image.width);
  989. pattern.setAttribute("height", image.height);
  990. if(image.nodeName === "CANVAS" || image.nodeName === "IMG") {
  991. img = document.createElementNS("http://www.w3.org/2000/svg", "image");
  992. img.setAttribute("width", image.width);
  993. img.setAttribute("height", image.height);
  994. img.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href",
  995. image.nodeName === "CANVAS" ? image.toDataURL() : image.getAttribute("src"));
  996. pattern.appendChild(img);
  997. this.__defs.appendChild(pattern);
  998. } else if(image instanceof ctx) {
  999. pattern.appendChild(image.__root.childNodes[1]);
  1000. this.__defs.appendChild(pattern);
  1001. }
  1002. return new CanvasPattern(pattern, this);
  1003. };
  1004. /**
  1005. * Not yet implemented
  1006. */
  1007. ctx.prototype.drawFocusRing = function(){};
  1008. ctx.prototype.createImageData = function(){};
  1009. ctx.prototype.getImageData = function(){};
  1010. ctx.prototype.putImageData = function(){};
  1011. ctx.prototype.globalCompositeOperation = function(){};
  1012. ctx.prototype.setTransform = function(){};
  1013. //add options for alternative namespace
  1014. module.exports = ctx;
  1015. }());
  1016. },{}],2:[function(require,module,exports){
  1017. var C2S = require('./canvas2svg');
  1018. var Context = function(width, height, options) {
  1019. C2S.call(this);
  1020. this.__width = width;
  1021. this.__height = height;
  1022. this.generations = [[]]; // used to collect element references for different generations
  1023. var _this = this;
  1024. this.__imageSmoothingEnabled = true;
  1025. ["mozImageSmoothingEnabled",
  1026. "webkitImageSmoothingEnabled",
  1027. "msImageSmoothingEnabled",
  1028. "imageSmoothingEnabled"].forEach(function(k) {
  1029. Object.defineProperty(_this, k, {
  1030. get: function() {
  1031. return _this.__imageSmoothingEnabled;
  1032. },
  1033. set: function(val) {
  1034. _this.__imageSmoothingEnabled = val;
  1035. }
  1036. });
  1037. });
  1038. options = options || {};
  1039. ["fillStyle", "strokeStyle"].forEach(function(prop) {
  1040. var key = "__" + prop;
  1041. Object.defineProperty(_this, prop, {
  1042. get: function() {
  1043. return _this[key];
  1044. },
  1045. set: function(val) {
  1046. if (val.indexOf('NaN') > -1) {
  1047. console.warn("svgcanvas: invalid value for " + prop + ", fail to set it to " + val);
  1048. return;
  1049. }
  1050. _this[key] = val;
  1051. }
  1052. });
  1053. });
  1054. if (options.debug) {
  1055. this.__history = []; // method history
  1056. var methods = [];
  1057. for(var key in this) {
  1058. if (typeof this[key] === "function") {
  1059. if (key.indexOf('__') !== 0) {
  1060. if (key !== 'getSerializedSvg') {
  1061. methods.push(key);
  1062. }
  1063. }
  1064. }
  1065. }
  1066. ["__fillStyle", "__strokeStyle"].forEach(function(prop) {
  1067. var key = "__debug__" + prop;
  1068. Object.defineProperty(_this, prop, {
  1069. get: function() {
  1070. return _this[key];
  1071. },
  1072. set: function(val) {
  1073. var call = prop.replace(/__/g, '') + " = " + val;
  1074. _this.__history.push(call);
  1075. _this[key] = val;
  1076. }
  1077. });
  1078. });
  1079. methods.forEach(function(method) {
  1080. var fn = _this[method];
  1081. _this[method] = function() {
  1082. var call = method + '(' + Array.prototype.slice.call(arguments).join(', ') + ');';
  1083. // keep call history
  1084. _this.__history.push(call);
  1085. if (_this.__history.length > 100) {
  1086. _this.__history.shift();
  1087. }
  1088. return fn.apply(_this, arguments);
  1089. };
  1090. });
  1091. }
  1092. };
  1093. Context.prototype = Object.create(C2S.prototype);
  1094. Context.prototype.scale = function(x, y) {
  1095. if (x === undefined || y === undefined) {
  1096. return;
  1097. } else {
  1098. C2S.prototype.scale.apply(this, arguments);
  1099. }
  1100. };
  1101. Context.prototype.__createElement = function(elementName, properties, resetFill) {
  1102. if (!this.__imageSmoothingEnabled) {
  1103. // only shape elements can use the shape-rendering attribute
  1104. if (["circle", "ellipse", "line", "path", "polygon", "polyline", "rect"].indexOf(elementName) > -1) {
  1105. properties = properties || {};
  1106. properties["shape-rendering"] = "crispEdges"; // disable anti-aliasing
  1107. }
  1108. }
  1109. var element = C2S.prototype.__createElement.call(this, elementName, properties, resetFill);
  1110. var currentGeneration = this.generations[this.generations.length - 1];
  1111. currentGeneration.push(element);
  1112. return element;
  1113. };
  1114. Context.prototype.__gc = function() {
  1115. this.generations.push([]);
  1116. var ctx = this;
  1117. // make sure it happens after current job done
  1118. // for example: in p5.js's redraw use setTimeout will make gc called after both save() and restore() called
  1119. setTimeout(function() {
  1120. if (ctx.__groupStack.length > 0) {
  1121. // we are between ctx.save() and ctx.restore(), skip gc
  1122. return;
  1123. }
  1124. if (ctx.__currentElement.nodeName === 'path') {
  1125. // we are still in path, skip gc
  1126. return;
  1127. }
  1128. // keep only latest generation
  1129. while (ctx.generations.length > 1) {
  1130. var elements = ctx.generations.shift();
  1131. var lastCount = 0;
  1132. var count = elements.length;
  1133. while (count > 0) {
  1134. lastCount = count;
  1135. elements = elements.filter(function(elem) {
  1136. // in case children may from live generation, gc from bottom to top
  1137. var children = elem.children || elem.childNodes; // childNodes for IE
  1138. if (children.length === 0) {
  1139. elem.parentNode.removeChild(elem);
  1140. return false;
  1141. } else {
  1142. return true;
  1143. }
  1144. });
  1145. count = elements.length;
  1146. if (count === lastCount) {
  1147. // could not gc more, exit now
  1148. // save this elements to live generation
  1149. var liveGeneration = ctx.generations[ctx.generations.length - 1];
  1150. elements.forEach(function(elem) {
  1151. liveGeneration.push(elem);
  1152. });
  1153. // exit
  1154. break;
  1155. }
  1156. }
  1157. }
  1158. }, 0);
  1159. };
  1160. Context.prototype.clearRect = function(x, y, w, h) {
  1161. if (x === 0 && y === 0 && w === this.__width && h === this.__height) {
  1162. // remove all
  1163. this.generations.forEach(function(elems) {
  1164. elems.forEach(function(elem) {
  1165. if (elem) {
  1166. elem.parentNode.removeChild(elem);
  1167. }
  1168. });
  1169. });
  1170. this.generations = [[]];
  1171. var g = this.__createElement('g');
  1172. this.__root.appendChild(g);
  1173. this.__currentElement = g;
  1174. } else {
  1175. C2S.prototype.clearRect.call(this, x, y, w, h);
  1176. }
  1177. };
  1178. Context.prototype.fillRect = function(x, y, w, h) {
  1179. if (x === 0 && y === 0 && w === this.__width && h === this.__height) {
  1180. this.__gc();
  1181. }
  1182. C2S.prototype.fillRect.call(this, x, y, w, h);
  1183. };
  1184. // Simple version of drawImage
  1185. // Note that this version does not handle drawing mock context
  1186. Context.prototype.drawImage = function() {
  1187. var canvas = document.createElement('canvas');
  1188. canvas.width = this.__width;
  1189. canvas.height = this.__height;
  1190. var args = arguments;
  1191. var ctx = canvas.getContext('2d');
  1192. ctx.drawImage.apply(ctx, args);
  1193. // Note: don't use foreign object,
  1194. // otherwise the saved SVG may be unusable for other application
  1195. var url = canvas.toDataURL('image/png');
  1196. var image = this.__createElement('image', {
  1197. x: 0,
  1198. y: 0,
  1199. width: canvas.width,
  1200. height: canvas.height,
  1201. preserveAspectRatio: 'none'
  1202. });
  1203. var parent = this.__closestGroupOrSvg();
  1204. image.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", url);
  1205. parent.appendChild(image);
  1206. };
  1207. Context.prototype.getSerializedSvg = null;
  1208. module.exports = Context;
  1209. },{"./canvas2svg":1}],3:[function(require,module,exports){
  1210. var Context = require('./context');
  1211. function SVGCanvas(options) {
  1212. var debug = options && options.debug;
  1213. this.ctx = new Context(100, 100, {debug: debug});
  1214. this.svg = this.ctx.__root;
  1215. // sync attributes to svg
  1216. var svg = this.svg;
  1217. var _this = this;
  1218. var wrapper = document.createElement('div');
  1219. wrapper.style.display = 'inline-block';
  1220. wrapper.appendChild(svg);
  1221. this.wrapper = wrapper;
  1222. Object.defineProperty(this, 'className', {
  1223. get: function() {
  1224. return wrapper.getAttribute('class') || '';
  1225. },
  1226. set: function(val) {
  1227. return wrapper.setAttribute('class', val);
  1228. }
  1229. });
  1230. ["width", "height"].forEach(function(prop) {
  1231. Object.defineProperty(_this, prop, {
  1232. get: function() {
  1233. return svg.getAttribute(prop) | 0;
  1234. },
  1235. set: function(val) {
  1236. if (isNaN(val) || (typeof val === "undefined")) {
  1237. return;
  1238. }
  1239. _this.ctx['__'+prop] = val;
  1240. svg.setAttribute(prop, val);
  1241. return wrapper[prop] = val;
  1242. }
  1243. });
  1244. });
  1245. ["style", "id"].forEach(function(prop) {
  1246. Object.defineProperty(_this, prop, {
  1247. get: function() {
  1248. return wrapper[prop];
  1249. },
  1250. set: function(val) {
  1251. if (typeof val !== "undefined") {
  1252. return wrapper[prop] = val;
  1253. }
  1254. }
  1255. });
  1256. });
  1257. ["getBoundingClientRect"].forEach(function(fn) {
  1258. _this[fn] = function() {
  1259. return svg[fn]();
  1260. };
  1261. });
  1262. }
  1263. SVGCanvas.prototype.getContext = function(type) {
  1264. if (type !== '2d') {
  1265. throw new Error('Unsupported type of context for SVGCanvas');
  1266. }
  1267. return this.ctx;
  1268. };
  1269. // you should always use URL.revokeObjectURL after your work done
  1270. SVGCanvas.prototype.toObjectURL = function() {
  1271. var data = new XMLSerializer().serializeToString(this.svg);
  1272. var svg = new Blob([data], {type: 'image/svg+xml;charset=utf-8'});
  1273. return URL.createObjectURL(svg);
  1274. };
  1275. SVGCanvas.prototype.toDataURL = function(type, options) {
  1276. var xml = new XMLSerializer().serializeToString(this.svg);
  1277. // documentMode is an IE-only property
  1278. // http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
  1279. // http://stackoverflow.com/questions/10964966/detect-ie-version-prior-to-v9-in-javascript
  1280. var isIE = document.documentMode;
  1281. if (isIE) {
  1282. // This is patch from canvas2svg
  1283. // IE search for a duplicate xmnls because they didn't implement setAttributeNS correctly
  1284. var xmlns = /xmlns="http:\/\/www\.w3\.org\/2000\/svg".+xmlns="http:\/\/www\.w3\.org\/2000\/svg/gi;
  1285. if(xmlns.test(xml)) {
  1286. xml = xml.replace('xmlns="http://www.w3.org/2000/svg','xmlns:xlink="http://www.w3.org/1999/xlink');
  1287. }
  1288. }
  1289. var SVGDataURL = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(xml);
  1290. if (type === "image/svg+xml" || !type) {
  1291. return SVGDataURL;
  1292. }
  1293. if (type === "image/jpeg" || type === "image/png") {
  1294. var canvas = document.createElement('canvas');
  1295. canvas.width = this.width;
  1296. canvas.height = this.height;
  1297. var ctx = canvas.getContext('2d');
  1298. var img = new Image();
  1299. img.src = SVGDataURL;
  1300. if (img.complete && img.width > 0 && img.height > 0) {
  1301. // for chrome, it's ready immediately
  1302. ctx.drawImage(img, 0, 0);
  1303. return canvas.toDataURL(type, options);
  1304. } else {
  1305. // for firefox, it's not possible to provide sync api in current thread
  1306. // and web worker doesn't provide canvas API, so
  1307. throw new Error('svgcanvas.toDataURL() for jpeg/png is only available in Chrome.');
  1308. }
  1309. }
  1310. throw new Error('Unknown type for SVGCanvas.prototype.toDataURL, please use image/jpeg | image/png | image/svg+xml.');
  1311. };
  1312. // will return wrapper element: <div><svg></svg></div>
  1313. SVGCanvas.prototype.getElement = function() {
  1314. return this.wrapper;
  1315. };
  1316. module.exports = SVGCanvas;
  1317. },{"./context":2}],4:[function(require,module,exports){
  1318. var constants = {
  1319. SVG: 'svg'
  1320. };
  1321. module.exports = constants;
  1322. },{}],5:[function(require,module,exports){
  1323. module.exports = function(p5) {
  1324. /**
  1325. * Returns an Array of SVGElements of current SVG Graphics matching given selector
  1326. *
  1327. * @function querySVG
  1328. * @memberof p5.prototype
  1329. * @param {String} selector CSS selector for query
  1330. * @returns {SVGElement[]}
  1331. */
  1332. p5.prototype.querySVG = function(selector) {
  1333. var svg = this._renderer && this._renderer.svg;
  1334. if (!svg) {
  1335. return null;
  1336. }
  1337. return p5.SVGElement.prototype.query.call({elt: svg}, selector);
  1338. };
  1339. /**
  1340. * @namespace SVGElement
  1341. * @constructor
  1342. * @param {Element} element
  1343. */
  1344. function SVGElement(element) {
  1345. if (!element) {
  1346. return null;
  1347. }
  1348. return p5.Element.apply(this, arguments);
  1349. }
  1350. SVGElement.prototype = Object.create(p5.Element.prototype);
  1351. /**
  1352. * Returns an Array of children of current SVG Element matching given selector
  1353. *
  1354. * @function query
  1355. * @memberof SVGElement.prototype
  1356. * @param {String} selector CSS selector for query
  1357. * @returns {SVGElement[]}
  1358. */
  1359. SVGElement.prototype.query = function(selector) {
  1360. var elements = this.elt.querySelectorAll(selector);
  1361. var objects = [];
  1362. for (var i = 0; i < elements.length; i++) {
  1363. objects[i] = new SVGElement(elements[i]);
  1364. }
  1365. return objects;
  1366. };
  1367. /**
  1368. * Append a new child to current element.
  1369. *
  1370. * @function append
  1371. * @memberof SVGElement.prototype
  1372. * @param {SVGElement|Element} element
  1373. */
  1374. SVGElement.prototype.append = function(element) {
  1375. var elt = element.elt || element;
  1376. this.elt.appendChild(elt);
  1377. return this;
  1378. };
  1379. /**
  1380. * Apply different attribute operation based on arguments.length
  1381. * <ul>
  1382. * <li>setAttribute(name, value)</li>
  1383. * <li>setAttributeNS(namespace, name, value)</li>
  1384. * <li>getAttribute(name)</li>
  1385. * </ul>
  1386. *
  1387. * @function attribute
  1388. * @memberof SVGElement.prototype
  1389. */
  1390. SVGElement.prototype.attribute = function() {
  1391. var args = arguments;
  1392. if (args.length === 3) {
  1393. this.elt.setAttributeNS.apply(this.elt, args);
  1394. }
  1395. if (args.length === 2) {
  1396. this.elt.setAttribute.apply(this.elt, args);
  1397. }
  1398. if (args.length === 1) {
  1399. return this.elt.getAttribute.apply(this.elt, args);
  1400. }
  1401. return this;
  1402. };
  1403. /**
  1404. * Apply filter on current element.
  1405. * If called multiple times,
  1406. * these filters will be chained together and combine to a bigger SVG filter.
  1407. *
  1408. * @function filter
  1409. * @memberof SVGElement.prototype
  1410. * @param {String} filter BLUR, GRAY, INVERT, THRESHOLD, OPAQUE, ERODE, DILATE (defined in p5's constants)
  1411. * @param {Any} argument Argument for that filter
  1412. */
  1413. SVGElement.prototype.filter = function(filter, arg) {
  1414. p5.SVGFilters.apply(this, filter, arg);
  1415. return this;
  1416. };
  1417. /**
  1418. * Remove applied filter on current element
  1419. * After called, rest filters will be chained together
  1420. * and combine to a new SVG filter.
  1421. *
  1422. * @function unfilter
  1423. * @memberof SVGElement.prototype
  1424. * @param {String} filter BLUR, GRAY, INVERT, THRESHOLD, OPAQUE, ERODE, DILATE (defined in p5's constants)
  1425. * @param {Any} argument Argument for that filter
  1426. */
  1427. SVGElement.prototype.unfilter = function(filterName, arg) {
  1428. var filters = this.attribute('data-p5-svg-filters') || '[]';
  1429. filters = JSON.parse(filters);
  1430. if (arg === undefined) {
  1431. arg = null;
  1432. }
  1433. var found = false;
  1434. filters = filters.reverse().filter(function(filter) {
  1435. if ((filter[0] === filterName) && (filter[1] === arg) && !found) {
  1436. found = true;
  1437. return false;
  1438. }
  1439. return true;
  1440. }).reverse();
  1441. this.attribute('data-p5-svg-filters', JSON.stringify(filters));
  1442. p5.SVGFilters.apply(this, null);
  1443. return this;
  1444. };
  1445. /**
  1446. * Create SVGElement
  1447. *
  1448. * @function create
  1449. * @memberof SVGElement
  1450. * @param {String} nodeName
  1451. * @param {Object} [attributes] Attributes for the element
  1452. * @return {SVGElement}
  1453. */
  1454. SVGElement.create = function(nodeName, attributes) {
  1455. attributes = attributes || {};
  1456. var elt = document.createElementNS('http://www.w3.org/2000/svg', nodeName);
  1457. Object.keys(attributes).forEach(function(k) {
  1458. elt.setAttribute(k, attributes[k]);
  1459. });
  1460. return new SVGElement(elt);
  1461. };
  1462. /**
  1463. * Tell if current element matching given selector.
  1464. * This is polyfill from MDN.
  1465. *
  1466. * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/matches
  1467. *
  1468. * @function matches
  1469. * @memberof SVGElement.prototype
  1470. * @param {String} selector CSS Selector
  1471. * @return {Bool}
  1472. */
  1473. SVGElement.prototype.matches = function(selector) {
  1474. var element = this.elt;
  1475. var matches = (element.document || element.ownerDocument).querySelectorAll(selector);
  1476. var i = 0;
  1477. while (matches[i] && matches[i] !== element) {
  1478. i++;
  1479. }
  1480. return matches[i] ? true : false;
  1481. };
  1482. /**
  1483. * Get defs element, or create one if not exists
  1484. *
  1485. * @private
  1486. */
  1487. SVGElement.prototype._getDefs = function() {
  1488. var svg = this.parentNode('svg');
  1489. var defs = svg.query('defs');
  1490. if (defs[0]) {
  1491. defs = defs[0];
  1492. } else {
  1493. defs = SVGElement.create('defs');
  1494. svg.append(defs);
  1495. }
  1496. return defs;
  1497. };
  1498. /**
  1499. * Get parentNode.
  1500. * If selector not given, returns parentNode.
  1501. * Otherwise, will look up all ancestors,
  1502. * and return closest element matching given selector,
  1503. * or return null if not found.
  1504. *
  1505. * @function parentNode
  1506. * @memberof SVGElement.prototype
  1507. * @param {String} [selector] CSS Selector
  1508. * @return {SVGElement}
  1509. */
  1510. SVGElement.prototype.parentNode = function(selector) {
  1511. if (!selector) {
  1512. return new SVGElement(this.elt.parentNode);
  1513. }
  1514. var elt = this;
  1515. while (elt) {
  1516. elt = this.parentNode();
  1517. if (elt && elt.matches(selector)) {
  1518. return elt;
  1519. }
  1520. }
  1521. return null;
  1522. };
  1523. p5.SVGElement = SVGElement;
  1524. };
  1525. },{}],6:[function(require,module,exports){
  1526. // SVG Filter
  1527. module.exports = function(p5) {
  1528. var _filter = p5.prototype.filter;
  1529. var SVGFilters = require('./p5.SVGFilters')(p5);
  1530. /**
  1531. * Register a custom SVG Filter
  1532. *
  1533. * @function registerSVGFilter
  1534. * @memberof p5.prototype
  1535. * @param {String} name Name for Custom SVG filter
  1536. * @param {Function} filterFunction filterFunction(inGraphicsName, resultGraphicsName, value)
  1537. * should return SVGElement or Array of SVGElement.
  1538. * @example
  1539. * registerSVGFilter('myblur', function(inGraphicsName, resultGraphicsName, value) {
  1540. * return SVGElement.create('feGaussianBlur', {
  1541. * stdDeviation: val,
  1542. * in: inGraphics,
  1543. * result: resultGraphics,
  1544. * 'color-interpolation-filters': 'sRGB'
  1545. * });
  1546. * });
  1547. * filter('myblur', 5);
  1548. */
  1549. p5.prototype.registerSVGFilter = function(name, fn) {
  1550. SVGFilters[name] = fn;
  1551. };
  1552. p5.prototype.filter = function(operation, value) {
  1553. var svg = this._renderer.svg;
  1554. if (svg) {
  1555. // move nodes to a new <g>
  1556. var nodes = svg.children || svg.childNodes; // childNodes is for IE
  1557. var g = p5.SVGElement.create('g');
  1558. this._renderer._setGCFlag(g.elt);
  1559. svg.appendChild(g.elt);
  1560. // convert nodeList to array and use forEach
  1561. // instead of using for loop,
  1562. // which is buggy due to the length changed during append
  1563. nodes = Array.prototype.slice.call(nodes);
  1564. nodes.forEach(function(node) {
  1565. if (node !== g.elt && (node.nodeName.toLowerCase() !== 'defs')) {
  1566. g.elt.appendChild(node);
  1567. }
  1568. });
  1569. // apply filter
  1570. g.filter(operation, value);
  1571. // create new <g> so that new element won't be influenced by the filter
  1572. g = p5.SVGElement.create('g');
  1573. this._renderer._setGCFlag(g.elt);
  1574. this._renderer.svg.appendChild(g.elt);
  1575. this._renderer.drawingContext.__currentElement = g.elt;
  1576. } else {
  1577. _filter.apply(this, arguments);
  1578. }
  1579. };
  1580. };
  1581. },{"./p5.SVGFilters":10}],7:[function(require,module,exports){
  1582. module.exports = function(p5) {
  1583. /**
  1584. * @namespace p5
  1585. */
  1586. require('./p5.RendererSVG')(p5);
  1587. require('./rendering')(p5);
  1588. require('./io')(p5);
  1589. require('./element')(p5);
  1590. require('./filters')(p5);
  1591. // attach constants to p5 instance
  1592. var constants = require('./constants');
  1593. Object.keys(constants).forEach(function(k) {
  1594. p5.prototype[k] = constants[k];
  1595. });
  1596. };
  1597. },{"./constants":4,"./element":5,"./filters":6,"./io":8,"./p5.RendererSVG":9,"./rendering":11}],8:[function(require,module,exports){
  1598. module.exports = function(p5) {
  1599. /**
  1600. * Convert SVG Element to jpeg / png data url
  1601. *
  1602. * @private
  1603. * @param {SVGElement} svg SVG Element
  1604. * @param {String} mine Mine
  1605. * @param {Function} callback
  1606. */
  1607. var svg2img = function(svg, mine, callback) {
  1608. svg = (new XMLSerializer()).serializeToString(svg);
  1609. svg = 'data:image/svg+xml;charset=utf-8,' + encodeURI(svg);
  1610. if (mine == 'image/svg+xml') {
  1611. callback(null, svg);
  1612. return;
  1613. }
  1614. var img = new Image();
  1615. var canvas = document.createElement('canvas');
  1616. var ctx = canvas.getContext('2d');
  1617. img.onload = function() {
  1618. canvas.width = img.width;
  1619. canvas.height = img.height;
  1620. ctx.drawImage(img, 0, 0);
  1621. var dataURL = canvas.toDataURL(mine);
  1622. callback(null, dataURL);
  1623. };
  1624. img.src = svg;
  1625. };
  1626. /**
  1627. * Get SVG frame, and convert to target type
  1628. *
  1629. * @private
  1630. * @param {Object} options
  1631. * @param {SVGElement} options.svg SVG Element, defaults to current svg element
  1632. * @param {String} options.filename
  1633. * @param {String} options.ext Extension: 'svg' or 'jpg' or 'jpeg' or 'png'
  1634. * @param {Function} options.callback
  1635. */
  1636. p5.prototype._makeSVGFrame = function(options) {
  1637. var filename = options.filename || 'untitled';
  1638. var ext = options.extension;
  1639. ext = ext || this._checkFileExtension(filename, ext)[1];
  1640. var regexp = new RegExp('\\.' + ext + '$');
  1641. filename = filename.replace(regexp, '');
  1642. if (ext === '') {
  1643. ext = 'svg';
  1644. }
  1645. var mine = {
  1646. png: 'image/png',
  1647. jpeg: 'image/jpeg',
  1648. jpg: 'image/jpeg',
  1649. svg: 'image/svg+xml'
  1650. }[ext];
  1651. if (!mine) {
  1652. throw new Error('Fail to getFrame, invalid extension: ' + ext + ', please use png | jpeg | jpg | svg.');
  1653. }
  1654. var svg = options.svg || this._renderer.svg;
  1655. svg2img(svg, mine, function(err, dataURL) {
  1656. var downloadMime = 'image/octet-stream';
  1657. dataURL = dataURL.replace(mine, downloadMime);
  1658. options.callback(err, {
  1659. imageData: dataURL,
  1660. filename: filename,
  1661. ext: ext
  1662. });
  1663. });
  1664. };
  1665. /**
  1666. * Save the current SVG as an image. In Safari, will open the
  1667. * image in the window and the user must provide their own
  1668. * filename on save-as. Other browsers will either save the
  1669. * file immediately, or prompt the user with a dialogue window.
  1670. *
  1671. * @function saveSVG
  1672. * @memberof p5.prototype
  1673. * @param {Graphics|Element|SVGElement} [svg] Source to save
  1674. * @param {String} [filename]
  1675. * @param {String} [extension] Extension: 'svg' or 'jpg' or 'jpeg' or 'png'
  1676. */
  1677. p5.prototype.saveSVG = function() {
  1678. // don't use slice on arguments because it prevents optimizations
  1679. var args = arguments;
  1680. args = [args[0], args[1], args[2]];
  1681. var svg;
  1682. if (args[0] instanceof p5.Graphics) {
  1683. svg = args[0]._renderer.svg;
  1684. args.shift();
  1685. }
  1686. if (args[0] && args[0].elt) {
  1687. svg = args[0].elt;
  1688. args.shift();
  1689. }
  1690. if (typeof args[0] == 'object') {
  1691. svg = args[0];
  1692. args.shift();
  1693. }
  1694. var filename = args[0];
  1695. var ext = args[1];
  1696. var p = this;
  1697. this._makeSVGFrame({
  1698. svg: svg,
  1699. filename: filename,
  1700. extension: ext,
  1701. callback: function(err, frame) {
  1702. p.downloadFile(frame.imageData, frame.filename, frame.ext);
  1703. }
  1704. });
  1705. };
  1706. /**
  1707. * Extends p5's saveFrames with SVG support
  1708. *
  1709. * @function saveFrames
  1710. * @memberof p5.prototype
  1711. * @param {String} filename filename
  1712. * @param {String} extension Extension: 'svg' or 'jpg' or 'jpeg' or 'png'
  1713. * @param {Number} duration duration
  1714. * @param {Number} fps fps
  1715. * @param {Function} callback callback
  1716. */
  1717. var _saveFrames = p5.prototype.saveFrames;
  1718. p5.prototype.saveFrames = function(filename, extension, duration, fps, callback) {
  1719. var args = arguments;
  1720. if (!this._renderer.svg) {
  1721. _saveFrames.apply(this, args);
  1722. return;
  1723. }
  1724. duration = duration || 3;
  1725. duration = p5.prototype.constrain(duration, 0, 15);
  1726. duration = duration * 1000;
  1727. fps = fps || 15;
  1728. fps = p5.prototype.constrain(fps, 0, 22);
  1729. var count = 0;
  1730. var frames = [];
  1731. var pending = 0;
  1732. var p = this;
  1733. var frameFactory = setInterval(function () {
  1734. (function(count) {
  1735. pending++;
  1736. p._makeSVGFrame({
  1737. filename: filename + count,
  1738. extension: extension,
  1739. callback: function(err, frame) {
  1740. frames[count] = frame;
  1741. pending--;
  1742. }
  1743. });
  1744. })(count);
  1745. count++;
  1746. }, 1000 / fps);
  1747. var done = function() {
  1748. if (pending > 0) {
  1749. setTimeout(function() {
  1750. done();
  1751. }, 10);
  1752. return;
  1753. }
  1754. if (callback) {
  1755. callback(frames);
  1756. } else {
  1757. frames.forEach(function(f) {
  1758. p.downloadFile(f.imageData, f.filename, f.ext);
  1759. });
  1760. }
  1761. };
  1762. setTimeout(function () {
  1763. clearInterval(frameFactory);
  1764. done();
  1765. }, duration + 0.01);
  1766. };
  1767. /**
  1768. * Extends p5's save method with SVG support
  1769. *
  1770. * @function save
  1771. * @memberof p5.prototype
  1772. * @param {Graphics|Element|SVGElement} [source] Source to save
  1773. * @param {String} [filename] filename
  1774. */
  1775. var _save = p5.prototype.save;
  1776. p5.prototype.save = function() {
  1777. var args = arguments;
  1778. args = [args[0], args[1]];
  1779. var svg;
  1780. if (args[0] instanceof p5.Graphics) {
  1781. var svgcanvas = args[0].elt;
  1782. svg = svgcanvas.svg;
  1783. args.shift();
  1784. }
  1785. if (args[0] && args[0].elt) {
  1786. svg = args[0].elt;
  1787. args.shift();
  1788. }
  1789. if (typeof args[0] == 'object') {
  1790. svg = args[0];
  1791. args.shift();
  1792. }
  1793. svg = svg || (this._renderer && this._renderer.svg);
  1794. var filename = args[0];
  1795. var supportedExtensions = ['jpeg', 'png', 'jpg', 'svg', ''];
  1796. var ext = this._checkFileExtension(filename, '')[1];
  1797. var useSVG = svg && svg.nodeName && svg.nodeName.toLowerCase() === 'svg' && supportedExtensions.indexOf(ext) > -1;
  1798. if (useSVG) {
  1799. this.saveSVG(svg, filename);
  1800. } else {
  1801. return _save.apply(this, arguments);
  1802. }
  1803. };
  1804. /**
  1805. * Custom get in p5.svg (handles http and dataurl)
  1806. * @private
  1807. */
  1808. p5.prototype._svg_get = function(path, successCallback, failureCallback) {
  1809. if (path.indexOf('data:') === 0) {
  1810. if (path.indexOf(',') === -1) {
  1811. failureCallback(new Error('Fail to parse dataurl: ' + path));
  1812. return;
  1813. }
  1814. var svg = path.split(',').pop();
  1815. // force request to dataurl to be async
  1816. // so that it won't make preload mess
  1817. setTimeout(function() {
  1818. if (path.indexOf(';base64,') > -1) {
  1819. svg = atob(svg);
  1820. } else {
  1821. svg = decodeURIComponent(svg);
  1822. }
  1823. successCallback(svg);
  1824. }, 1);
  1825. return svg;
  1826. } else {
  1827. this.httpGet(path, successCallback);
  1828. return null;
  1829. }
  1830. };
  1831. /**
  1832. * loadSVG (like loadImage, but will return SVGElement)
  1833. *
  1834. * @function loadSVG
  1835. * @memberof p5.prototype
  1836. * @returns {p5.SVGElement}
  1837. */
  1838. p5.prototype.loadSVG = function(path, successCallback, failureCallback) {
  1839. var div = document.createElement('div');
  1840. var element = new p5.SVGElement(div);
  1841. this._svg_get(path, function(svg) {
  1842. div.innerHTML = svg;
  1843. svg = div.querySelector('svg');
  1844. if (!svg) {
  1845. if (failureCallback) {
  1846. failureCallback(new Error('Fail to create <svg>.'));
  1847. }
  1848. return;
  1849. }
  1850. element.elt = svg;
  1851. if (successCallback) {
  1852. successCallback(element);
  1853. }
  1854. }, failureCallback);
  1855. return element;
  1856. };
  1857. // cause preload to wait
  1858. p5.prototype._preloadMethods.loadSVG = p5.prototype;
  1859. p5.prototype.getDataURL = function() {
  1860. return this._renderer.elt.toDataURL('image/svg+xml');
  1861. };
  1862. };
  1863. },{}],9:[function(require,module,exports){
  1864. var SVGCanvas = require('svgcanvas');
  1865. module.exports = function(p5) {
  1866. /**
  1867. * @namespace RendererSVG
  1868. * @constructor
  1869. * @param {Element} elt canvas element to be replaced
  1870. * @param {p5} pInst p5 Instance
  1871. * @param {Bool} isMainCanvas
  1872. */
  1873. function RendererSVG(elt, pInst, isMainCanvas) {
  1874. var svgCanvas = new SVGCanvas();
  1875. var svg = svgCanvas.svg;
  1876. // replace <canvas> with <svg> and copy id, className
  1877. var parent = elt.parentNode;
  1878. var id = elt.id;
  1879. var className = elt.className;
  1880. parent.replaceChild(svgCanvas.getElement(), elt);
  1881. svgCanvas.id = id;
  1882. svgCanvas.className = className;
  1883. elt = svgCanvas; // our fake <canvas>
  1884. elt.parentNode = {
  1885. // fake parentNode.removeChild so that noCanvas will work
  1886. removeChild: function(element) {
  1887. if (element === elt) {
  1888. var wrapper = svgCanvas.getElement();
  1889. wrapper.parentNode.removeChild(wrapper);
  1890. }
  1891. }
  1892. };
  1893. p5.Renderer2D.call(this, elt, pInst, isMainCanvas);
  1894. this.isSVG = true;
  1895. this.svg = svg;
  1896. return this;
  1897. }
  1898. RendererSVG.prototype = Object.create(p5.Renderer2D.prototype);
  1899. RendererSVG.prototype._applyDefaults = function() {
  1900. p5.Renderer2D.prototype._applyDefaults.call(this);
  1901. this.drawingContext.lineWidth = 1;
  1902. };
  1903. RendererSVG.prototype.line = function(x1, y1, x2, y2) {
  1904. var styleEmpty = 'rgba(0,0,0,0)';
  1905. var ctx = this.drawingContext;
  1906. if (!this._doStroke) {
  1907. return this;
  1908. } else if(ctx.strokeStyle === styleEmpty){
  1909. return this;
  1910. }
  1911. ctx.beginPath();
  1912. ctx.moveTo(x1, y1);
  1913. ctx.lineTo(x2, y2);
  1914. ctx.stroke();
  1915. return this;
  1916. };
  1917. RendererSVG.prototype.resize = function(w, h) {
  1918. if (!w || !h) {
  1919. // ignore invalid values for width and height
  1920. return;
  1921. }
  1922. if (this.width !== w || this.height !== h) {
  1923. // canvas will be cleared if its size changed
  1924. // so, we do same thing for SVG
  1925. // note that at first this.width and this.height is undefined
  1926. // so, also check that
  1927. if (this.width && this.height) {
  1928. this.drawingContext.clearRect(0, 0, this.width, this.height);
  1929. }
  1930. }
  1931. this._withPixelDensity(function() {
  1932. p5.Renderer2D.prototype.resize.call(this, w, h);
  1933. });
  1934. // For scale, crop
  1935. // see also: http://sarasoueidan.com/blog/svg-coordinate-systems/
  1936. this.svg.setAttribute('viewBox', [0, 0, w, h].join(' '));
  1937. };
  1938. /**
  1939. * @private
  1940. */
  1941. RendererSVG.prototype._withPixelDensity = function(fn) {
  1942. var pixelDensity = this._pInst.pixelDensity;
  1943. this._pInst.pixelDensity = 1; // 1 is OK for SVG
  1944. fn.apply(this);
  1945. this._pInst.pixelDensity = pixelDensity;
  1946. };
  1947. RendererSVG.prototype.background = function() {
  1948. var args = arguments;
  1949. this._withPixelDensity(function() {
  1950. p5.Renderer2D.prototype.background.apply(this, args);
  1951. });
  1952. };
  1953. RendererSVG.prototype.resetMatrix = function() {
  1954. this._withPixelDensity(function() {
  1955. p5.Renderer2D.prototype.resetMatrix.apply(this);
  1956. });
  1957. };
  1958. /**
  1959. * set gc flag for svgcanvas
  1960. *
  1961. * @private
  1962. */
  1963. RendererSVG.prototype._setGCFlag = function(element) {
  1964. var that = this.drawingContext;
  1965. var currentGeneration = that.generations[that.generations.length - 1];
  1966. currentGeneration.push(element);
  1967. };
  1968. /**
  1969. * Append a element to current SVG Graphics
  1970. *
  1971. * @function appendChild
  1972. * @memberof RendererSVG.prototype
  1973. * @param {SVGElement|Element} element
  1974. */
  1975. RendererSVG.prototype.appendChild = function(element) {
  1976. if (element && element.elt) {
  1977. element = element.elt;
  1978. }
  1979. this._setGCFlag(element);
  1980. var g = this.drawingContext.__closestGroupOrSvg();
  1981. g.appendChild(element);
  1982. };
  1983. /**
  1984. * Draw an image or SVG to current SVG Graphics
  1985. *
  1986. * @function image
  1987. * @memberof RendererSVG.prototype
  1988. * @param {p5.Graphics|SVGGraphics|SVGElement|Element} image
  1989. * @param {Number} x
  1990. * @param {Number} y
  1991. * @param {Number} width
  1992. * @param {Number} height
  1993. */
  1994. RendererSVG.prototype.image = function(img, x, y, w, h) {
  1995. if (!img) {
  1996. throw new Error('Invalid image: ' + img);
  1997. }
  1998. var elt = img._renderer && img._renderer.svg; // handle SVG Graphics
  1999. elt = elt || (img.elt && img.elt.nodeName && (img.elt.nodeName.toLowerCase() === 'svg') && img.elt); // SVGElement
  2000. elt = elt || (img.nodeName && (img.nodeName.toLowerCase() == 'svg') && img); // <svg>
  2001. if (elt) {
  2002. // it's <svg> element, let's handle it
  2003. elt = elt.cloneNode(true);
  2004. elt.setAttribute('width', w);
  2005. elt.setAttribute('height', h);
  2006. elt.setAttribute('x', x);
  2007. elt.setAttribute('y', y);
  2008. this.appendChild(elt);
  2009. } else {
  2010. p5.Renderer2D.prototype.image.apply(this, arguments);
  2011. }
  2012. };
  2013. p5.RendererSVG = RendererSVG;
  2014. };
  2015. },{"svgcanvas":3}],10:[function(require,module,exports){
  2016. module.exports = function(p5) {
  2017. var SVGFilters = function() {};
  2018. var SVGElement = p5.SVGElement;
  2019. var generateID = function() {
  2020. return Date.now().toString() + Math.random().toString().replace(/0\./, '');
  2021. };
  2022. // @private
  2023. // We have to build a filter for each element
  2024. // the `filter: f1 f2` and svg param is not supported by many browsers
  2025. // so we can just modify the filter def to do so
  2026. SVGFilters.apply = function(svgElement, func, arg) {
  2027. // get filters
  2028. var filters = svgElement.attribute('data-p5-svg-filters') || '[]';
  2029. filters = JSON.parse(filters);
  2030. if (func) {
  2031. filters.push([func, arg]);
  2032. }
  2033. svgElement.attribute('data-p5-svg-filters', JSON.stringify(filters));
  2034. if (filters.length === 0) {
  2035. svgElement.attribute('filter', null);
  2036. return;
  2037. }
  2038. // generate filters chain
  2039. filters = filters.map(function(filter, index) {
  2040. var inGraphics = index === 0 ? 'SourceGraphic' : ('result-' + (index - 1));
  2041. var resultGraphics = 'result-' + index;
  2042. return SVGFilters[filter[0]].call(null, inGraphics, resultGraphics, filter[1]);
  2043. });
  2044. // get filter id for this element or create one
  2045. var filterid = svgElement.attribute('data-p5-svg-filter-id');
  2046. if (!filterid) {
  2047. filterid = 'p5-svg-' + generateID();
  2048. svgElement.attribute('data-p5-svg-filter-id', filterid);
  2049. }
  2050. // Note that when filters is [], we will remove filter attr
  2051. // So, here, we write this attr every time
  2052. svgElement.attribute('filter', 'url(#' + filterid + ')');
  2053. // create <filter>
  2054. var filter = SVGElement.create('filter', {id: filterid});
  2055. filters.forEach(function(elt) {
  2056. if (!Array.isArray(elt)) {
  2057. elt = [elt];
  2058. }
  2059. elt.forEach(function(elt) {
  2060. filter.append(elt);
  2061. });
  2062. });
  2063. // get defs
  2064. var defs = svgElement._getDefs();
  2065. var oldfilter = defs.query('#' + filterid)[0];
  2066. if (!oldfilter) {
  2067. defs.append(filter);
  2068. } else {
  2069. oldfilter.elt.parentNode.replaceChild(filter.elt, oldfilter.elt);
  2070. }
  2071. };
  2072. SVGFilters.blur = function(inGraphics, resultGraphics, val) {
  2073. return SVGElement.create('feGaussianBlur', {
  2074. stdDeviation: val,
  2075. in: inGraphics,
  2076. result: resultGraphics,
  2077. 'color-interpolation-filters': 'sRGB'
  2078. });
  2079. };
  2080. // See also: http://www.w3.org/TR/SVG11/filters.html#feColorMatrixElement
  2081. // See also: http://stackoverflow.com/questions/21977929/match-colors-in-fecolormatrix-filter
  2082. SVGFilters.colorMatrix = function(inGraphics, resultGraphics, matrix) {
  2083. return SVGElement.create('feColorMatrix', {
  2084. type: 'matrix',
  2085. values: matrix.join(' '),
  2086. 'color-interpolation-filters': 'sRGB',
  2087. in: inGraphics,
  2088. result: resultGraphics
  2089. });
  2090. };
  2091. // Here we use CIE luminance for RGB
  2092. SVGFilters.gray = function(inGraphics, resultGraphics) {
  2093. var matrix = [
  2094. 0.2126, 0.7152, 0.0722, 0, 0, // R'
  2095. 0.2126, 0.7152, 0.0722, 0, 0, // G'
  2096. 0.2126, 0.7152, 0.0722, 0, 0, // B'
  2097. 0, 0, 0, 1, 0 // A'
  2098. ];
  2099. return SVGFilters.colorMatrix(inGraphics, resultGraphics, matrix);
  2100. };
  2101. SVGFilters.threshold = function(inGraphics, resultGraphics, val) {
  2102. var elements = [];
  2103. elements.push(SVGFilters.gray(inGraphics, resultGraphics + '-tmp'));
  2104. var componentTransfer = SVGElement.create('feComponentTransfer', {
  2105. 'in': resultGraphics + '-tmp',
  2106. result: resultGraphics
  2107. });
  2108. var thresh = Math.floor(val * 255);
  2109. ['R', 'G', 'B'].forEach(function(channel) {
  2110. // Note that original value is from 0 to 1
  2111. var func = SVGElement.create('feFunc' + channel, {
  2112. type: 'linear',
  2113. slope: 255, // all non-zero * 255
  2114. intercept: (thresh - 1) * -1
  2115. });
  2116. componentTransfer.append(func);
  2117. });
  2118. elements.push(componentTransfer);
  2119. return elements;
  2120. };
  2121. SVGFilters.invert = function(inGraphics, resultGraphics) {
  2122. var matrix = [
  2123. -1, 0, 0, 0, 1,
  2124. 0, -1, 0, 0, 1,
  2125. 0, 0, -1, 0, 1,
  2126. 0, 0, 0, 1, 0
  2127. ];
  2128. return SVGFilters.colorMatrix(inGraphics, resultGraphics, matrix);
  2129. };
  2130. SVGFilters.opaque = function(inGraphics, resultGraphics) {
  2131. var matrix = [
  2132. 1, 0, 0, 0, 0, // original R
  2133. 0, 1, 0, 0, 0, // original G
  2134. 0, 0, 1, 0, 0, // original B
  2135. 0, 0, 0, 0, 1 // set A to 1
  2136. ];
  2137. return SVGFilters.colorMatrix(inGraphics, resultGraphics, matrix);
  2138. };
  2139. /**
  2140. * Generate discrete table values based on the given color map function
  2141. *
  2142. * @private
  2143. * @param {Function} fn - Function to map channel values (val ∈ [0, 255])
  2144. * @see http://www.w3.org/TR/SVG/filters.html#feComponentTransferElement
  2145. */
  2146. SVGFilters._discreteTableValues = function(fn) {
  2147. var table = [];
  2148. for (var val = 0; val < 256; val++) {
  2149. var newval = fn(val);
  2150. table.push(newval / 255); // map to ∈ [0, 1]
  2151. }
  2152. return table;
  2153. };
  2154. /**
  2155. * Limits each channel of the image to the number of colors specified as
  2156. * the parameter. The parameter can be set to values between 2 and 255, but
  2157. * results are most noticeable in the lower ranges.
  2158. *
  2159. * Adapted from p5's Filters.posterize
  2160. */
  2161. SVGFilters.posterize = function(inGraphics, resultGraphics, level) {
  2162. level = parseInt(level, 10);
  2163. if ((level < 2) || (level > 255)) {
  2164. throw new Error(
  2165. 'Level must be greater than 2 and less than 255 for posterize'
  2166. );
  2167. }
  2168. var tableValues = SVGFilters._discreteTableValues(function(val) {
  2169. return (((val * level) >> 8) * 255) / (level - 1);
  2170. });
  2171. var componentTransfer = SVGElement.create('feComponentTransfer', {
  2172. 'in': inGraphics,
  2173. result: resultGraphics,
  2174. 'color-interpolation-filters': 'sRGB'
  2175. });
  2176. ['R', 'G', 'B'].forEach(function(channel) {
  2177. var func = SVGElement.create('feFunc' + channel, {
  2178. type: 'discrete',
  2179. tableValues: tableValues.join(' ')
  2180. });
  2181. componentTransfer.append(func);
  2182. });
  2183. return componentTransfer;
  2184. };
  2185. SVGFilters._blendOffset = function(inGraphics, resultGraphics, mode) {
  2186. var elements = [];
  2187. [
  2188. ['left', -1, 0],
  2189. ['right', 1, 0],
  2190. ['up', 0, -1],
  2191. ['down', 0, 1]
  2192. ].forEach(function(neighbor) {
  2193. elements.push(SVGElement.create('feOffset', {
  2194. 'in': inGraphics,
  2195. result: resultGraphics + '-' + neighbor[0],
  2196. dx: neighbor[1],
  2197. dy: neighbor[2]
  2198. }));
  2199. });
  2200. [
  2201. [null, inGraphics],
  2202. [resultGraphics + '-left', resultGraphics + '-tmp-0'],
  2203. [resultGraphics + '-right', resultGraphics + '-tmp-1'],
  2204. [resultGraphics + '-up', resultGraphics + '-tmp-2'],
  2205. [resultGraphics + '-down', resultGraphics + '-tmp-3']
  2206. ].forEach(function(layer, i, layers) {
  2207. if (i === 0) {
  2208. return;
  2209. }
  2210. elements.push(SVGElement.create('feBlend', {
  2211. 'in': layers[i - 1][1],
  2212. in2: layer[0],
  2213. result: layer[1],
  2214. mode: mode
  2215. }));
  2216. });
  2217. return elements;
  2218. };
  2219. /**
  2220. * Increases the bright areas in an image
  2221. *
  2222. * Will create 4 offset layer and blend them using darken mode
  2223. */
  2224. SVGFilters.erode = function(inGraphics, resultGraphics) {
  2225. return SVGFilters._blendOffset(inGraphics, resultGraphics, 'darken');
  2226. };
  2227. SVGFilters.dilate = function(inGraphics, resultGraphics) {
  2228. return SVGFilters._blendOffset(inGraphics, resultGraphics, 'lighten');
  2229. };
  2230. p5.SVGFilters = SVGFilters;
  2231. return SVGFilters;
  2232. };
  2233. },{}],11:[function(require,module,exports){
  2234. var constants = require('./constants');
  2235. var SVGCanvas = require('svgcanvas');
  2236. module.exports = function(p5) {
  2237. // patch p5.Graphics for SVG
  2238. var _graphics = p5.Graphics;
  2239. p5.Graphics = function(w, h, renderer, pInst) {
  2240. var args = arguments;
  2241. _graphics.apply(this, args);
  2242. if (renderer === constants.SVG) {
  2243. // replace <canvas> with <svg>
  2244. var c = this._renderer.elt;
  2245. this._renderer = new p5.RendererSVG(c, pInst, false); // replace renderer
  2246. c = this._renderer.elt;
  2247. this.elt = c; // replace this.elt
  2248. // do default again
  2249. this._renderer.resize(w, h);
  2250. this._renderer._applyDefaults();
  2251. }
  2252. return this;
  2253. };
  2254. p5.Graphics.prototype = _graphics.prototype;
  2255. /**
  2256. * Due to a known issue (image is not surely ready before onload fires),
  2257. * we have no way to draw SVG element synchronously.
  2258. * So, this method will load a SVG Graphics
  2259. * and then convert it to Canvas Graphics asynchronously
  2260. *
  2261. * @see https://github.com/zenozeng/p5.js-svg/issues/78
  2262. *
  2263. * @function loadGraphics
  2264. * @memberof p5.prototype
  2265. * @param {p5.Graphics} graphics the p5.Grphaics object
  2266. * @param {Function(p5.Graphics)} [successCallback] Function to be called once
  2267. * the SVG Graphics is loaded. Will be passed the
  2268. * p5.Graphics.
  2269. * @param {Function(Event)} [failureCallback] called with event error.
  2270. *
  2271. * @example
  2272. * pg = createGraphics(100, 100, SVG);
  2273. * background(200);
  2274. * pg.background(100);
  2275. * pg.ellipse(pg.width/2, pg.height/2, 50, 50);
  2276. * loadGraphics(pg, function(pgCanvas) {
  2277. * image(pgCanvas, 50, 50);
  2278. * image(pgCanvas, 0, 0, 50, 50);
  2279. * });
  2280. *
  2281. */
  2282. p5.prototype.loadGraphics = function(graphics, successCallback, failureCallback) {
  2283. if (graphics._renderer.svg) {
  2284. var svg = graphics._renderer.svg;
  2285. var url = SVGCanvas.prototype.toDataURL.call(graphics._renderer.elt, 'image/svg+xml');
  2286. var pg = this.createGraphics(graphics.width, graphics.height);
  2287. // also copy SVG, so we can keep vector SVG when image(pg) in SVG runtime
  2288. pg._renderer.svg = svg.cloneNode(true);
  2289. pg.loadImage(url, function(img) {
  2290. pg.image(img);
  2291. successCallback(pg);
  2292. }, failureCallback);
  2293. } else {
  2294. successCallback(graphics);
  2295. }
  2296. };
  2297. /**
  2298. * Patched version of createCanvas
  2299. *
  2300. * use createCanvas(100, 100, SVG) to create SVG canvas.
  2301. *
  2302. * Creates a SVG element in the document, and sets its width and
  2303. * height in pixels. This method should be called only once at
  2304. * the start of setup.
  2305. * @function createCanvas
  2306. * @memberof p5.prototype
  2307. * @param {Number} width - Width (in px) for SVG Element
  2308. * @param {Number} height - Height (in px) for SVG Element
  2309. * @return {Graphics}
  2310. */
  2311. var _createCanvas = p5.prototype.createCanvas;
  2312. p5.prototype.createCanvas = function(w, h, renderer) {
  2313. var graphics = _createCanvas.apply(this, arguments);
  2314. if (renderer === constants.SVG) {
  2315. var c = graphics.elt;
  2316. this._setProperty('_renderer', new p5.RendererSVG(c, this, true));
  2317. this._isdefaultGraphics = true;
  2318. this._renderer.resize(w, h);
  2319. this._renderer._applyDefaults();
  2320. }
  2321. return this._renderer;
  2322. };
  2323. };
  2324. },{"./constants":4,"svgcanvas":3}],12:[function(require,module,exports){
  2325. require('../src/index.js')(p5);
  2326. },{"../src/index.js":7}]},{},[12]);
  2327. });