fabric.js 785 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361133621336313364133651336613367133681336913370133711337213373133741337513376133771337813379133801338113382133831338413385133861338713388133891339013391133921339313394133951339613397133981339913400134011340213403134041340513406134071340813409134101341113412134131341413415134161341713418134191342013421134221342313424134251342613427134281342913430134311343213433134341343513436134371343813439134401344113442134431344413445134461344713448134491345013451134521345313454134551345613457134581345913460134611346213463134641346513466134671346813469134701347113472134731347413475134761347713478134791348013481134821348313484134851348613487134881348913490134911349213493134941349513496134971349813499135001350113502135031350413505135061350713508135091351013511135121351313514135151351613517135181351913520135211352213523135241352513526135271352813529135301353113532135331353413535135361353713538135391354013541135421354313544135451354613547135481354913550135511355213553135541355513556135571355813559135601356113562135631356413565135661356713568135691357013571135721357313574135751357613577135781357913580135811358213583135841358513586135871358813589135901359113592135931359413595135961359713598135991360013601136021360313604136051360613607136081360913610136111361213613136141361513616136171361813619136201362113622136231362413625136261362713628136291363013631136321363313634136351363613637136381363913640136411364213643136441364513646136471364813649136501365113652136531365413655136561365713658136591366013661136621366313664136651366613667136681366913670136711367213673136741367513676136771367813679136801368113682136831368413685136861368713688136891369013691136921369313694136951369613697136981369913700137011370213703137041370513706137071370813709137101371113712137131371413715137161371713718137191372013721137221372313724137251372613727137281372913730137311373213733137341373513736137371373813739137401374113742137431374413745137461374713748137491375013751137521375313754137551375613757137581375913760137611376213763137641376513766137671376813769137701377113772137731377413775137761377713778137791378013781137821378313784137851378613787137881378913790137911379213793137941379513796137971379813799138001380113802138031380413805138061380713808138091381013811138121381313814138151381613817138181381913820138211382213823138241382513826138271382813829138301383113832138331383413835138361383713838138391384013841138421384313844138451384613847138481384913850138511385213853138541385513856138571385813859138601386113862138631386413865138661386713868138691387013871138721387313874138751387613877138781387913880138811388213883138841388513886138871388813889138901389113892138931389413895138961389713898138991390013901139021390313904139051390613907139081390913910139111391213913139141391513916139171391813919139201392113922139231392413925139261392713928139291393013931139321393313934139351393613937139381393913940139411394213943139441394513946139471394813949139501395113952139531395413955139561395713958139591396013961139621396313964139651396613967139681396913970139711397213973139741397513976139771397813979139801398113982139831398413985139861398713988139891399013991139921399313994139951399613997139981399914000140011400214003140041400514006140071400814009140101401114012140131401414015140161401714018140191402014021140221402314024140251402614027140281402914030140311403214033140341403514036140371403814039140401404114042140431404414045140461404714048140491405014051140521405314054140551405614057140581405914060140611406214063140641406514066140671406814069140701407114072140731407414075140761407714078140791408014081140821408314084140851408614087140881408914090140911409214093140941409514096140971409814099141001410114102141031410414105141061410714108141091411014111141121411314114141151411614117141181411914120141211412214123141241412514126141271412814129141301413114132141331413414135141361413714138141391414014141141421414314144141451414614147141481414914150141511415214153141541415514156141571415814159141601416114162141631416414165141661416714168141691417014171141721417314174141751417614177141781417914180141811418214183141841418514186141871418814189141901419114192141931419414195141961419714198141991420014201142021420314204142051420614207142081420914210142111421214213142141421514216142171421814219142201422114222142231422414225142261422714228142291423014231142321423314234142351423614237142381423914240142411424214243142441424514246142471424814249142501425114252142531425414255142561425714258142591426014261142621426314264142651426614267142681426914270142711427214273142741427514276142771427814279142801428114282142831428414285142861428714288142891429014291142921429314294142951429614297142981429914300143011430214303143041430514306143071430814309143101431114312143131431414315143161431714318143191432014321143221432314324143251432614327143281432914330143311433214333143341433514336143371433814339143401434114342143431434414345143461434714348143491435014351143521435314354143551435614357143581435914360143611436214363143641436514366143671436814369143701437114372143731437414375143761437714378143791438014381143821438314384143851438614387143881438914390143911439214393143941439514396143971439814399144001440114402144031440414405144061440714408144091441014411144121441314414144151441614417144181441914420144211442214423144241442514426144271442814429144301443114432144331443414435144361443714438144391444014441144421444314444144451444614447144481444914450144511445214453144541445514456144571445814459144601446114462144631446414465144661446714468144691447014471144721447314474144751447614477144781447914480144811448214483144841448514486144871448814489144901449114492144931449414495144961449714498144991450014501145021450314504145051450614507145081450914510145111451214513145141451514516145171451814519145201452114522145231452414525145261452714528145291453014531145321453314534145351453614537145381453914540145411454214543145441454514546145471454814549145501455114552145531455414555145561455714558145591456014561145621456314564145651456614567145681456914570145711457214573145741457514576145771457814579145801458114582145831458414585145861458714588145891459014591145921459314594145951459614597145981459914600146011460214603146041460514606146071460814609146101461114612146131461414615146161461714618146191462014621146221462314624146251462614627146281462914630146311463214633146341463514636146371463814639146401464114642146431464414645146461464714648146491465014651146521465314654146551465614657146581465914660146611466214663146641466514666146671466814669146701467114672146731467414675146761467714678146791468014681146821468314684146851468614687146881468914690146911469214693146941469514696146971469814699147001470114702147031470414705147061470714708147091471014711147121471314714147151471614717147181471914720147211472214723147241472514726147271472814729147301473114732147331473414735147361473714738147391474014741147421474314744147451474614747147481474914750147511475214753147541475514756147571475814759147601476114762147631476414765147661476714768147691477014771147721477314774147751477614777147781477914780147811478214783147841478514786147871478814789147901479114792147931479414795147961479714798147991480014801148021480314804148051480614807148081480914810148111481214813148141481514816148171481814819148201482114822148231482414825148261482714828148291483014831148321483314834148351483614837148381483914840148411484214843148441484514846148471484814849148501485114852148531485414855148561485714858148591486014861148621486314864148651486614867148681486914870148711487214873148741487514876148771487814879148801488114882148831488414885148861488714888148891489014891148921489314894148951489614897148981489914900149011490214903149041490514906149071490814909149101491114912149131491414915149161491714918149191492014921149221492314924149251492614927149281492914930149311493214933149341493514936149371493814939149401494114942149431494414945149461494714948149491495014951149521495314954149551495614957149581495914960149611496214963149641496514966149671496814969149701497114972149731497414975149761497714978149791498014981149821498314984149851498614987149881498914990149911499214993149941499514996149971499814999150001500115002150031500415005150061500715008150091501015011150121501315014150151501615017150181501915020150211502215023150241502515026150271502815029150301503115032150331503415035150361503715038150391504015041150421504315044150451504615047150481504915050150511505215053150541505515056150571505815059150601506115062150631506415065150661506715068150691507015071150721507315074150751507615077150781507915080150811508215083150841508515086150871508815089150901509115092150931509415095150961509715098150991510015101151021510315104151051510615107151081510915110151111511215113151141511515116151171511815119151201512115122151231512415125151261512715128151291513015131151321513315134151351513615137151381513915140151411514215143151441514515146151471514815149151501515115152151531515415155151561515715158151591516015161151621516315164151651516615167151681516915170151711517215173151741517515176151771517815179151801518115182151831518415185151861518715188151891519015191151921519315194151951519615197151981519915200152011520215203152041520515206152071520815209152101521115212152131521415215152161521715218152191522015221152221522315224152251522615227152281522915230152311523215233152341523515236152371523815239152401524115242152431524415245152461524715248152491525015251152521525315254152551525615257152581525915260152611526215263152641526515266152671526815269152701527115272152731527415275152761527715278152791528015281152821528315284152851528615287152881528915290152911529215293152941529515296152971529815299153001530115302153031530415305153061530715308153091531015311153121531315314153151531615317153181531915320153211532215323153241532515326153271532815329153301533115332153331533415335153361533715338153391534015341153421534315344153451534615347153481534915350153511535215353153541535515356153571535815359153601536115362153631536415365153661536715368153691537015371153721537315374153751537615377153781537915380153811538215383153841538515386153871538815389153901539115392153931539415395153961539715398153991540015401154021540315404154051540615407154081540915410154111541215413154141541515416154171541815419154201542115422154231542415425154261542715428154291543015431154321543315434154351543615437154381543915440154411544215443154441544515446154471544815449154501545115452154531545415455154561545715458154591546015461154621546315464154651546615467154681546915470154711547215473154741547515476154771547815479154801548115482154831548415485154861548715488154891549015491154921549315494154951549615497154981549915500155011550215503155041550515506155071550815509155101551115512155131551415515155161551715518155191552015521155221552315524155251552615527155281552915530155311553215533155341553515536155371553815539155401554115542155431554415545155461554715548155491555015551155521555315554155551555615557155581555915560155611556215563155641556515566155671556815569155701557115572155731557415575155761557715578155791558015581155821558315584155851558615587155881558915590155911559215593155941559515596155971559815599156001560115602156031560415605156061560715608156091561015611156121561315614156151561615617156181561915620156211562215623156241562515626156271562815629156301563115632156331563415635156361563715638156391564015641156421564315644156451564615647156481564915650156511565215653156541565515656156571565815659156601566115662156631566415665156661566715668156691567015671156721567315674156751567615677156781567915680156811568215683156841568515686156871568815689156901569115692156931569415695156961569715698156991570015701157021570315704157051570615707157081570915710157111571215713157141571515716157171571815719157201572115722157231572415725157261572715728157291573015731157321573315734157351573615737157381573915740157411574215743157441574515746157471574815749157501575115752157531575415755157561575715758157591576015761157621576315764157651576615767157681576915770157711577215773157741577515776157771577815779157801578115782157831578415785157861578715788157891579015791157921579315794157951579615797157981579915800158011580215803158041580515806158071580815809158101581115812158131581415815158161581715818158191582015821158221582315824158251582615827158281582915830158311583215833158341583515836158371583815839158401584115842158431584415845158461584715848158491585015851158521585315854158551585615857158581585915860158611586215863158641586515866158671586815869158701587115872158731587415875158761587715878158791588015881158821588315884158851588615887158881588915890158911589215893158941589515896158971589815899159001590115902159031590415905159061590715908159091591015911159121591315914159151591615917159181591915920159211592215923159241592515926159271592815929159301593115932159331593415935159361593715938159391594015941159421594315944159451594615947159481594915950159511595215953159541595515956159571595815959159601596115962159631596415965159661596715968159691597015971159721597315974159751597615977159781597915980159811598215983159841598515986159871598815989159901599115992159931599415995159961599715998159991600016001160021600316004160051600616007160081600916010160111601216013160141601516016160171601816019160201602116022160231602416025160261602716028160291603016031160321603316034160351603616037160381603916040160411604216043160441604516046160471604816049160501605116052160531605416055160561605716058160591606016061160621606316064160651606616067160681606916070160711607216073160741607516076160771607816079160801608116082160831608416085160861608716088160891609016091160921609316094160951609616097160981609916100161011610216103161041610516106161071610816109161101611116112161131611416115161161611716118161191612016121161221612316124161251612616127161281612916130161311613216133161341613516136161371613816139161401614116142161431614416145161461614716148161491615016151161521615316154161551615616157161581615916160161611616216163161641616516166161671616816169161701617116172161731617416175161761617716178161791618016181161821618316184161851618616187161881618916190161911619216193161941619516196161971619816199162001620116202162031620416205162061620716208162091621016211162121621316214162151621616217162181621916220162211622216223162241622516226162271622816229162301623116232162331623416235162361623716238162391624016241162421624316244162451624616247162481624916250162511625216253162541625516256162571625816259162601626116262162631626416265162661626716268162691627016271162721627316274162751627616277162781627916280162811628216283162841628516286162871628816289162901629116292162931629416295162961629716298162991630016301163021630316304163051630616307163081630916310163111631216313163141631516316163171631816319163201632116322163231632416325163261632716328163291633016331163321633316334163351633616337163381633916340163411634216343163441634516346163471634816349163501635116352163531635416355163561635716358163591636016361163621636316364163651636616367163681636916370163711637216373163741637516376163771637816379163801638116382163831638416385163861638716388163891639016391163921639316394163951639616397163981639916400164011640216403164041640516406164071640816409164101641116412164131641416415164161641716418164191642016421164221642316424164251642616427164281642916430164311643216433164341643516436164371643816439164401644116442164431644416445164461644716448164491645016451164521645316454164551645616457164581645916460164611646216463164641646516466164671646816469164701647116472164731647416475164761647716478164791648016481164821648316484164851648616487164881648916490164911649216493164941649516496164971649816499165001650116502165031650416505165061650716508165091651016511165121651316514165151651616517165181651916520165211652216523165241652516526165271652816529165301653116532165331653416535165361653716538165391654016541165421654316544165451654616547165481654916550165511655216553165541655516556165571655816559165601656116562165631656416565165661656716568165691657016571165721657316574165751657616577165781657916580165811658216583165841658516586165871658816589165901659116592165931659416595165961659716598165991660016601166021660316604166051660616607166081660916610166111661216613166141661516616166171661816619166201662116622166231662416625166261662716628166291663016631166321663316634166351663616637166381663916640166411664216643166441664516646166471664816649166501665116652166531665416655166561665716658166591666016661166621666316664166651666616667166681666916670166711667216673166741667516676166771667816679166801668116682166831668416685166861668716688166891669016691166921669316694166951669616697166981669916700167011670216703167041670516706167071670816709167101671116712167131671416715167161671716718167191672016721167221672316724167251672616727167281672916730167311673216733167341673516736167371673816739167401674116742167431674416745167461674716748167491675016751167521675316754167551675616757167581675916760167611676216763167641676516766167671676816769167701677116772167731677416775167761677716778167791678016781167821678316784167851678616787167881678916790167911679216793167941679516796167971679816799168001680116802168031680416805168061680716808168091681016811168121681316814168151681616817168181681916820168211682216823168241682516826168271682816829168301683116832168331683416835168361683716838168391684016841168421684316844168451684616847168481684916850168511685216853168541685516856168571685816859168601686116862168631686416865168661686716868168691687016871168721687316874168751687616877168781687916880168811688216883168841688516886168871688816889168901689116892168931689416895168961689716898168991690016901169021690316904169051690616907169081690916910169111691216913169141691516916169171691816919169201692116922169231692416925169261692716928169291693016931169321693316934169351693616937169381693916940169411694216943169441694516946169471694816949169501695116952169531695416955169561695716958169591696016961169621696316964169651696616967169681696916970169711697216973169741697516976169771697816979169801698116982169831698416985169861698716988169891699016991169921699316994169951699616997169981699917000170011700217003170041700517006170071700817009170101701117012170131701417015170161701717018170191702017021170221702317024170251702617027170281702917030170311703217033170341703517036170371703817039170401704117042170431704417045170461704717048170491705017051170521705317054170551705617057170581705917060170611706217063170641706517066170671706817069170701707117072170731707417075170761707717078170791708017081170821708317084170851708617087170881708917090170911709217093170941709517096170971709817099171001710117102171031710417105171061710717108171091711017111171121711317114171151711617117171181711917120171211712217123171241712517126171271712817129171301713117132171331713417135171361713717138171391714017141171421714317144171451714617147171481714917150171511715217153171541715517156171571715817159171601716117162171631716417165171661716717168171691717017171171721717317174171751717617177171781717917180171811718217183171841718517186171871718817189171901719117192171931719417195171961719717198171991720017201172021720317204172051720617207172081720917210172111721217213172141721517216172171721817219172201722117222172231722417225172261722717228172291723017231172321723317234172351723617237172381723917240172411724217243172441724517246172471724817249172501725117252172531725417255172561725717258172591726017261172621726317264172651726617267172681726917270172711727217273172741727517276172771727817279172801728117282172831728417285172861728717288172891729017291172921729317294172951729617297172981729917300173011730217303173041730517306173071730817309173101731117312173131731417315173161731717318173191732017321173221732317324173251732617327173281732917330173311733217333173341733517336173371733817339173401734117342173431734417345173461734717348173491735017351173521735317354173551735617357173581735917360173611736217363173641736517366173671736817369173701737117372173731737417375173761737717378173791738017381173821738317384173851738617387173881738917390173911739217393173941739517396173971739817399174001740117402174031740417405174061740717408174091741017411174121741317414174151741617417174181741917420174211742217423174241742517426174271742817429174301743117432174331743417435174361743717438174391744017441174421744317444174451744617447174481744917450174511745217453174541745517456174571745817459174601746117462174631746417465174661746717468174691747017471174721747317474174751747617477174781747917480174811748217483174841748517486174871748817489174901749117492174931749417495174961749717498174991750017501175021750317504175051750617507175081750917510175111751217513175141751517516175171751817519175201752117522175231752417525175261752717528175291753017531175321753317534175351753617537175381753917540175411754217543175441754517546175471754817549175501755117552175531755417555175561755717558175591756017561175621756317564175651756617567175681756917570175711757217573175741757517576175771757817579175801758117582175831758417585175861758717588175891759017591175921759317594175951759617597175981759917600176011760217603176041760517606176071760817609176101761117612176131761417615176161761717618176191762017621176221762317624176251762617627176281762917630176311763217633176341763517636176371763817639176401764117642176431764417645176461764717648176491765017651176521765317654176551765617657176581765917660176611766217663176641766517666176671766817669176701767117672176731767417675176761767717678176791768017681176821768317684176851768617687176881768917690176911769217693176941769517696176971769817699177001770117702177031770417705177061770717708177091771017711177121771317714177151771617717177181771917720177211772217723177241772517726177271772817729177301773117732177331773417735177361773717738177391774017741177421774317744177451774617747177481774917750177511775217753177541775517756177571775817759177601776117762177631776417765177661776717768177691777017771177721777317774177751777617777177781777917780177811778217783177841778517786177871778817789177901779117792177931779417795177961779717798177991780017801178021780317804178051780617807178081780917810178111781217813178141781517816178171781817819178201782117822178231782417825178261782717828178291783017831178321783317834178351783617837178381783917840178411784217843178441784517846178471784817849178501785117852178531785417855178561785717858178591786017861178621786317864178651786617867178681786917870178711787217873178741787517876178771787817879178801788117882178831788417885178861788717888178891789017891178921789317894178951789617897178981789917900179011790217903179041790517906179071790817909179101791117912179131791417915179161791717918179191792017921179221792317924179251792617927179281792917930179311793217933179341793517936179371793817939179401794117942179431794417945179461794717948179491795017951179521795317954179551795617957179581795917960179611796217963179641796517966179671796817969179701797117972179731797417975179761797717978179791798017981179821798317984179851798617987179881798917990179911799217993179941799517996179971799817999180001800118002180031800418005180061800718008180091801018011180121801318014180151801618017180181801918020180211802218023180241802518026180271802818029180301803118032180331803418035180361803718038180391804018041180421804318044180451804618047180481804918050180511805218053180541805518056180571805818059180601806118062180631806418065180661806718068180691807018071180721807318074180751807618077180781807918080180811808218083180841808518086180871808818089180901809118092180931809418095180961809718098180991810018101181021810318104181051810618107181081810918110181111811218113181141811518116181171811818119181201812118122181231812418125181261812718128181291813018131181321813318134181351813618137181381813918140181411814218143181441814518146181471814818149181501815118152181531815418155181561815718158181591816018161181621816318164181651816618167181681816918170181711817218173181741817518176181771817818179181801818118182181831818418185181861818718188181891819018191181921819318194181951819618197181981819918200182011820218203182041820518206182071820818209182101821118212182131821418215182161821718218182191822018221182221822318224182251822618227182281822918230182311823218233182341823518236182371823818239182401824118242182431824418245182461824718248182491825018251182521825318254182551825618257182581825918260182611826218263182641826518266182671826818269182701827118272182731827418275182761827718278182791828018281182821828318284182851828618287182881828918290182911829218293182941829518296182971829818299183001830118302183031830418305183061830718308183091831018311183121831318314183151831618317183181831918320183211832218323183241832518326183271832818329183301833118332183331833418335183361833718338183391834018341183421834318344183451834618347183481834918350183511835218353183541835518356183571835818359183601836118362183631836418365183661836718368183691837018371183721837318374183751837618377183781837918380183811838218383183841838518386183871838818389183901839118392183931839418395183961839718398183991840018401184021840318404184051840618407184081840918410184111841218413184141841518416184171841818419184201842118422184231842418425184261842718428184291843018431184321843318434184351843618437184381843918440184411844218443184441844518446184471844818449184501845118452184531845418455184561845718458184591846018461184621846318464184651846618467184681846918470184711847218473184741847518476184771847818479184801848118482184831848418485184861848718488184891849018491184921849318494184951849618497184981849918500185011850218503185041850518506185071850818509185101851118512185131851418515185161851718518185191852018521185221852318524185251852618527185281852918530185311853218533185341853518536185371853818539185401854118542185431854418545185461854718548185491855018551185521855318554185551855618557185581855918560185611856218563185641856518566185671856818569185701857118572185731857418575185761857718578185791858018581185821858318584185851858618587185881858918590185911859218593185941859518596185971859818599186001860118602186031860418605186061860718608186091861018611186121861318614186151861618617186181861918620186211862218623186241862518626186271862818629186301863118632186331863418635186361863718638186391864018641186421864318644186451864618647186481864918650186511865218653186541865518656186571865818659186601866118662186631866418665186661866718668186691867018671186721867318674186751867618677186781867918680186811868218683186841868518686186871868818689186901869118692186931869418695186961869718698186991870018701187021870318704187051870618707187081870918710187111871218713187141871518716187171871818719187201872118722187231872418725187261872718728187291873018731187321873318734187351873618737187381873918740187411874218743187441874518746187471874818749187501875118752187531875418755187561875718758187591876018761187621876318764187651876618767187681876918770187711877218773187741877518776187771877818779187801878118782187831878418785187861878718788187891879018791187921879318794187951879618797187981879918800188011880218803188041880518806188071880818809188101881118812188131881418815188161881718818188191882018821188221882318824188251882618827188281882918830188311883218833188341883518836188371883818839188401884118842188431884418845188461884718848188491885018851188521885318854188551885618857188581885918860188611886218863188641886518866188671886818869188701887118872188731887418875188761887718878188791888018881188821888318884188851888618887188881888918890188911889218893188941889518896188971889818899189001890118902189031890418905189061890718908189091891018911189121891318914189151891618917189181891918920189211892218923189241892518926189271892818929189301893118932189331893418935189361893718938189391894018941189421894318944189451894618947189481894918950189511895218953189541895518956189571895818959189601896118962189631896418965189661896718968189691897018971189721897318974189751897618977189781897918980189811898218983189841898518986189871898818989189901899118992189931899418995189961899718998189991900019001190021900319004190051900619007190081900919010190111901219013190141901519016190171901819019190201902119022190231902419025190261902719028190291903019031190321903319034190351903619037190381903919040190411904219043190441904519046190471904819049190501905119052190531905419055190561905719058190591906019061190621906319064190651906619067190681906919070190711907219073190741907519076190771907819079190801908119082190831908419085190861908719088190891909019091190921909319094190951909619097190981909919100191011910219103191041910519106191071910819109191101911119112191131911419115191161911719118191191912019121191221912319124191251912619127191281912919130191311913219133191341913519136191371913819139191401914119142191431914419145191461914719148191491915019151191521915319154191551915619157191581915919160191611916219163191641916519166191671916819169191701917119172191731917419175191761917719178191791918019181191821918319184191851918619187191881918919190191911919219193191941919519196191971919819199192001920119202192031920419205192061920719208192091921019211192121921319214192151921619217192181921919220192211922219223192241922519226192271922819229192301923119232192331923419235192361923719238192391924019241192421924319244192451924619247192481924919250192511925219253192541925519256192571925819259192601926119262192631926419265192661926719268192691927019271192721927319274192751927619277192781927919280192811928219283192841928519286192871928819289192901929119292192931929419295192961929719298192991930019301193021930319304193051930619307193081930919310193111931219313193141931519316193171931819319193201932119322193231932419325193261932719328193291933019331193321933319334193351933619337193381933919340193411934219343193441934519346193471934819349193501935119352193531935419355193561935719358193591936019361193621936319364193651936619367193681936919370193711937219373193741937519376193771937819379193801938119382193831938419385193861938719388193891939019391193921939319394193951939619397193981939919400194011940219403194041940519406194071940819409194101941119412194131941419415194161941719418194191942019421194221942319424194251942619427194281942919430194311943219433194341943519436194371943819439194401944119442194431944419445194461944719448194491945019451194521945319454194551945619457194581945919460194611946219463194641946519466194671946819469194701947119472194731947419475194761947719478194791948019481194821948319484194851948619487194881948919490194911949219493194941949519496194971949819499195001950119502195031950419505195061950719508195091951019511195121951319514195151951619517195181951919520195211952219523195241952519526195271952819529195301953119532195331953419535195361953719538195391954019541195421954319544195451954619547195481954919550195511955219553195541955519556195571955819559195601956119562195631956419565195661956719568195691957019571195721957319574195751957619577195781957919580195811958219583195841958519586195871958819589195901959119592195931959419595195961959719598195991960019601196021960319604196051960619607196081960919610196111961219613196141961519616196171961819619196201962119622196231962419625196261962719628196291963019631196321963319634196351963619637196381963919640196411964219643196441964519646196471964819649196501965119652196531965419655196561965719658196591966019661196621966319664196651966619667196681966919670196711967219673196741967519676196771967819679196801968119682196831968419685196861968719688196891969019691196921969319694196951969619697196981969919700197011970219703197041970519706197071970819709197101971119712197131971419715197161971719718197191972019721197221972319724197251972619727197281972919730197311973219733197341973519736197371973819739197401974119742197431974419745197461974719748197491975019751197521975319754197551975619757197581975919760197611976219763197641976519766197671976819769197701977119772197731977419775197761977719778197791978019781197821978319784197851978619787197881978919790197911979219793197941979519796197971979819799198001980119802198031980419805198061980719808198091981019811198121981319814198151981619817198181981919820198211982219823198241982519826198271982819829198301983119832198331983419835198361983719838198391984019841198421984319844198451984619847198481984919850198511985219853198541985519856198571985819859198601986119862198631986419865198661986719868198691987019871198721987319874198751987619877198781987919880198811988219883198841988519886198871988819889198901989119892198931989419895198961989719898198991990019901199021990319904199051990619907199081990919910199111991219913199141991519916199171991819919199201992119922199231992419925199261992719928199291993019931199321993319934199351993619937199381993919940199411994219943199441994519946199471994819949199501995119952199531995419955199561995719958199591996019961199621996319964199651996619967199681996919970199711997219973199741997519976199771997819979199801998119982199831998419985199861998719988199891999019991199921999319994199951999619997199981999920000200012000220003200042000520006200072000820009200102001120012200132001420015200162001720018200192002020021200222002320024200252002620027200282002920030200312003220033200342003520036200372003820039200402004120042200432004420045200462004720048200492005020051200522005320054200552005620057200582005920060200612006220063200642006520066200672006820069200702007120072200732007420075200762007720078200792008020081200822008320084200852008620087200882008920090200912009220093200942009520096200972009820099201002010120102201032010420105201062010720108201092011020111201122011320114201152011620117201182011920120201212012220123201242012520126201272012820129201302013120132201332013420135201362013720138201392014020141201422014320144201452014620147201482014920150201512015220153201542015520156201572015820159201602016120162201632016420165201662016720168201692017020171201722017320174201752017620177201782017920180201812018220183201842018520186201872018820189201902019120192201932019420195201962019720198201992020020201202022020320204202052020620207202082020920210202112021220213202142021520216202172021820219202202022120222202232022420225202262022720228202292023020231202322023320234202352023620237202382023920240202412024220243202442024520246202472024820249202502025120252202532025420255202562025720258202592026020261202622026320264202652026620267202682026920270202712027220273202742027520276202772027820279202802028120282202832028420285202862028720288202892029020291202922029320294202952029620297202982029920300203012030220303203042030520306203072030820309203102031120312203132031420315203162031720318203192032020321203222032320324203252032620327203282032920330203312033220333203342033520336203372033820339203402034120342203432034420345203462034720348203492035020351203522035320354203552035620357203582035920360203612036220363203642036520366203672036820369203702037120372203732037420375203762037720378203792038020381203822038320384203852038620387203882038920390203912039220393203942039520396203972039820399204002040120402204032040420405204062040720408204092041020411204122041320414204152041620417204182041920420204212042220423204242042520426204272042820429204302043120432204332043420435204362043720438204392044020441204422044320444204452044620447204482044920450204512045220453204542045520456204572045820459204602046120462204632046420465204662046720468204692047020471204722047320474204752047620477204782047920480204812048220483204842048520486204872048820489204902049120492204932049420495204962049720498204992050020501205022050320504205052050620507205082050920510205112051220513205142051520516205172051820519205202052120522205232052420525205262052720528205292053020531205322053320534205352053620537205382053920540205412054220543205442054520546205472054820549205502055120552205532055420555205562055720558205592056020561205622056320564205652056620567205682056920570205712057220573205742057520576205772057820579205802058120582205832058420585205862058720588205892059020591205922059320594205952059620597205982059920600206012060220603206042060520606206072060820609206102061120612206132061420615206162061720618206192062020621206222062320624206252062620627206282062920630206312063220633206342063520636206372063820639206402064120642206432064420645206462064720648206492065020651206522065320654206552065620657206582065920660206612066220663206642066520666206672066820669206702067120672206732067420675206762067720678206792068020681206822068320684206852068620687206882068920690206912069220693206942069520696206972069820699207002070120702207032070420705207062070720708207092071020711207122071320714207152071620717207182071920720207212072220723207242072520726207272072820729207302073120732207332073420735207362073720738207392074020741207422074320744207452074620747207482074920750207512075220753207542075520756207572075820759207602076120762207632076420765207662076720768207692077020771207722077320774207752077620777207782077920780207812078220783207842078520786207872078820789207902079120792207932079420795207962079720798207992080020801208022080320804208052080620807208082080920810208112081220813208142081520816208172081820819208202082120822208232082420825208262082720828208292083020831208322083320834208352083620837208382083920840208412084220843208442084520846208472084820849208502085120852208532085420855208562085720858208592086020861208622086320864208652086620867208682086920870208712087220873208742087520876208772087820879208802088120882208832088420885208862088720888208892089020891208922089320894208952089620897208982089920900209012090220903209042090520906209072090820909209102091120912209132091420915209162091720918209192092020921209222092320924209252092620927209282092920930209312093220933209342093520936209372093820939209402094120942209432094420945209462094720948209492095020951209522095320954209552095620957209582095920960209612096220963209642096520966209672096820969209702097120972209732097420975209762097720978209792098020981209822098320984209852098620987209882098920990209912099220993209942099520996209972099820999210002100121002210032100421005210062100721008210092101021011210122101321014210152101621017210182101921020210212102221023210242102521026210272102821029210302103121032210332103421035210362103721038210392104021041210422104321044210452104621047210482104921050210512105221053210542105521056210572105821059210602106121062210632106421065210662106721068210692107021071210722107321074210752107621077210782107921080210812108221083210842108521086210872108821089210902109121092210932109421095210962109721098210992110021101211022110321104211052110621107211082110921110211112111221113211142111521116211172111821119211202112121122211232112421125211262112721128211292113021131211322113321134211352113621137211382113921140211412114221143211442114521146211472114821149211502115121152211532115421155211562115721158211592116021161211622116321164211652116621167211682116921170211712117221173211742117521176211772117821179211802118121182211832118421185211862118721188211892119021191211922119321194211952119621197211982119921200212012120221203212042120521206212072120821209212102121121212212132121421215212162121721218212192122021221212222122321224212252122621227212282122921230212312123221233212342123521236212372123821239212402124121242212432124421245212462124721248212492125021251212522125321254212552125621257212582125921260212612126221263212642126521266212672126821269212702127121272212732127421275212762127721278212792128021281212822128321284212852128621287212882128921290212912129221293212942129521296212972129821299213002130121302213032130421305213062130721308213092131021311213122131321314213152131621317213182131921320213212132221323213242132521326213272132821329213302133121332213332133421335213362133721338213392134021341213422134321344213452134621347213482134921350213512135221353213542135521356213572135821359213602136121362213632136421365213662136721368213692137021371213722137321374213752137621377213782137921380213812138221383213842138521386213872138821389213902139121392213932139421395213962139721398213992140021401214022140321404214052140621407214082140921410214112141221413214142141521416214172141821419214202142121422214232142421425214262142721428214292143021431214322143321434214352143621437214382143921440214412144221443214442144521446214472144821449214502145121452214532145421455214562145721458214592146021461214622146321464214652146621467214682146921470214712147221473214742147521476214772147821479214802148121482214832148421485214862148721488214892149021491214922149321494214952149621497214982149921500215012150221503215042150521506215072150821509215102151121512215132151421515215162151721518215192152021521215222152321524215252152621527215282152921530215312153221533215342153521536215372153821539215402154121542215432154421545215462154721548215492155021551215522155321554215552155621557215582155921560215612156221563215642156521566215672156821569215702157121572215732157421575215762157721578215792158021581215822158321584215852158621587215882158921590215912159221593215942159521596215972159821599216002160121602216032160421605216062160721608216092161021611216122161321614216152161621617216182161921620216212162221623216242162521626216272162821629216302163121632216332163421635216362163721638216392164021641216422164321644216452164621647216482164921650216512165221653216542165521656216572165821659216602166121662216632166421665216662166721668216692167021671216722167321674216752167621677216782167921680216812168221683216842168521686216872168821689216902169121692216932169421695216962169721698216992170021701217022170321704217052170621707217082170921710217112171221713217142171521716217172171821719217202172121722217232172421725217262172721728217292173021731217322173321734217352173621737217382173921740217412174221743217442174521746217472174821749217502175121752217532175421755217562175721758217592176021761217622176321764217652176621767217682176921770217712177221773217742177521776217772177821779217802178121782217832178421785217862178721788217892179021791217922179321794217952179621797217982179921800218012180221803218042180521806218072180821809218102181121812218132181421815218162181721818218192182021821218222182321824218252182621827218282182921830218312183221833218342183521836218372183821839218402184121842218432184421845218462184721848218492185021851218522185321854218552185621857218582185921860218612186221863218642186521866218672186821869218702187121872218732187421875218762187721878218792188021881218822188321884218852188621887218882188921890218912189221893218942189521896218972189821899219002190121902219032190421905219062190721908219092191021911219122191321914219152191621917219182191921920219212192221923219242192521926219272192821929219302193121932219332193421935219362193721938219392194021941219422194321944219452194621947219482194921950219512195221953219542195521956219572195821959219602196121962219632196421965219662196721968219692197021971219722197321974219752197621977219782197921980219812198221983219842198521986219872198821989219902199121992219932199421995219962199721998219992200022001220022200322004220052200622007220082200922010220112201222013220142201522016220172201822019220202202122022220232202422025220262202722028220292203022031220322203322034220352203622037220382203922040220412204222043220442204522046220472204822049220502205122052220532205422055220562205722058220592206022061220622206322064220652206622067220682206922070220712207222073220742207522076220772207822079220802208122082220832208422085220862208722088220892209022091220922209322094220952209622097220982209922100221012210222103221042210522106221072210822109221102211122112221132211422115221162211722118221192212022121221222212322124221252212622127221282212922130221312213222133221342213522136221372213822139221402214122142221432214422145221462214722148221492215022151221522215322154221552215622157221582215922160221612216222163221642216522166221672216822169221702217122172221732217422175221762217722178221792218022181221822218322184221852218622187221882218922190221912219222193221942219522196221972219822199222002220122202222032220422205222062220722208222092221022211222122221322214222152221622217222182221922220222212222222223222242222522226222272222822229222302223122232222332223422235222362223722238222392224022241222422224322244222452224622247222482224922250222512225222253222542225522256222572225822259222602226122262222632226422265222662226722268222692227022271222722227322274222752227622277222782227922280222812228222283222842228522286222872228822289222902229122292222932229422295222962229722298222992230022301223022230322304223052230622307223082230922310223112231222313223142231522316223172231822319223202232122322223232232422325223262232722328223292233022331223322233322334223352233622337223382233922340223412234222343223442234522346223472234822349223502235122352223532235422355223562235722358223592236022361223622236322364223652236622367223682236922370223712237222373223742237522376223772237822379223802238122382223832238422385223862238722388223892239022391223922239322394223952239622397223982239922400224012240222403224042240522406224072240822409224102241122412224132241422415224162241722418224192242022421224222242322424224252242622427224282242922430224312243222433224342243522436224372243822439224402244122442224432244422445224462244722448224492245022451224522245322454224552245622457224582245922460224612246222463224642246522466224672246822469224702247122472224732247422475224762247722478224792248022481224822248322484224852248622487224882248922490224912249222493224942249522496224972249822499225002250122502225032250422505225062250722508225092251022511225122251322514225152251622517225182251922520225212252222523225242252522526225272252822529225302253122532225332253422535225362253722538225392254022541225422254322544225452254622547225482254922550225512255222553225542255522556225572255822559225602256122562225632256422565225662256722568225692257022571225722257322574225752257622577225782257922580225812258222583225842258522586225872258822589225902259122592225932259422595225962259722598225992260022601226022260322604226052260622607226082260922610226112261222613226142261522616226172261822619226202262122622226232262422625226262262722628226292263022631226322263322634226352263622637226382263922640226412264222643226442264522646226472264822649226502265122652226532265422655226562265722658226592266022661226622266322664226652266622667226682266922670226712267222673226742267522676226772267822679226802268122682226832268422685226862268722688226892269022691226922269322694226952269622697226982269922700227012270222703227042270522706227072270822709227102271122712227132271422715227162271722718227192272022721227222272322724227252272622727227282272922730227312273222733227342273522736227372273822739227402274122742227432274422745227462274722748227492275022751227522275322754227552275622757227582275922760227612276222763227642276522766227672276822769227702277122772227732277422775227762277722778227792278022781227822278322784227852278622787227882278922790227912279222793227942279522796227972279822799228002280122802228032280422805228062280722808228092281022811228122281322814228152281622817228182281922820228212282222823228242282522826228272282822829228302283122832228332283422835228362283722838228392284022841228422284322844228452284622847228482284922850228512285222853228542285522856228572285822859228602286122862228632286422865228662286722868228692287022871228722287322874228752287622877228782287922880228812288222883228842288522886228872288822889228902289122892228932289422895228962289722898228992290022901229022290322904229052290622907229082290922910229112291222913229142291522916229172291822919229202292122922229232292422925229262292722928229292293022931229322293322934229352293622937229382293922940229412294222943229442294522946229472294822949229502295122952229532295422955229562295722958229592296022961229622296322964229652296622967229682296922970229712297222973229742297522976229772297822979229802298122982229832298422985229862298722988229892299022991229922299322994229952299622997229982299923000230012300223003230042300523006230072300823009230102301123012230132301423015230162301723018230192302023021230222302323024230252302623027230282302923030230312303223033230342303523036230372303823039230402304123042230432304423045230462304723048230492305023051230522305323054230552305623057230582305923060230612306223063230642306523066230672306823069230702307123072230732307423075230762307723078230792308023081230822308323084230852308623087230882308923090230912309223093230942309523096230972309823099231002310123102231032310423105231062310723108231092311023111231122311323114231152311623117231182311923120231212312223123231242312523126231272312823129231302313123132231332313423135231362313723138231392314023141231422314323144231452314623147231482314923150231512315223153231542315523156231572315823159231602316123162231632316423165231662316723168231692317023171231722317323174231752317623177231782317923180231812318223183231842318523186231872318823189231902319123192231932319423195231962319723198231992320023201232022320323204232052320623207232082320923210232112321223213232142321523216232172321823219232202322123222232232322423225232262322723228232292323023231232322323323234232352323623237232382323923240232412324223243232442324523246232472324823249232502325123252232532325423255232562325723258232592326023261232622326323264232652326623267232682326923270232712327223273232742327523276232772327823279232802328123282232832328423285232862328723288232892329023291232922329323294232952329623297232982329923300233012330223303233042330523306233072330823309233102331123312233132331423315233162331723318233192332023321233222332323324233252332623327233282332923330233312333223333233342333523336233372333823339233402334123342233432334423345233462334723348233492335023351233522335323354233552335623357233582335923360233612336223363233642336523366233672336823369233702337123372233732337423375233762337723378233792338023381233822338323384233852338623387233882338923390233912339223393233942339523396233972339823399234002340123402234032340423405234062340723408234092341023411234122341323414234152341623417234182341923420234212342223423234242342523426234272342823429234302343123432234332343423435234362343723438234392344023441234422344323444234452344623447234482344923450234512345223453234542345523456234572345823459234602346123462234632346423465234662346723468234692347023471234722347323474234752347623477234782347923480234812348223483234842348523486234872348823489234902349123492234932349423495234962349723498234992350023501235022350323504235052350623507235082350923510235112351223513235142351523516235172351823519235202352123522235232352423525235262352723528235292353023531235322353323534235352353623537235382353923540235412354223543235442354523546235472354823549235502355123552235532355423555235562355723558235592356023561235622356323564235652356623567235682356923570235712357223573235742357523576235772357823579235802358123582235832358423585235862358723588235892359023591235922359323594235952359623597235982359923600236012360223603236042360523606236072360823609236102361123612236132361423615236162361723618236192362023621236222362323624236252362623627236282362923630236312363223633236342363523636236372363823639236402364123642236432364423645236462364723648236492365023651236522365323654236552365623657236582365923660236612366223663236642366523666236672366823669236702367123672236732367423675236762367723678236792368023681236822368323684236852368623687236882368923690236912369223693236942369523696236972369823699237002370123702237032370423705237062370723708237092371023711237122371323714237152371623717237182371923720237212372223723237242372523726237272372823729237302373123732237332373423735237362373723738237392374023741237422374323744237452374623747237482374923750237512375223753237542375523756237572375823759237602376123762237632376423765237662376723768237692377023771237722377323774237752377623777237782377923780237812378223783237842378523786237872378823789237902379123792237932379423795237962379723798237992380023801238022380323804238052380623807238082380923810238112381223813238142381523816238172381823819238202382123822238232382423825238262382723828238292383023831238322383323834238352383623837238382383923840238412384223843238442384523846238472384823849238502385123852238532385423855238562385723858238592386023861238622386323864238652386623867238682386923870238712387223873238742387523876238772387823879238802388123882238832388423885238862388723888238892389023891238922389323894238952389623897238982389923900239012390223903239042390523906239072390823909239102391123912239132391423915239162391723918239192392023921239222392323924239252392623927239282392923930239312393223933239342393523936239372393823939239402394123942239432394423945239462394723948239492395023951239522395323954239552395623957239582395923960239612396223963239642396523966239672396823969239702397123972239732397423975239762397723978239792398023981239822398323984239852398623987239882398923990239912399223993239942399523996239972399823999240002400124002240032400424005240062400724008240092401024011240122401324014240152401624017240182401924020240212402224023240242402524026240272402824029240302403124032240332403424035240362403724038240392404024041240422404324044240452404624047240482404924050240512405224053240542405524056240572405824059240602406124062240632406424065240662406724068240692407024071240722407324074240752407624077240782407924080240812408224083240842408524086240872408824089240902409124092240932409424095240962409724098240992410024101241022410324104241052410624107241082410924110241112411224113241142411524116241172411824119241202412124122241232412424125241262412724128241292413024131241322413324134241352413624137241382413924140241412414224143241442414524146241472414824149241502415124152241532415424155241562415724158241592416024161241622416324164241652416624167241682416924170241712417224173241742417524176241772417824179241802418124182241832418424185241862418724188241892419024191241922419324194241952419624197241982419924200242012420224203242042420524206242072420824209242102421124212242132421424215242162421724218242192422024221242222422324224242252422624227242282422924230242312423224233242342423524236242372423824239242402424124242242432424424245242462424724248242492425024251242522425324254242552425624257242582425924260242612426224263242642426524266242672426824269242702427124272242732427424275242762427724278242792428024281242822428324284242852428624287242882428924290242912429224293242942429524296242972429824299243002430124302243032430424305243062430724308243092431024311243122431324314243152431624317243182431924320243212432224323243242432524326243272432824329243302433124332243332433424335243362433724338243392434024341243422434324344243452434624347243482434924350243512435224353243542435524356243572435824359243602436124362243632436424365243662436724368243692437024371243722437324374243752437624377243782437924380243812438224383243842438524386243872438824389243902439124392243932439424395243962439724398243992440024401244022440324404244052440624407244082440924410244112441224413244142441524416244172441824419244202442124422244232442424425244262442724428244292443024431244322443324434244352443624437244382443924440244412444224443244442444524446244472444824449244502445124452244532445424455244562445724458244592446024461244622446324464244652446624467244682446924470244712447224473244742447524476244772447824479244802448124482244832448424485244862448724488244892449024491244922449324494244952449624497244982449924500245012450224503245042450524506245072450824509245102451124512245132451424515245162451724518245192452024521245222452324524245252452624527245282452924530245312453224533245342453524536245372453824539245402454124542245432454424545245462454724548245492455024551245522455324554245552455624557245582455924560245612456224563245642456524566245672456824569245702457124572245732457424575245762457724578245792458024581245822458324584245852458624587245882458924590245912459224593245942459524596245972459824599246002460124602246032460424605246062460724608246092461024611246122461324614246152461624617246182461924620246212462224623246242462524626246272462824629246302463124632246332463424635246362463724638246392464024641246422464324644246452464624647246482464924650246512465224653246542465524656246572465824659246602466124662246632466424665246662466724668246692467024671246722467324674246752467624677246782467924680246812468224683246842468524686246872468824689246902469124692246932469424695246962469724698246992470024701247022470324704247052470624707247082470924710247112471224713247142471524716247172471824719247202472124722247232472424725247262472724728247292473024731247322473324734247352473624737247382473924740247412474224743247442474524746247472474824749247502475124752247532475424755247562475724758247592476024761247622476324764247652476624767247682476924770247712477224773247742477524776247772477824779247802478124782247832478424785247862478724788247892479024791247922479324794247952479624797247982479924800248012480224803248042480524806248072480824809248102481124812248132481424815248162481724818248192482024821248222482324824248252482624827248282482924830248312483224833248342483524836248372483824839248402484124842248432484424845248462484724848248492485024851248522485324854248552485624857248582485924860248612486224863248642486524866248672486824869248702487124872248732487424875248762487724878248792488024881248822488324884248852488624887248882488924890248912489224893248942489524896248972489824899249002490124902249032490424905249062490724908249092491024911249122491324914249152491624917249182491924920249212492224923249242492524926249272492824929249302493124932249332493424935249362493724938249392494024941249422494324944249452494624947249482494924950249512495224953249542495524956249572495824959249602496124962249632496424965249662496724968249692497024971249722497324974249752497624977249782497924980249812498224983249842498524986249872498824989249902499124992249932499424995249962499724998249992500025001250022500325004250052500625007250082500925010250112501225013250142501525016250172501825019250202502125022250232502425025250262502725028250292503025031250322503325034250352503625037250382503925040250412504225043250442504525046250472504825049250502505125052250532505425055250562505725058250592506025061250622506325064250652506625067250682506925070250712507225073250742507525076250772507825079250802508125082250832508425085250862508725088250892509025091250922509325094250952509625097250982509925100251012510225103251042510525106251072510825109251102511125112251132511425115251162511725118251192512025121251222512325124251252512625127251282512925130251312513225133251342513525136251372513825139251402514125142251432514425145251462514725148251492515025151251522515325154251552515625157251582515925160251612516225163251642516525166251672516825169251702517125172251732517425175251762517725178251792518025181251822518325184251852518625187251882518925190251912519225193251942519525196251972519825199252002520125202252032520425205252062520725208252092521025211252122521325214252152521625217252182521925220252212522225223252242522525226252272522825229252302523125232252332523425235252362523725238252392524025241252422524325244252452524625247252482524925250252512525225253252542525525256252572525825259252602526125262252632526425265252662526725268252692527025271252722527325274252752527625277252782527925280252812528225283252842528525286252872528825289252902529125292252932529425295252962529725298252992530025301253022530325304253052530625307253082530925310253112531225313253142531525316253172531825319253202532125322253232532425325253262532725328253292533025331253322533325334253352533625337253382533925340253412534225343253442534525346253472534825349253502535125352253532535425355253562535725358253592536025361253622536325364253652536625367253682536925370253712537225373253742537525376253772537825379253802538125382253832538425385253862538725388253892539025391253922539325394253952539625397253982539925400254012540225403254042540525406254072540825409254102541125412254132541425415254162541725418254192542025421254222542325424254252542625427254282542925430254312543225433254342543525436254372543825439254402544125442254432544425445254462544725448254492545025451254522545325454254552545625457254582545925460254612546225463254642546525466254672546825469254702547125472254732547425475254762547725478254792548025481254822548325484254852548625487254882548925490254912549225493254942549525496254972549825499255002550125502255032550425505255062550725508255092551025511255122551325514255152551625517255182551925520255212552225523255242552525526255272552825529255302553125532255332553425535255362553725538255392554025541255422554325544255452554625547255482554925550255512555225553255542555525556255572555825559255602556125562255632556425565255662556725568255692557025571255722557325574255752557625577255782557925580255812558225583255842558525586255872558825589255902559125592255932559425595255962559725598255992560025601256022560325604256052560625607256082560925610256112561225613256142561525616256172561825619256202562125622256232562425625256262562725628256292563025631256322563325634256352563625637256382563925640256412564225643256442564525646256472564825649256502565125652256532565425655256562565725658256592566025661256622566325664256652566625667256682566925670256712567225673256742567525676256772567825679256802568125682256832568425685256862568725688256892569025691256922569325694256952569625697256982569925700257012570225703257042570525706257072570825709257102571125712257132571425715257162571725718257192572025721257222572325724257252572625727257282572925730257312573225733257342573525736257372573825739257402574125742257432574425745257462574725748257492575025751257522575325754257552575625757257582575925760257612576225763257642576525766257672576825769257702577125772257732577425775257762577725778257792578025781257822578325784257852578625787257882578925790257912579225793257942579525796257972579825799258002580125802258032580425805258062580725808258092581025811258122581325814258152581625817258182581925820258212582225823258242582525826258272582825829258302583125832258332583425835258362583725838258392584025841258422584325844258452584625847258482584925850258512585225853258542585525856258572585825859258602586125862258632586425865258662586725868258692587025871258722587325874258752587625877258782587925880258812588225883258842588525886258872588825889258902589125892258932589425895258962589725898258992590025901259022590325904259052590625907259082590925910259112591225913259142591525916259172591825919259202592125922259232592425925259262592725928259292593025931259322593325934259352593625937259382593925940259412594225943259442594525946259472594825949259502595125952259532595425955259562595725958259592596025961259622596325964259652596625967259682596925970259712597225973259742597525976259772597825979259802598125982259832598425985259862598725988259892599025991259922599325994259952599625997259982599926000260012600226003260042600526006260072600826009260102601126012260132601426015260162601726018260192602026021260222602326024260252602626027260282602926030260312603226033260342603526036260372603826039260402604126042260432604426045260462604726048260492605026051260522605326054260552605626057260582605926060260612606226063260642606526066260672606826069260702607126072260732607426075260762607726078260792608026081260822608326084260852608626087260882608926090260912609226093260942609526096260972609826099261002610126102261032610426105261062610726108261092611026111261122611326114261152611626117261182611926120261212612226123261242612526126261272612826129261302613126132261332613426135261362613726138261392614026141261422614326144261452614626147261482614926150261512615226153261542615526156261572615826159261602616126162261632616426165261662616726168261692617026171261722617326174261752617626177261782617926180261812618226183261842618526186261872618826189261902619126192261932619426195261962619726198261992620026201262022620326204262052620626207262082620926210262112621226213262142621526216262172621826219262202622126222262232622426225262262622726228262292623026231262322623326234262352623626237262382623926240262412624226243262442624526246262472624826249262502625126252262532625426255262562625726258262592626026261262622626326264262652626626267262682626926270262712627226273262742627526276262772627826279262802628126282262832628426285262862628726288262892629026291262922629326294262952629626297262982629926300263012630226303263042630526306263072630826309263102631126312263132631426315263162631726318263192632026321263222632326324263252632626327263282632926330263312633226333263342633526336263372633826339263402634126342263432634426345263462634726348263492635026351263522635326354263552635626357263582635926360263612636226363263642636526366263672636826369263702637126372263732637426375263762637726378263792638026381263822638326384263852638626387263882638926390263912639226393263942639526396263972639826399264002640126402264032640426405264062640726408264092641026411264122641326414264152641626417264182641926420264212642226423264242642526426264272642826429264302643126432264332643426435264362643726438264392644026441264422644326444264452644626447264482644926450264512645226453264542645526456264572645826459264602646126462264632646426465264662646726468264692647026471264722647326474264752647626477264782647926480264812648226483264842648526486264872648826489264902649126492264932649426495264962649726498264992650026501265022650326504265052650626507265082650926510265112651226513265142651526516265172651826519265202652126522265232652426525265262652726528265292653026531265322653326534265352653626537265382653926540265412654226543265442654526546265472654826549265502655126552265532655426555265562655726558265592656026561265622656326564265652656626567265682656926570265712657226573265742657526576265772657826579265802658126582265832658426585265862658726588265892659026591265922659326594265952659626597265982659926600266012660226603266042660526606266072660826609266102661126612266132661426615266162661726618266192662026621266222662326624266252662626627266282662926630266312663226633266342663526636266372663826639266402664126642266432664426645266462664726648266492665026651266522665326654
  1. /* build: `node build.js modules=ALL exclude=json,gestures minifier=uglifyjs` */
  2. /*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */
  3. var fabric = fabric || { version: "1.7.9" };
  4. if (typeof exports !== 'undefined') {
  5. exports.fabric = fabric;
  6. }
  7. if (typeof document !== 'undefined' && typeof window !== 'undefined') {
  8. fabric.document = document;
  9. fabric.window = window;
  10. // ensure globality even if entire library were function wrapped (as in Meteor.js packaging system)
  11. window.fabric = fabric;
  12. }
  13. else {
  14. // assume we're running under node.js when document/window are not present
  15. fabric.document = require("jsdom")
  16. .jsdom(
  17. decodeURIComponent("%3C!DOCTYPE%20html%3E%3Chtml%3E%3Chead%3E%3C%2Fhead%3E%3Cbody%3E%3C%2Fbody%3E%3C%2Fhtml%3E")
  18. );
  19. if (fabric.document.createWindow) {
  20. fabric.window = fabric.document.createWindow();
  21. } else {
  22. fabric.window = fabric.document.parentWindow;
  23. }
  24. }
  25. /**
  26. * True when in environment that supports touch events
  27. * @type boolean
  28. */
  29. fabric.isTouchSupported = "ontouchstart" in fabric.document.documentElement;
  30. /**
  31. * True when in environment that's probably Node.js
  32. * @type boolean
  33. */
  34. fabric.isLikelyNode = typeof Buffer !== 'undefined' &&
  35. typeof window === 'undefined';
  36. /* _FROM_SVG_START_ */
  37. /**
  38. * Attributes parsed from all SVG elements
  39. * @type array
  40. */
  41. fabric.SHARED_ATTRIBUTES = [
  42. "display",
  43. "transform",
  44. "fill", "fill-opacity", "fill-rule",
  45. "opacity",
  46. "stroke", "stroke-dasharray", "stroke-linecap",
  47. "stroke-linejoin", "stroke-miterlimit",
  48. "stroke-opacity", "stroke-width",
  49. "id"
  50. ];
  51. /* _FROM_SVG_END_ */
  52. /**
  53. * Pixel per Inch as a default value set to 96. Can be changed for more realistic conversion.
  54. */
  55. fabric.DPI = 96;
  56. fabric.reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)';
  57. fabric.fontPaths = { };
  58. fabric.iMatrix = [1, 0, 0, 1, 0, 0];
  59. /**
  60. * Cache Object for widths of chars in text rendering.
  61. */
  62. fabric.charWidthsCache = { };
  63. /**
  64. * Device Pixel Ratio
  65. * @see https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/HTML-canvas-guide/SettingUptheCanvas/SettingUptheCanvas.html
  66. */
  67. fabric.devicePixelRatio = fabric.window.devicePixelRatio ||
  68. fabric.window.webkitDevicePixelRatio ||
  69. fabric.window.mozDevicePixelRatio ||
  70. 1;
  71. (function() {
  72. /**
  73. * @private
  74. * @param {String} eventName
  75. * @param {Function} handler
  76. */
  77. function _removeEventListener(eventName, handler) {
  78. if (!this.__eventListeners[eventName]) {
  79. return;
  80. }
  81. var eventListener = this.__eventListeners[eventName];
  82. if (handler) {
  83. eventListener[eventListener.indexOf(handler)] = false;
  84. }
  85. else {
  86. fabric.util.array.fill(eventListener, false);
  87. }
  88. }
  89. /**
  90. * Observes specified event
  91. * @deprecated `observe` deprecated since 0.8.34 (use `on` instead)
  92. * @memberOf fabric.Observable
  93. * @alias on
  94. * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler})
  95. * @param {Function} handler Function that receives a notification when an event of the specified type occurs
  96. * @return {Self} thisArg
  97. * @chainable
  98. */
  99. function observe(eventName, handler) {
  100. if (!this.__eventListeners) {
  101. this.__eventListeners = { };
  102. }
  103. // one object with key/value pairs was passed
  104. if (arguments.length === 1) {
  105. for (var prop in eventName) {
  106. this.on(prop, eventName[prop]);
  107. }
  108. }
  109. else {
  110. if (!this.__eventListeners[eventName]) {
  111. this.__eventListeners[eventName] = [];
  112. }
  113. this.__eventListeners[eventName].push(handler);
  114. }
  115. return this;
  116. }
  117. /**
  118. * Stops event observing for a particular event handler. Calling this method
  119. * without arguments removes all handlers for all events
  120. * @deprecated `stopObserving` deprecated since 0.8.34 (use `off` instead)
  121. * @memberOf fabric.Observable
  122. * @alias off
  123. * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler})
  124. * @param {Function} handler Function to be deleted from EventListeners
  125. * @return {Self} thisArg
  126. * @chainable
  127. */
  128. function stopObserving(eventName, handler) {
  129. if (!this.__eventListeners) {
  130. return;
  131. }
  132. // remove all key/value pairs (event name -> event handler)
  133. if (arguments.length === 0) {
  134. for (eventName in this.__eventListeners) {
  135. _removeEventListener.call(this, eventName);
  136. }
  137. }
  138. // one object with key/value pairs was passed
  139. else if (arguments.length === 1 && typeof arguments[0] === 'object') {
  140. for (var prop in eventName) {
  141. _removeEventListener.call(this, prop, eventName[prop]);
  142. }
  143. }
  144. else {
  145. _removeEventListener.call(this, eventName, handler);
  146. }
  147. return this;
  148. }
  149. /**
  150. * Fires event with an optional options object
  151. * @deprecated `fire` deprecated since 1.0.7 (use `trigger` instead)
  152. * @memberOf fabric.Observable
  153. * @alias trigger
  154. * @param {String} eventName Event name to fire
  155. * @param {Object} [options] Options object
  156. * @return {Self} thisArg
  157. * @chainable
  158. */
  159. function fire(eventName, options) {
  160. if (!this.__eventListeners) {
  161. return;
  162. }
  163. var listenersForEvent = this.__eventListeners[eventName];
  164. if (!listenersForEvent) {
  165. return;
  166. }
  167. for (var i = 0, len = listenersForEvent.length; i < len; i++) {
  168. listenersForEvent[i] && listenersForEvent[i].call(this, options || { });
  169. }
  170. this.__eventListeners[eventName] = listenersForEvent.filter(function(value) {
  171. return value !== false;
  172. });
  173. return this;
  174. }
  175. /**
  176. * @namespace fabric.Observable
  177. * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#events}
  178. * @see {@link http://fabricjs.com/events|Events demo}
  179. */
  180. fabric.Observable = {
  181. observe: observe,
  182. stopObserving: stopObserving,
  183. fire: fire,
  184. on: observe,
  185. off: stopObserving,
  186. trigger: fire
  187. };
  188. })();
  189. /**
  190. * @namespace fabric.Collection
  191. */
  192. fabric.Collection = {
  193. _objects: [],
  194. /**
  195. * Adds objects to collection, Canvas or Group, then renders canvas
  196. * (if `renderOnAddRemove` is not `false`).
  197. * in case of Group no changes to bounding box are made.
  198. * Objects should be instances of (or inherit from) fabric.Object
  199. * @param {...fabric.Object} object Zero or more fabric instances
  200. * @return {Self} thisArg
  201. * @chainable
  202. */
  203. add: function () {
  204. this._objects.push.apply(this._objects, arguments);
  205. if (this._onObjectAdded) {
  206. for (var i = 0, length = arguments.length; i < length; i++) {
  207. this._onObjectAdded(arguments[i]);
  208. }
  209. }
  210. this.renderOnAddRemove && this.renderAll();
  211. return this;
  212. },
  213. /**
  214. * Inserts an object into collection at specified index, then renders canvas (if `renderOnAddRemove` is not `false`)
  215. * An object should be an instance of (or inherit from) fabric.Object
  216. * @param {Object} object Object to insert
  217. * @param {Number} index Index to insert object at
  218. * @param {Boolean} nonSplicing When `true`, no splicing (shifting) of objects occurs
  219. * @return {Self} thisArg
  220. * @chainable
  221. */
  222. insertAt: function (object, index, nonSplicing) {
  223. var objects = this.getObjects();
  224. if (nonSplicing) {
  225. objects[index] = object;
  226. }
  227. else {
  228. objects.splice(index, 0, object);
  229. }
  230. this._onObjectAdded && this._onObjectAdded(object);
  231. this.renderOnAddRemove && this.renderAll();
  232. return this;
  233. },
  234. /**
  235. * Removes objects from a collection, then renders canvas (if `renderOnAddRemove` is not `false`)
  236. * @param {...fabric.Object} object Zero or more fabric instances
  237. * @return {Self} thisArg
  238. * @chainable
  239. */
  240. remove: function() {
  241. var objects = this.getObjects(),
  242. index, somethingRemoved = false;
  243. for (var i = 0, length = arguments.length; i < length; i++) {
  244. index = objects.indexOf(arguments[i]);
  245. // only call onObjectRemoved if an object was actually removed
  246. if (index !== -1) {
  247. somethingRemoved = true;
  248. objects.splice(index, 1);
  249. this._onObjectRemoved && this._onObjectRemoved(arguments[i]);
  250. }
  251. }
  252. this.renderOnAddRemove && somethingRemoved && this.renderAll();
  253. return this;
  254. },
  255. /**
  256. * Executes given function for each object in this group
  257. * @param {Function} callback
  258. * Callback invoked with current object as first argument,
  259. * index - as second and an array of all objects - as third.
  260. * Callback is invoked in a context of Global Object (e.g. `window`)
  261. * when no `context` argument is given
  262. *
  263. * @param {Object} context Context (aka thisObject)
  264. * @return {Self} thisArg
  265. * @chainable
  266. */
  267. forEachObject: function(callback, context) {
  268. var objects = this.getObjects();
  269. for (var i = 0, len = objects.length; i < len; i++) {
  270. callback.call(context, objects[i], i, objects);
  271. }
  272. return this;
  273. },
  274. /**
  275. * Returns an array of children objects of this instance
  276. * Type parameter introduced in 1.3.10
  277. * @param {String} [type] When specified, only objects of this type are returned
  278. * @return {Array}
  279. */
  280. getObjects: function(type) {
  281. if (typeof type === 'undefined') {
  282. return this._objects;
  283. }
  284. return this._objects.filter(function(o) {
  285. return o.type === type;
  286. });
  287. },
  288. /**
  289. * Returns object at specified index
  290. * @param {Number} index
  291. * @return {Self} thisArg
  292. */
  293. item: function (index) {
  294. return this.getObjects()[index];
  295. },
  296. /**
  297. * Returns true if collection contains no objects
  298. * @return {Boolean} true if collection is empty
  299. */
  300. isEmpty: function () {
  301. return this.getObjects().length === 0;
  302. },
  303. /**
  304. * Returns a size of a collection (i.e: length of an array containing its objects)
  305. * @return {Number} Collection size
  306. */
  307. size: function() {
  308. return this.getObjects().length;
  309. },
  310. /**
  311. * Returns true if collection contains an object
  312. * @param {Object} object Object to check against
  313. * @return {Boolean} `true` if collection contains an object
  314. */
  315. contains: function(object) {
  316. return this.getObjects().indexOf(object) > -1;
  317. },
  318. /**
  319. * Returns number representation of a collection complexity
  320. * @return {Number} complexity
  321. */
  322. complexity: function () {
  323. return this.getObjects().reduce(function (memo, current) {
  324. memo += current.complexity ? current.complexity() : 0;
  325. return memo;
  326. }, 0);
  327. }
  328. };
  329. /**
  330. * @namespace fabric.CommonMethods
  331. */
  332. fabric.CommonMethods = {
  333. /**
  334. * Sets object's properties from options
  335. * @param {Object} [options] Options object
  336. */
  337. _setOptions: function(options) {
  338. for (var prop in options) {
  339. this.set(prop, options[prop]);
  340. }
  341. },
  342. /**
  343. * @private
  344. * @param {Object} [filler] Options object
  345. * @param {String} [property] property to set the Gradient to
  346. */
  347. _initGradient: function(filler, property) {
  348. if (filler && filler.colorStops && !(filler instanceof fabric.Gradient)) {
  349. this.set(property, new fabric.Gradient(filler));
  350. }
  351. },
  352. /**
  353. * @private
  354. * @param {Object} [filler] Options object
  355. * @param {String} [property] property to set the Pattern to
  356. * @param {Function} [callback] callback to invoke after pattern load
  357. */
  358. _initPattern: function(filler, property, callback) {
  359. if (filler && filler.source && !(filler instanceof fabric.Pattern)) {
  360. this.set(property, new fabric.Pattern(filler, callback));
  361. }
  362. else {
  363. callback && callback();
  364. }
  365. },
  366. /**
  367. * @private
  368. * @param {Object} [options] Options object
  369. */
  370. _initClipping: function(options) {
  371. if (!options.clipTo || typeof options.clipTo !== 'string') {
  372. return;
  373. }
  374. var functionBody = fabric.util.getFunctionBody(options.clipTo);
  375. if (typeof functionBody !== 'undefined') {
  376. this.clipTo = new Function('ctx', functionBody);
  377. }
  378. },
  379. /**
  380. * @private
  381. */
  382. _setObject: function(obj) {
  383. for (var prop in obj) {
  384. this._set(prop, obj[prop]);
  385. }
  386. },
  387. /**
  388. * Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`.
  389. * @param {String|Object} key Property name or object (if object, iterate over the object properties)
  390. * @param {Object|Function} value Property value (if function, the value is passed into it and its return value is used as a new one)
  391. * @return {fabric.Object} thisArg
  392. * @chainable
  393. */
  394. set: function(key, value) {
  395. if (typeof key === 'object') {
  396. this._setObject(key);
  397. }
  398. else {
  399. if (typeof value === 'function' && key !== 'clipTo') {
  400. this._set(key, value(this.get(key)));
  401. }
  402. else {
  403. this._set(key, value);
  404. }
  405. }
  406. return this;
  407. },
  408. _set: function(key, value) {
  409. this[key] = value;
  410. },
  411. /**
  412. * Toggles specified property from `true` to `false` or from `false` to `true`
  413. * @param {String} property Property to toggle
  414. * @return {fabric.Object} thisArg
  415. * @chainable
  416. */
  417. toggle: function(property) {
  418. var value = this.get(property);
  419. if (typeof value === 'boolean') {
  420. this.set(property, !value);
  421. }
  422. return this;
  423. },
  424. /**
  425. * Basic getter
  426. * @param {String} property Property name
  427. * @return {*} value of a property
  428. */
  429. get: function(property) {
  430. return this[property];
  431. }
  432. };
  433. (function(global) {
  434. var sqrt = Math.sqrt,
  435. atan2 = Math.atan2,
  436. pow = Math.pow,
  437. abs = Math.abs,
  438. PiBy180 = Math.PI / 180;
  439. /**
  440. * @namespace fabric.util
  441. */
  442. fabric.util = {
  443. /**
  444. * Removes value from an array.
  445. * Presence of value (and its position in an array) is determined via `Array.prototype.indexOf`
  446. * @static
  447. * @memberOf fabric.util
  448. * @param {Array} array
  449. * @param {*} value
  450. * @return {Array} original array
  451. */
  452. removeFromArray: function(array, value) {
  453. var idx = array.indexOf(value);
  454. if (idx !== -1) {
  455. array.splice(idx, 1);
  456. }
  457. return array;
  458. },
  459. /**
  460. * Returns random number between 2 specified ones.
  461. * @static
  462. * @memberOf fabric.util
  463. * @param {Number} min lower limit
  464. * @param {Number} max upper limit
  465. * @return {Number} random value (between min and max)
  466. */
  467. getRandomInt: function(min, max) {
  468. return Math.floor(Math.random() * (max - min + 1)) + min;
  469. },
  470. /**
  471. * Transforms degrees to radians.
  472. * @static
  473. * @memberOf fabric.util
  474. * @param {Number} degrees value in degrees
  475. * @return {Number} value in radians
  476. */
  477. degreesToRadians: function(degrees) {
  478. return degrees * PiBy180;
  479. },
  480. /**
  481. * Transforms radians to degrees.
  482. * @static
  483. * @memberOf fabric.util
  484. * @param {Number} radians value in radians
  485. * @return {Number} value in degrees
  486. */
  487. radiansToDegrees: function(radians) {
  488. return radians / PiBy180;
  489. },
  490. /**
  491. * Rotates `point` around `origin` with `radians`
  492. * @static
  493. * @memberOf fabric.util
  494. * @param {fabric.Point} point The point to rotate
  495. * @param {fabric.Point} origin The origin of the rotation
  496. * @param {Number} radians The radians of the angle for the rotation
  497. * @return {fabric.Point} The new rotated point
  498. */
  499. rotatePoint: function(point, origin, radians) {
  500. point.subtractEquals(origin);
  501. var v = fabric.util.rotateVector(point, radians);
  502. return new fabric.Point(v.x, v.y).addEquals(origin);
  503. },
  504. /**
  505. * Rotates `vector` with `radians`
  506. * @static
  507. * @memberOf fabric.util
  508. * @param {Object} vector The vector to rotate (x and y)
  509. * @param {Number} radians The radians of the angle for the rotation
  510. * @return {Object} The new rotated point
  511. */
  512. rotateVector: function(vector, radians) {
  513. var sin = Math.sin(radians),
  514. cos = Math.cos(radians),
  515. rx = vector.x * cos - vector.y * sin,
  516. ry = vector.x * sin + vector.y * cos;
  517. return {
  518. x: rx,
  519. y: ry
  520. };
  521. },
  522. /**
  523. * Apply transform t to point p
  524. * @static
  525. * @memberOf fabric.util
  526. * @param {fabric.Point} p The point to transform
  527. * @param {Array} t The transform
  528. * @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied
  529. * @return {fabric.Point} The transformed point
  530. */
  531. transformPoint: function(p, t, ignoreOffset) {
  532. if (ignoreOffset) {
  533. return new fabric.Point(
  534. t[0] * p.x + t[2] * p.y,
  535. t[1] * p.x + t[3] * p.y
  536. );
  537. }
  538. return new fabric.Point(
  539. t[0] * p.x + t[2] * p.y + t[4],
  540. t[1] * p.x + t[3] * p.y + t[5]
  541. );
  542. },
  543. /**
  544. * Returns coordinates of points's bounding rectangle (left, top, width, height)
  545. * @param {Array} points 4 points array
  546. * @return {Object} Object with left, top, width, height properties
  547. */
  548. makeBoundingBoxFromPoints: function(points) {
  549. var xPoints = [points[0].x, points[1].x, points[2].x, points[3].x],
  550. minX = fabric.util.array.min(xPoints),
  551. maxX = fabric.util.array.max(xPoints),
  552. width = Math.abs(minX - maxX),
  553. yPoints = [points[0].y, points[1].y, points[2].y, points[3].y],
  554. minY = fabric.util.array.min(yPoints),
  555. maxY = fabric.util.array.max(yPoints),
  556. height = Math.abs(minY - maxY);
  557. return {
  558. left: minX,
  559. top: minY,
  560. width: width,
  561. height: height
  562. };
  563. },
  564. /**
  565. * Invert transformation t
  566. * @static
  567. * @memberOf fabric.util
  568. * @param {Array} t The transform
  569. * @return {Array} The inverted transform
  570. */
  571. invertTransform: function(t) {
  572. var a = 1 / (t[0] * t[3] - t[1] * t[2]),
  573. r = [a * t[3], -a * t[1], -a * t[2], a * t[0]],
  574. o = fabric.util.transformPoint({ x: t[4], y: t[5] }, r, true);
  575. r[4] = -o.x;
  576. r[5] = -o.y;
  577. return r;
  578. },
  579. /**
  580. * A wrapper around Number#toFixed, which contrary to native method returns number, not string.
  581. * @static
  582. * @memberOf fabric.util
  583. * @param {Number|String} number number to operate on
  584. * @param {Number} fractionDigits number of fraction digits to "leave"
  585. * @return {Number}
  586. */
  587. toFixed: function(number, fractionDigits) {
  588. return parseFloat(Number(number).toFixed(fractionDigits));
  589. },
  590. /**
  591. * Converts from attribute value to pixel value if applicable.
  592. * Returns converted pixels or original value not converted.
  593. * @param {Number|String} value number to operate on
  594. * @param {Number} fontSize
  595. * @return {Number|String}
  596. */
  597. parseUnit: function(value, fontSize) {
  598. var unit = /\D{0,2}$/.exec(value),
  599. number = parseFloat(value);
  600. if (!fontSize) {
  601. fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE;
  602. }
  603. switch (unit[0]) {
  604. case 'mm':
  605. return number * fabric.DPI / 25.4;
  606. case 'cm':
  607. return number * fabric.DPI / 2.54;
  608. case 'in':
  609. return number * fabric.DPI;
  610. case 'pt':
  611. return number * fabric.DPI / 72; // or * 4 / 3
  612. case 'pc':
  613. return number * fabric.DPI / 72 * 12; // or * 16
  614. case 'em':
  615. return number * fontSize;
  616. default:
  617. return number;
  618. }
  619. },
  620. /**
  621. * Function which always returns `false`.
  622. * @static
  623. * @memberOf fabric.util
  624. * @return {Boolean}
  625. */
  626. falseFunction: function() {
  627. return false;
  628. },
  629. /**
  630. * Returns klass "Class" object of given namespace
  631. * @memberOf fabric.util
  632. * @param {String} type Type of object (eg. 'circle')
  633. * @param {String} namespace Namespace to get klass "Class" object from
  634. * @return {Object} klass "Class"
  635. */
  636. getKlass: function(type, namespace) {
  637. // capitalize first letter only
  638. type = fabric.util.string.camelize(type.charAt(0).toUpperCase() + type.slice(1));
  639. return fabric.util.resolveNamespace(namespace)[type];
  640. },
  641. /**
  642. * Returns object of given namespace
  643. * @memberOf fabric.util
  644. * @param {String} namespace Namespace string e.g. 'fabric.Image.filter' or 'fabric'
  645. * @return {Object} Object for given namespace (default fabric)
  646. */
  647. resolveNamespace: function(namespace) {
  648. if (!namespace) {
  649. return fabric;
  650. }
  651. var parts = namespace.split('.'),
  652. len = parts.length, i,
  653. obj = global || fabric.window;
  654. for (i = 0; i < len; ++i) {
  655. obj = obj[parts[i]];
  656. }
  657. return obj;
  658. },
  659. /**
  660. * Loads image element from given url and passes it to a callback
  661. * @memberOf fabric.util
  662. * @param {String} url URL representing an image
  663. * @param {Function} callback Callback; invoked with loaded image
  664. * @param {*} [context] Context to invoke callback in
  665. * @param {Object} [crossOrigin] crossOrigin value to set image element to
  666. */
  667. loadImage: function(url, callback, context, crossOrigin) {
  668. if (!url) {
  669. callback && callback.call(context, url);
  670. return;
  671. }
  672. var img = fabric.util.createImage();
  673. /** @ignore */
  674. img.onload = function () {
  675. callback && callback.call(context, img);
  676. img = img.onload = img.onerror = null;
  677. };
  678. /** @ignore */
  679. img.onerror = function() {
  680. fabric.log('Error loading ' + img.src);
  681. callback && callback.call(context, null, true);
  682. img = img.onload = img.onerror = null;
  683. };
  684. // data-urls appear to be buggy with crossOrigin
  685. // https://github.com/kangax/fabric.js/commit/d0abb90f1cd5c5ef9d2a94d3fb21a22330da3e0a#commitcomment-4513767
  686. // see https://code.google.com/p/chromium/issues/detail?id=315152
  687. // https://bugzilla.mozilla.org/show_bug.cgi?id=935069
  688. if (url.indexOf('data') !== 0 && crossOrigin) {
  689. img.crossOrigin = crossOrigin;
  690. }
  691. img.src = url;
  692. },
  693. /**
  694. * Creates corresponding fabric instances from their object representations
  695. * @static
  696. * @memberOf fabric.util
  697. * @param {Array} objects Objects to enliven
  698. * @param {Function} callback Callback to invoke when all objects are created
  699. * @param {String} namespace Namespace to get klass "Class" object from
  700. * @param {Function} reviver Method for further parsing of object elements,
  701. * called after each fabric object created.
  702. */
  703. enlivenObjects: function(objects, callback, namespace, reviver) {
  704. objects = objects || [];
  705. function onLoaded() {
  706. if (++numLoadedObjects === numTotalObjects) {
  707. callback && callback(enlivenedObjects);
  708. }
  709. }
  710. var enlivenedObjects = [],
  711. numLoadedObjects = 0,
  712. numTotalObjects = objects.length,
  713. forceAsync = true;
  714. if (!numTotalObjects) {
  715. callback && callback(enlivenedObjects);
  716. return;
  717. }
  718. objects.forEach(function (o, index) {
  719. // if sparse array
  720. if (!o || !o.type) {
  721. onLoaded();
  722. return;
  723. }
  724. var klass = fabric.util.getKlass(o.type, namespace);
  725. klass.fromObject(o, function (obj, error) {
  726. error || (enlivenedObjects[index] = obj);
  727. reviver && reviver(o, obj, error);
  728. onLoaded();
  729. }, forceAsync);
  730. });
  731. },
  732. /**
  733. * Create and wait for loading of patterns
  734. * @static
  735. * @memberOf fabric.util
  736. * @param {Array} objects Objects to enliven
  737. * @param {Function} callback Callback to invoke when all objects are created
  738. * @param {String} namespace Namespace to get klass "Class" object from
  739. * @param {Function} reviver Method for further parsing of object elements,
  740. * called after each fabric object created.
  741. */
  742. enlivenPatterns: function(patterns, callback) {
  743. patterns = patterns || [];
  744. function onLoaded() {
  745. if (++numLoadedPatterns === numPatterns) {
  746. callback && callback(enlivenedPatterns);
  747. }
  748. }
  749. var enlivenedPatterns = [],
  750. numLoadedPatterns = 0,
  751. numPatterns = patterns.length;
  752. if (!numPatterns) {
  753. callback && callback(enlivenedPatterns);
  754. return;
  755. }
  756. patterns.forEach(function (p, index) {
  757. if (p && p.source) {
  758. new fabric.Pattern(p, function(pattern) {
  759. enlivenedPatterns[index] = pattern;
  760. onLoaded();
  761. });
  762. }
  763. else {
  764. enlivenedPatterns[index] = p;
  765. onLoaded();
  766. }
  767. });
  768. },
  769. /**
  770. * Groups SVG elements (usually those retrieved from SVG document)
  771. * @static
  772. * @memberOf fabric.util
  773. * @param {Array} elements SVG elements to group
  774. * @param {Object} [options] Options object
  775. * @param {String} path Value to set sourcePath to
  776. * @return {fabric.Object|fabric.PathGroup}
  777. */
  778. groupSVGElements: function(elements, options, path) {
  779. var object;
  780. object = new fabric.PathGroup(elements, options);
  781. if (typeof path !== 'undefined') {
  782. object.setSourcePath(path);
  783. }
  784. return object;
  785. },
  786. /**
  787. * Populates an object with properties of another object
  788. * @static
  789. * @memberOf fabric.util
  790. * @param {Object} source Source object
  791. * @param {Object} destination Destination object
  792. * @return {Array} properties Propertie names to include
  793. */
  794. populateWithProperties: function(source, destination, properties) {
  795. if (properties && Object.prototype.toString.call(properties) === '[object Array]') {
  796. for (var i = 0, len = properties.length; i < len; i++) {
  797. if (properties[i] in source) {
  798. destination[properties[i]] = source[properties[i]];
  799. }
  800. }
  801. }
  802. },
  803. /**
  804. * Draws a dashed line between two points
  805. *
  806. * This method is used to draw dashed line around selection area.
  807. * See <a href="http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas">dotted stroke in canvas</a>
  808. *
  809. * @param {CanvasRenderingContext2D} ctx context
  810. * @param {Number} x start x coordinate
  811. * @param {Number} y start y coordinate
  812. * @param {Number} x2 end x coordinate
  813. * @param {Number} y2 end y coordinate
  814. * @param {Array} da dash array pattern
  815. */
  816. drawDashedLine: function(ctx, x, y, x2, y2, da) {
  817. var dx = x2 - x,
  818. dy = y2 - y,
  819. len = sqrt(dx * dx + dy * dy),
  820. rot = atan2(dy, dx),
  821. dc = da.length,
  822. di = 0,
  823. draw = true;
  824. ctx.save();
  825. ctx.translate(x, y);
  826. ctx.moveTo(0, 0);
  827. ctx.rotate(rot);
  828. x = 0;
  829. while (len > x) {
  830. x += da[di++ % dc];
  831. if (x > len) {
  832. x = len;
  833. }
  834. ctx[draw ? 'lineTo' : 'moveTo'](x, 0);
  835. draw = !draw;
  836. }
  837. ctx.restore();
  838. },
  839. /**
  840. * Creates canvas element and initializes it via excanvas if necessary
  841. * @static
  842. * @memberOf fabric.util
  843. * @param {CanvasElement} [canvasEl] optional canvas element to initialize;
  844. * when not given, element is created implicitly
  845. * @return {CanvasElement} initialized canvas element
  846. */
  847. createCanvasElement: function(canvasEl) {
  848. canvasEl || (canvasEl = fabric.document.createElement('canvas'));
  849. /* eslint-disable camelcase */
  850. if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') {
  851. G_vmlCanvasManager.initElement(canvasEl);
  852. }
  853. /* eslint-enable camelcase */
  854. return canvasEl;
  855. },
  856. /**
  857. * Creates image element (works on client and node)
  858. * @static
  859. * @memberOf fabric.util
  860. * @return {HTMLImageElement} HTML image element
  861. */
  862. createImage: function() {
  863. return fabric.isLikelyNode
  864. ? new (require('canvas').Image)()
  865. : fabric.document.createElement('img');
  866. },
  867. /**
  868. * Creates accessors (getXXX, setXXX) for a "class", based on "stateProperties" array
  869. * @static
  870. * @memberOf fabric.util
  871. * @param {Object} klass "Class" to create accessors for
  872. */
  873. createAccessors: function(klass) {
  874. var proto = klass.prototype, i, propName,
  875. capitalizedPropName, setterName, getterName;
  876. for (i = proto.stateProperties.length; i--; ) {
  877. propName = proto.stateProperties[i];
  878. capitalizedPropName = propName.charAt(0).toUpperCase() + propName.slice(1);
  879. setterName = 'set' + capitalizedPropName;
  880. getterName = 'get' + capitalizedPropName;
  881. // using `new Function` for better introspection
  882. if (!proto[getterName]) {
  883. proto[getterName] = (function(property) {
  884. return new Function('return this.get("' + property + '")');
  885. })(propName);
  886. }
  887. if (!proto[setterName]) {
  888. proto[setterName] = (function(property) {
  889. return new Function('value', 'return this.set("' + property + '", value)');
  890. })(propName);
  891. }
  892. }
  893. },
  894. /**
  895. * @static
  896. * @memberOf fabric.util
  897. * @param {fabric.Object} receiver Object implementing `clipTo` method
  898. * @param {CanvasRenderingContext2D} ctx Context to clip
  899. */
  900. clipContext: function(receiver, ctx) {
  901. ctx.save();
  902. ctx.beginPath();
  903. receiver.clipTo(ctx);
  904. ctx.clip();
  905. },
  906. /**
  907. * Multiply matrix A by matrix B to nest transformations
  908. * @static
  909. * @memberOf fabric.util
  910. * @param {Array} a First transformMatrix
  911. * @param {Array} b Second transformMatrix
  912. * @param {Boolean} is2x2 flag to multiply matrices as 2x2 matrices
  913. * @return {Array} The product of the two transform matrices
  914. */
  915. multiplyTransformMatrices: function(a, b, is2x2) {
  916. // Matrix multiply a * b
  917. return [
  918. a[0] * b[0] + a[2] * b[1],
  919. a[1] * b[0] + a[3] * b[1],
  920. a[0] * b[2] + a[2] * b[3],
  921. a[1] * b[2] + a[3] * b[3],
  922. is2x2 ? 0 : a[0] * b[4] + a[2] * b[5] + a[4],
  923. is2x2 ? 0 : a[1] * b[4] + a[3] * b[5] + a[5]
  924. ];
  925. },
  926. /**
  927. * Decomposes standard 2x2 matrix into transform componentes
  928. * @static
  929. * @memberOf fabric.util
  930. * @param {Array} a transformMatrix
  931. * @return {Object} Components of transform
  932. */
  933. qrDecompose: function(a) {
  934. var angle = atan2(a[1], a[0]),
  935. denom = pow(a[0], 2) + pow(a[1], 2),
  936. scaleX = sqrt(denom),
  937. scaleY = (a[0] * a[3] - a[2] * a [1]) / scaleX,
  938. skewX = atan2(a[0] * a[2] + a[1] * a [3], denom);
  939. return {
  940. angle: angle / PiBy180,
  941. scaleX: scaleX,
  942. scaleY: scaleY,
  943. skewX: skewX / PiBy180,
  944. skewY: 0,
  945. translateX: a[4],
  946. translateY: a[5]
  947. };
  948. },
  949. customTransformMatrix: function(scaleX, scaleY, skewX) {
  950. var skewMatrixX = [1, 0, abs(Math.tan(skewX * PiBy180)), 1],
  951. scaleMatrix = [abs(scaleX), 0, 0, abs(scaleY)];
  952. return fabric.util.multiplyTransformMatrices(scaleMatrix, skewMatrixX, true);
  953. },
  954. resetObjectTransform: function (target) {
  955. target.scaleX = 1;
  956. target.scaleY = 1;
  957. target.skewX = 0;
  958. target.skewY = 0;
  959. target.flipX = false;
  960. target.flipY = false;
  961. target.setAngle(0);
  962. },
  963. /**
  964. * Returns string representation of function body
  965. * @param {Function} fn Function to get body of
  966. * @return {String} Function body
  967. */
  968. getFunctionBody: function(fn) {
  969. return (String(fn).match(/function[^{]*\{([\s\S]*)\}/) || {})[1];
  970. },
  971. /**
  972. * Returns true if context has transparent pixel
  973. * at specified location (taking tolerance into account)
  974. * @param {CanvasRenderingContext2D} ctx context
  975. * @param {Number} x x coordinate
  976. * @param {Number} y y coordinate
  977. * @param {Number} tolerance Tolerance
  978. */
  979. isTransparent: function(ctx, x, y, tolerance) {
  980. // If tolerance is > 0 adjust start coords to take into account.
  981. // If moves off Canvas fix to 0
  982. if (tolerance > 0) {
  983. if (x > tolerance) {
  984. x -= tolerance;
  985. }
  986. else {
  987. x = 0;
  988. }
  989. if (y > tolerance) {
  990. y -= tolerance;
  991. }
  992. else {
  993. y = 0;
  994. }
  995. }
  996. var _isTransparent = true, i, temp,
  997. imageData = ctx.getImageData(x, y, (tolerance * 2) || 1, (tolerance * 2) || 1),
  998. l = imageData.data.length;
  999. // Split image data - for tolerance > 1, pixelDataSize = 4;
  1000. for (i = 3; i < l; i += 4) {
  1001. temp = imageData.data[i];
  1002. _isTransparent = temp <= 0;
  1003. if (_isTransparent === false) {
  1004. break; // Stop if colour found
  1005. }
  1006. }
  1007. imageData = null;
  1008. return _isTransparent;
  1009. },
  1010. /**
  1011. * Parse preserveAspectRatio attribute from element
  1012. * @param {string} attribute to be parsed
  1013. * @return {Object} an object containing align and meetOrSlice attribute
  1014. */
  1015. parsePreserveAspectRatioAttribute: function(attribute) {
  1016. var meetOrSlice = 'meet', alignX = 'Mid', alignY = 'Mid',
  1017. aspectRatioAttrs = attribute.split(' '), align;
  1018. if (aspectRatioAttrs && aspectRatioAttrs.length) {
  1019. meetOrSlice = aspectRatioAttrs.pop();
  1020. if (meetOrSlice !== 'meet' && meetOrSlice !== 'slice') {
  1021. align = meetOrSlice;
  1022. meetOrSlice = 'meet';
  1023. }
  1024. else if (aspectRatioAttrs.length) {
  1025. align = aspectRatioAttrs.pop();
  1026. }
  1027. }
  1028. //divide align in alignX and alignY
  1029. alignX = align !== 'none' ? align.slice(1, 4) : 'none';
  1030. alignY = align !== 'none' ? align.slice(5, 8) : 'none';
  1031. return {
  1032. meetOrSlice: meetOrSlice,
  1033. alignX: alignX,
  1034. alignY: alignY
  1035. };
  1036. },
  1037. /**
  1038. * Clear char widths cache for a font family.
  1039. * @memberOf fabric.util
  1040. * @param {String} [fontFamily] font family to clear
  1041. */
  1042. clearFabricFontCache: function(fontFamily) {
  1043. if (!fontFamily) {
  1044. fabric.charWidthsCache = { };
  1045. }
  1046. else if (fabric.charWidthsCache[fontFamily]) {
  1047. delete fabric.charWidthsCache[fontFamily];
  1048. }
  1049. }
  1050. };
  1051. })(typeof exports !== 'undefined' ? exports : this);
  1052. (function() {
  1053. var arcToSegmentsCache = { },
  1054. segmentToBezierCache = { },
  1055. boundsOfCurveCache = { },
  1056. _join = Array.prototype.join;
  1057. /* Adapted from http://dxr.mozilla.org/mozilla-central/source/content/svg/content/src/nsSVGPathDataParser.cpp
  1058. * by Andrea Bogazzi code is under MPL. if you don't have a copy of the license you can take it here
  1059. * http://mozilla.org/MPL/2.0/
  1060. */
  1061. function arcToSegments(toX, toY, rx, ry, large, sweep, rotateX) {
  1062. var argsString = _join.call(arguments);
  1063. if (arcToSegmentsCache[argsString]) {
  1064. return arcToSegmentsCache[argsString];
  1065. }
  1066. var PI = Math.PI, th = rotateX * PI / 180,
  1067. sinTh = Math.sin(th),
  1068. cosTh = Math.cos(th),
  1069. fromX = 0, fromY = 0;
  1070. rx = Math.abs(rx);
  1071. ry = Math.abs(ry);
  1072. var px = -cosTh * toX * 0.5 - sinTh * toY * 0.5,
  1073. py = -cosTh * toY * 0.5 + sinTh * toX * 0.5,
  1074. rx2 = rx * rx, ry2 = ry * ry, py2 = py * py, px2 = px * px,
  1075. pl = rx2 * ry2 - rx2 * py2 - ry2 * px2,
  1076. root = 0;
  1077. if (pl < 0) {
  1078. var s = Math.sqrt(1 - pl / (rx2 * ry2));
  1079. rx *= s;
  1080. ry *= s;
  1081. }
  1082. else {
  1083. root = (large === sweep ? -1.0 : 1.0) *
  1084. Math.sqrt( pl / (rx2 * py2 + ry2 * px2));
  1085. }
  1086. var cx = root * rx * py / ry,
  1087. cy = -root * ry * px / rx,
  1088. cx1 = cosTh * cx - sinTh * cy + toX * 0.5,
  1089. cy1 = sinTh * cx + cosTh * cy + toY * 0.5,
  1090. mTheta = calcVectorAngle(1, 0, (px - cx) / rx, (py - cy) / ry),
  1091. dtheta = calcVectorAngle((px - cx) / rx, (py - cy) / ry, (-px - cx) / rx, (-py - cy) / ry);
  1092. if (sweep === 0 && dtheta > 0) {
  1093. dtheta -= 2 * PI;
  1094. }
  1095. else if (sweep === 1 && dtheta < 0) {
  1096. dtheta += 2 * PI;
  1097. }
  1098. // Convert into cubic bezier segments <= 90deg
  1099. var segments = Math.ceil(Math.abs(dtheta / PI * 2)),
  1100. result = [], mDelta = dtheta / segments,
  1101. mT = 8 / 3 * Math.sin(mDelta / 4) * Math.sin(mDelta / 4) / Math.sin(mDelta / 2),
  1102. th3 = mTheta + mDelta;
  1103. for (var i = 0; i < segments; i++) {
  1104. result[i] = segmentToBezier(mTheta, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY);
  1105. fromX = result[i][4];
  1106. fromY = result[i][5];
  1107. mTheta = th3;
  1108. th3 += mDelta;
  1109. }
  1110. arcToSegmentsCache[argsString] = result;
  1111. return result;
  1112. }
  1113. function segmentToBezier(th2, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY) {
  1114. var argsString2 = _join.call(arguments);
  1115. if (segmentToBezierCache[argsString2]) {
  1116. return segmentToBezierCache[argsString2];
  1117. }
  1118. var costh2 = Math.cos(th2),
  1119. sinth2 = Math.sin(th2),
  1120. costh3 = Math.cos(th3),
  1121. sinth3 = Math.sin(th3),
  1122. toX = cosTh * rx * costh3 - sinTh * ry * sinth3 + cx1,
  1123. toY = sinTh * rx * costh3 + cosTh * ry * sinth3 + cy1,
  1124. cp1X = fromX + mT * ( -cosTh * rx * sinth2 - sinTh * ry * costh2),
  1125. cp1Y = fromY + mT * ( -sinTh * rx * sinth2 + cosTh * ry * costh2),
  1126. cp2X = toX + mT * ( cosTh * rx * sinth3 + sinTh * ry * costh3),
  1127. cp2Y = toY + mT * ( sinTh * rx * sinth3 - cosTh * ry * costh3);
  1128. segmentToBezierCache[argsString2] = [
  1129. cp1X, cp1Y,
  1130. cp2X, cp2Y,
  1131. toX, toY
  1132. ];
  1133. return segmentToBezierCache[argsString2];
  1134. }
  1135. /*
  1136. * Private
  1137. */
  1138. function calcVectorAngle(ux, uy, vx, vy) {
  1139. var ta = Math.atan2(uy, ux),
  1140. tb = Math.atan2(vy, vx);
  1141. if (tb >= ta) {
  1142. return tb - ta;
  1143. }
  1144. else {
  1145. return 2 * Math.PI - (ta - tb);
  1146. }
  1147. }
  1148. /**
  1149. * Draws arc
  1150. * @param {CanvasRenderingContext2D} ctx
  1151. * @param {Number} fx
  1152. * @param {Number} fy
  1153. * @param {Array} coords
  1154. */
  1155. fabric.util.drawArc = function(ctx, fx, fy, coords) {
  1156. var rx = coords[0],
  1157. ry = coords[1],
  1158. rot = coords[2],
  1159. large = coords[3],
  1160. sweep = coords[4],
  1161. tx = coords[5],
  1162. ty = coords[6],
  1163. segs = [[], [], [], []],
  1164. segsNorm = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot);
  1165. for (var i = 0, len = segsNorm.length; i < len; i++) {
  1166. segs[i][0] = segsNorm[i][0] + fx;
  1167. segs[i][1] = segsNorm[i][1] + fy;
  1168. segs[i][2] = segsNorm[i][2] + fx;
  1169. segs[i][3] = segsNorm[i][3] + fy;
  1170. segs[i][4] = segsNorm[i][4] + fx;
  1171. segs[i][5] = segsNorm[i][5] + fy;
  1172. ctx.bezierCurveTo.apply(ctx, segs[i]);
  1173. }
  1174. };
  1175. /**
  1176. * Calculate bounding box of a elliptic-arc
  1177. * @param {Number} fx start point of arc
  1178. * @param {Number} fy
  1179. * @param {Number} rx horizontal radius
  1180. * @param {Number} ry vertical radius
  1181. * @param {Number} rot angle of horizontal axe
  1182. * @param {Number} large 1 or 0, whatever the arc is the big or the small on the 2 points
  1183. * @param {Number} sweep 1 or 0, 1 clockwise or counterclockwise direction
  1184. * @param {Number} tx end point of arc
  1185. * @param {Number} ty
  1186. */
  1187. fabric.util.getBoundsOfArc = function(fx, fy, rx, ry, rot, large, sweep, tx, ty) {
  1188. var fromX = 0, fromY = 0, bound, bounds = [],
  1189. segs = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot);
  1190. for (var i = 0, len = segs.length; i < len; i++) {
  1191. bound = getBoundsOfCurve(fromX, fromY, segs[i][0], segs[i][1], segs[i][2], segs[i][3], segs[i][4], segs[i][5]);
  1192. bounds.push({ x: bound[0].x + fx, y: bound[0].y + fy });
  1193. bounds.push({ x: bound[1].x + fx, y: bound[1].y + fy });
  1194. fromX = segs[i][4];
  1195. fromY = segs[i][5];
  1196. }
  1197. return bounds;
  1198. };
  1199. /**
  1200. * Calculate bounding box of a beziercurve
  1201. * @param {Number} x0 starting point
  1202. * @param {Number} y0
  1203. * @param {Number} x1 first control point
  1204. * @param {Number} y1
  1205. * @param {Number} x2 secondo control point
  1206. * @param {Number} y2
  1207. * @param {Number} x3 end of beizer
  1208. * @param {Number} y3
  1209. */
  1210. // taken from http://jsbin.com/ivomiq/56/edit no credits available for that.
  1211. function getBoundsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3) {
  1212. var argsString = _join.call(arguments);
  1213. if (boundsOfCurveCache[argsString]) {
  1214. return boundsOfCurveCache[argsString];
  1215. }
  1216. var sqrt = Math.sqrt,
  1217. min = Math.min, max = Math.max,
  1218. abs = Math.abs, tvalues = [],
  1219. bounds = [[], []],
  1220. a, b, c, t, t1, t2, b2ac, sqrtb2ac;
  1221. b = 6 * x0 - 12 * x1 + 6 * x2;
  1222. a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
  1223. c = 3 * x1 - 3 * x0;
  1224. for (var i = 0; i < 2; ++i) {
  1225. if (i > 0) {
  1226. b = 6 * y0 - 12 * y1 + 6 * y2;
  1227. a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
  1228. c = 3 * y1 - 3 * y0;
  1229. }
  1230. if (abs(a) < 1e-12) {
  1231. if (abs(b) < 1e-12) {
  1232. continue;
  1233. }
  1234. t = -c / b;
  1235. if (0 < t && t < 1) {
  1236. tvalues.push(t);
  1237. }
  1238. continue;
  1239. }
  1240. b2ac = b * b - 4 * c * a;
  1241. if (b2ac < 0) {
  1242. continue;
  1243. }
  1244. sqrtb2ac = sqrt(b2ac);
  1245. t1 = (-b + sqrtb2ac) / (2 * a);
  1246. if (0 < t1 && t1 < 1) {
  1247. tvalues.push(t1);
  1248. }
  1249. t2 = (-b - sqrtb2ac) / (2 * a);
  1250. if (0 < t2 && t2 < 1) {
  1251. tvalues.push(t2);
  1252. }
  1253. }
  1254. var x, y, j = tvalues.length, jlen = j, mt;
  1255. while (j--) {
  1256. t = tvalues[j];
  1257. mt = 1 - t;
  1258. x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);
  1259. bounds[0][j] = x;
  1260. y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);
  1261. bounds[1][j] = y;
  1262. }
  1263. bounds[0][jlen] = x0;
  1264. bounds[1][jlen] = y0;
  1265. bounds[0][jlen + 1] = x3;
  1266. bounds[1][jlen + 1] = y3;
  1267. var result = [
  1268. {
  1269. x: min.apply(null, bounds[0]),
  1270. y: min.apply(null, bounds[1])
  1271. },
  1272. {
  1273. x: max.apply(null, bounds[0]),
  1274. y: max.apply(null, bounds[1])
  1275. }
  1276. ];
  1277. boundsOfCurveCache[argsString] = result;
  1278. return result;
  1279. }
  1280. fabric.util.getBoundsOfCurve = getBoundsOfCurve;
  1281. })();
  1282. (function() {
  1283. var slice = Array.prototype.slice;
  1284. /* _ES5_COMPAT_START_ */
  1285. if (!Array.prototype.indexOf) {
  1286. /**
  1287. * Finds index of an element in an array
  1288. * @param {*} searchElement
  1289. * @return {Number}
  1290. */
  1291. Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
  1292. if (this === void 0 || this === null) {
  1293. throw new TypeError();
  1294. }
  1295. var t = Object(this), len = t.length >>> 0;
  1296. if (len === 0) {
  1297. return -1;
  1298. }
  1299. var n = 0;
  1300. if (arguments.length > 0) {
  1301. n = Number(arguments[1]);
  1302. if (n !== n) { // shortcut for verifying if it's NaN
  1303. n = 0;
  1304. }
  1305. else if (n !== 0 && n !== Number.POSITIVE_INFINITY && n !== Number.NEGATIVE_INFINITY) {
  1306. n = (n > 0 || -1) * Math.floor(Math.abs(n));
  1307. }
  1308. }
  1309. if (n >= len) {
  1310. return -1;
  1311. }
  1312. var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
  1313. for (; k < len; k++) {
  1314. if (k in t && t[k] === searchElement) {
  1315. return k;
  1316. }
  1317. }
  1318. return -1;
  1319. };
  1320. }
  1321. if (!Array.prototype.forEach) {
  1322. /**
  1323. * Iterates an array, invoking callback for each element
  1324. * @param {Function} fn Callback to invoke for each element
  1325. * @param {Object} [context] Context to invoke callback in
  1326. * @return {Array}
  1327. */
  1328. Array.prototype.forEach = function(fn, context) {
  1329. for (var i = 0, len = this.length >>> 0; i < len; i++) {
  1330. if (i in this) {
  1331. fn.call(context, this[i], i, this);
  1332. }
  1333. }
  1334. };
  1335. }
  1336. if (!Array.prototype.map) {
  1337. /**
  1338. * Returns a result of iterating over an array, invoking callback for each element
  1339. * @param {Function} fn Callback to invoke for each element
  1340. * @param {Object} [context] Context to invoke callback in
  1341. * @return {Array}
  1342. */
  1343. Array.prototype.map = function(fn, context) {
  1344. var result = [];
  1345. for (var i = 0, len = this.length >>> 0; i < len; i++) {
  1346. if (i in this) {
  1347. result[i] = fn.call(context, this[i], i, this);
  1348. }
  1349. }
  1350. return result;
  1351. };
  1352. }
  1353. if (!Array.prototype.every) {
  1354. /**
  1355. * Returns true if a callback returns truthy value for all elements in an array
  1356. * @param {Function} fn Callback to invoke for each element
  1357. * @param {Object} [context] Context to invoke callback in
  1358. * @return {Boolean}
  1359. */
  1360. Array.prototype.every = function(fn, context) {
  1361. for (var i = 0, len = this.length >>> 0; i < len; i++) {
  1362. if (i in this && !fn.call(context, this[i], i, this)) {
  1363. return false;
  1364. }
  1365. }
  1366. return true;
  1367. };
  1368. }
  1369. if (!Array.prototype.some) {
  1370. /**
  1371. * Returns true if a callback returns truthy value for at least one element in an array
  1372. * @param {Function} fn Callback to invoke for each element
  1373. * @param {Object} [context] Context to invoke callback in
  1374. * @return {Boolean}
  1375. */
  1376. Array.prototype.some = function(fn, context) {
  1377. for (var i = 0, len = this.length >>> 0; i < len; i++) {
  1378. if (i in this && fn.call(context, this[i], i, this)) {
  1379. return true;
  1380. }
  1381. }
  1382. return false;
  1383. };
  1384. }
  1385. if (!Array.prototype.filter) {
  1386. /**
  1387. * Returns the result of iterating over elements in an array
  1388. * @param {Function} fn Callback to invoke for each element
  1389. * @param {Object} [context] Context to invoke callback in
  1390. * @return {Array}
  1391. */
  1392. Array.prototype.filter = function(fn, context) {
  1393. var result = [], val;
  1394. for (var i = 0, len = this.length >>> 0; i < len; i++) {
  1395. if (i in this) {
  1396. val = this[i]; // in case fn mutates this
  1397. if (fn.call(context, val, i, this)) {
  1398. result.push(val);
  1399. }
  1400. }
  1401. }
  1402. return result;
  1403. };
  1404. }
  1405. if (!Array.prototype.reduce) {
  1406. /**
  1407. * Returns "folded" (reduced) result of iterating over elements in an array
  1408. * @param {Function} fn Callback to invoke for each element
  1409. * @return {*}
  1410. */
  1411. Array.prototype.reduce = function(fn /*, initial*/) {
  1412. var len = this.length >>> 0,
  1413. i = 0,
  1414. rv;
  1415. if (arguments.length > 1) {
  1416. rv = arguments[1];
  1417. }
  1418. else {
  1419. do {
  1420. if (i in this) {
  1421. rv = this[i++];
  1422. break;
  1423. }
  1424. // if array contains no values, no initial value to return
  1425. if (++i >= len) {
  1426. throw new TypeError();
  1427. }
  1428. }
  1429. while (true);
  1430. }
  1431. for (; i < len; i++) {
  1432. if (i in this) {
  1433. rv = fn.call(null, rv, this[i], i, this);
  1434. }
  1435. }
  1436. return rv;
  1437. };
  1438. }
  1439. /* _ES5_COMPAT_END_ */
  1440. /**
  1441. * Invokes method on all items in a given array
  1442. * @memberOf fabric.util.array
  1443. * @param {Array} array Array to iterate over
  1444. * @param {String} method Name of a method to invoke
  1445. * @return {Array}
  1446. */
  1447. function invoke(array, method) {
  1448. var args = slice.call(arguments, 2), result = [];
  1449. for (var i = 0, len = array.length; i < len; i++) {
  1450. result[i] = args.length ? array[i][method].apply(array[i], args) : array[i][method].call(array[i]);
  1451. }
  1452. return result;
  1453. }
  1454. /**
  1455. * Finds maximum value in array (not necessarily "first" one)
  1456. * @memberOf fabric.util.array
  1457. * @param {Array} array Array to iterate over
  1458. * @param {String} byProperty
  1459. * @return {*}
  1460. */
  1461. function max(array, byProperty) {
  1462. return find(array, byProperty, function(value1, value2) {
  1463. return value1 >= value2;
  1464. });
  1465. }
  1466. /**
  1467. * Finds minimum value in array (not necessarily "first" one)
  1468. * @memberOf fabric.util.array
  1469. * @param {Array} array Array to iterate over
  1470. * @param {String} byProperty
  1471. * @return {*}
  1472. */
  1473. function min(array, byProperty) {
  1474. return find(array, byProperty, function(value1, value2) {
  1475. return value1 < value2;
  1476. });
  1477. }
  1478. /**
  1479. * @private
  1480. */
  1481. function fill(array, value) {
  1482. var k = array.length;
  1483. while (k--) {
  1484. array[k] = value;
  1485. }
  1486. return array;
  1487. }
  1488. /**
  1489. * @private
  1490. */
  1491. function find(array, byProperty, condition) {
  1492. if (!array || array.length === 0) {
  1493. return;
  1494. }
  1495. var i = array.length - 1,
  1496. result = byProperty ? array[i][byProperty] : array[i];
  1497. if (byProperty) {
  1498. while (i--) {
  1499. if (condition(array[i][byProperty], result)) {
  1500. result = array[i][byProperty];
  1501. }
  1502. }
  1503. }
  1504. else {
  1505. while (i--) {
  1506. if (condition(array[i], result)) {
  1507. result = array[i];
  1508. }
  1509. }
  1510. }
  1511. return result;
  1512. }
  1513. /**
  1514. * @namespace fabric.util.array
  1515. */
  1516. fabric.util.array = {
  1517. fill: fill,
  1518. invoke: invoke,
  1519. min: min,
  1520. max: max
  1521. };
  1522. })();
  1523. (function() {
  1524. /**
  1525. * Copies all enumerable properties of one js object to another
  1526. * Does not clone or extend fabric.Object subclasses.
  1527. * @memberOf fabric.util.object
  1528. * @param {Object} destination Where to copy to
  1529. * @param {Object} source Where to copy from
  1530. * @return {Object}
  1531. */
  1532. function extend(destination, source, deep) {
  1533. // JScript DontEnum bug is not taken care of
  1534. // the deep clone is for internal use, is not meant to avoid
  1535. // javascript traps or cloning html element or self referenced objects.
  1536. if (deep) {
  1537. if (!fabric.isLikelyNode && source instanceof Element) {
  1538. // avoid cloning deep images, canvases,
  1539. destination = source;
  1540. }
  1541. else if (source instanceof Array) {
  1542. destination = [];
  1543. for (var i = 0, len = source.length; i < len; i++) {
  1544. destination[i] = extend({ }, source[i], deep);
  1545. }
  1546. }
  1547. else if (source && typeof source === 'object') {
  1548. for (var property in source) {
  1549. if (source.hasOwnProperty(property)) {
  1550. destination[property] = extend({ }, source[property], deep);
  1551. }
  1552. }
  1553. }
  1554. else {
  1555. // this sounds odd for an extend but is ok for recursive use
  1556. destination = source;
  1557. }
  1558. }
  1559. else {
  1560. for (var property in source) {
  1561. destination[property] = source[property];
  1562. }
  1563. }
  1564. return destination;
  1565. }
  1566. /**
  1567. * Creates an empty object and copies all enumerable properties of another object to it
  1568. * @memberOf fabric.util.object
  1569. * @param {Object} object Object to clone
  1570. * @return {Object}
  1571. */
  1572. function clone(object, deep) {
  1573. return extend({ }, object, deep);
  1574. }
  1575. /** @namespace fabric.util.object */
  1576. fabric.util.object = {
  1577. extend: extend,
  1578. clone: clone
  1579. };
  1580. })();
  1581. (function() {
  1582. /* _ES5_COMPAT_START_ */
  1583. if (!String.prototype.trim) {
  1584. /**
  1585. * Trims a string (removing whitespace from the beginning and the end)
  1586. * @function external:String#trim
  1587. * @see <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/Trim">String#trim on MDN</a>
  1588. */
  1589. String.prototype.trim = function () {
  1590. // this trim is not fully ES3 or ES5 compliant, but it should cover most cases for now
  1591. return this.replace(/^[\s\xA0]+/, '').replace(/[\s\xA0]+$/, '');
  1592. };
  1593. }
  1594. /* _ES5_COMPAT_END_ */
  1595. /**
  1596. * Camelizes a string
  1597. * @memberOf fabric.util.string
  1598. * @param {String} string String to camelize
  1599. * @return {String} Camelized version of a string
  1600. */
  1601. function camelize(string) {
  1602. return string.replace(/-+(.)?/g, function(match, character) {
  1603. return character ? character.toUpperCase() : '';
  1604. });
  1605. }
  1606. /**
  1607. * Capitalizes a string
  1608. * @memberOf fabric.util.string
  1609. * @param {String} string String to capitalize
  1610. * @param {Boolean} [firstLetterOnly] If true only first letter is capitalized
  1611. * and other letters stay untouched, if false first letter is capitalized
  1612. * and other letters are converted to lowercase.
  1613. * @return {String} Capitalized version of a string
  1614. */
  1615. function capitalize(string, firstLetterOnly) {
  1616. return string.charAt(0).toUpperCase() +
  1617. (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase());
  1618. }
  1619. /**
  1620. * Escapes XML in a string
  1621. * @memberOf fabric.util.string
  1622. * @param {String} string String to escape
  1623. * @return {String} Escaped version of a string
  1624. */
  1625. function escapeXml(string) {
  1626. return string.replace(/&/g, '&amp;')
  1627. .replace(/"/g, '&quot;')
  1628. .replace(/'/g, '&apos;')
  1629. .replace(/</g, '&lt;')
  1630. .replace(/>/g, '&gt;');
  1631. }
  1632. /**
  1633. * String utilities
  1634. * @namespace fabric.util.string
  1635. */
  1636. fabric.util.string = {
  1637. camelize: camelize,
  1638. capitalize: capitalize,
  1639. escapeXml: escapeXml
  1640. };
  1641. })();
  1642. /* _ES5_COMPAT_START_ */
  1643. (function() {
  1644. var slice = Array.prototype.slice,
  1645. apply = Function.prototype.apply,
  1646. Dummy = function() { };
  1647. if (!Function.prototype.bind) {
  1648. /**
  1649. * Cross-browser approximation of ES5 Function.prototype.bind (not fully spec conforming)
  1650. * @see <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind">Function#bind on MDN</a>
  1651. * @param {Object} thisArg Object to bind function to
  1652. * @param {Any[]} Values to pass to a bound function
  1653. * @return {Function}
  1654. */
  1655. Function.prototype.bind = function(thisArg) {
  1656. var _this = this, args = slice.call(arguments, 1), bound;
  1657. if (args.length) {
  1658. bound = function() {
  1659. return apply.call(_this, this instanceof Dummy ? this : thisArg, args.concat(slice.call(arguments)));
  1660. };
  1661. }
  1662. else {
  1663. /** @ignore */
  1664. bound = function() {
  1665. return apply.call(_this, this instanceof Dummy ? this : thisArg, arguments);
  1666. };
  1667. }
  1668. Dummy.prototype = this.prototype;
  1669. bound.prototype = new Dummy();
  1670. return bound;
  1671. };
  1672. }
  1673. })();
  1674. /* _ES5_COMPAT_END_ */
  1675. (function() {
  1676. var slice = Array.prototype.slice, emptyFunction = function() { },
  1677. IS_DONTENUM_BUGGY = (function() {
  1678. for (var p in { toString: 1 }) {
  1679. if (p === 'toString') {
  1680. return false;
  1681. }
  1682. }
  1683. return true;
  1684. })(),
  1685. /** @ignore */
  1686. addMethods = function(klass, source, parent) {
  1687. for (var property in source) {
  1688. if (property in klass.prototype &&
  1689. typeof klass.prototype[property] === 'function' &&
  1690. (source[property] + '').indexOf('callSuper') > -1) {
  1691. klass.prototype[property] = (function(property) {
  1692. return function() {
  1693. var superclass = this.constructor.superclass;
  1694. this.constructor.superclass = parent;
  1695. var returnValue = source[property].apply(this, arguments);
  1696. this.constructor.superclass = superclass;
  1697. if (property !== 'initialize') {
  1698. return returnValue;
  1699. }
  1700. };
  1701. })(property);
  1702. }
  1703. else {
  1704. klass.prototype[property] = source[property];
  1705. }
  1706. if (IS_DONTENUM_BUGGY) {
  1707. if (source.toString !== Object.prototype.toString) {
  1708. klass.prototype.toString = source.toString;
  1709. }
  1710. if (source.valueOf !== Object.prototype.valueOf) {
  1711. klass.prototype.valueOf = source.valueOf;
  1712. }
  1713. }
  1714. }
  1715. };
  1716. function Subclass() { }
  1717. function callSuper(methodName) {
  1718. var fn = this.constructor.superclass.prototype[methodName];
  1719. return (arguments.length > 1)
  1720. ? fn.apply(this, slice.call(arguments, 1))
  1721. : fn.call(this);
  1722. }
  1723. /**
  1724. * Helper for creation of "classes".
  1725. * @memberOf fabric.util
  1726. * @param {Function} [parent] optional "Class" to inherit from
  1727. * @param {Object} [properties] Properties shared by all instances of this class
  1728. * (be careful modifying objects defined here as this would affect all instances)
  1729. */
  1730. function createClass() {
  1731. var parent = null,
  1732. properties = slice.call(arguments, 0);
  1733. if (typeof properties[0] === 'function') {
  1734. parent = properties.shift();
  1735. }
  1736. function klass() {
  1737. this.initialize.apply(this, arguments);
  1738. }
  1739. klass.superclass = parent;
  1740. klass.subclasses = [];
  1741. if (parent) {
  1742. Subclass.prototype = parent.prototype;
  1743. klass.prototype = new Subclass();
  1744. parent.subclasses.push(klass);
  1745. }
  1746. for (var i = 0, length = properties.length; i < length; i++) {
  1747. addMethods(klass, properties[i], parent);
  1748. }
  1749. if (!klass.prototype.initialize) {
  1750. klass.prototype.initialize = emptyFunction;
  1751. }
  1752. klass.prototype.constructor = klass;
  1753. klass.prototype.callSuper = callSuper;
  1754. return klass;
  1755. }
  1756. fabric.util.createClass = createClass;
  1757. })();
  1758. (function () {
  1759. var unknown = 'unknown';
  1760. /* EVENT HANDLING */
  1761. function areHostMethods(object) {
  1762. var methodNames = Array.prototype.slice.call(arguments, 1),
  1763. t, i, len = methodNames.length;
  1764. for (i = 0; i < len; i++) {
  1765. t = typeof object[methodNames[i]];
  1766. if (!(/^(?:function|object|unknown)$/).test(t)) {
  1767. return false;
  1768. }
  1769. }
  1770. return true;
  1771. }
  1772. /** @ignore */
  1773. var getElement,
  1774. setElement,
  1775. getUniqueId = (function () {
  1776. var uid = 0;
  1777. return function (element) {
  1778. return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++);
  1779. };
  1780. })();
  1781. (function () {
  1782. var elements = { };
  1783. /** @ignore */
  1784. getElement = function (uid) {
  1785. return elements[uid];
  1786. };
  1787. /** @ignore */
  1788. setElement = function (uid, element) {
  1789. elements[uid] = element;
  1790. };
  1791. })();
  1792. function createListener(uid, handler) {
  1793. return {
  1794. handler: handler,
  1795. wrappedHandler: createWrappedHandler(uid, handler)
  1796. };
  1797. }
  1798. function createWrappedHandler(uid, handler) {
  1799. return function (e) {
  1800. handler.call(getElement(uid), e || fabric.window.event);
  1801. };
  1802. }
  1803. function createDispatcher(uid, eventName) {
  1804. return function (e) {
  1805. if (handlers[uid] && handlers[uid][eventName]) {
  1806. var handlersForEvent = handlers[uid][eventName];
  1807. for (var i = 0, len = handlersForEvent.length; i < len; i++) {
  1808. handlersForEvent[i].call(this, e || fabric.window.event);
  1809. }
  1810. }
  1811. };
  1812. }
  1813. var shouldUseAddListenerRemoveListener = (
  1814. areHostMethods(fabric.document.documentElement, 'addEventListener', 'removeEventListener') &&
  1815. areHostMethods(fabric.window, 'addEventListener', 'removeEventListener')),
  1816. shouldUseAttachEventDetachEvent = (
  1817. areHostMethods(fabric.document.documentElement, 'attachEvent', 'detachEvent') &&
  1818. areHostMethods(fabric.window, 'attachEvent', 'detachEvent')),
  1819. // IE branch
  1820. listeners = { },
  1821. // DOM L0 branch
  1822. handlers = { },
  1823. addListener, removeListener;
  1824. if (shouldUseAddListenerRemoveListener) {
  1825. /** @ignore */
  1826. addListener = function (element, eventName, handler, options) {
  1827. // since ie10 or ie9 can use addEventListener but they do not support options, i need to check
  1828. element.addEventListener(eventName, handler, shouldUseAttachEventDetachEvent ? false : options);
  1829. };
  1830. /** @ignore */
  1831. removeListener = function (element, eventName, handler, options) {
  1832. element.removeEventListener(eventName, handler, shouldUseAttachEventDetachEvent ? false : options);
  1833. };
  1834. }
  1835. else if (shouldUseAttachEventDetachEvent) {
  1836. /** @ignore */
  1837. addListener = function (element, eventName, handler) {
  1838. var uid = getUniqueId(element);
  1839. setElement(uid, element);
  1840. if (!listeners[uid]) {
  1841. listeners[uid] = { };
  1842. }
  1843. if (!listeners[uid][eventName]) {
  1844. listeners[uid][eventName] = [];
  1845. }
  1846. var listener = createListener(uid, handler);
  1847. listeners[uid][eventName].push(listener);
  1848. element.attachEvent('on' + eventName, listener.wrappedHandler);
  1849. };
  1850. /** @ignore */
  1851. removeListener = function (element, eventName, handler) {
  1852. var uid = getUniqueId(element), listener;
  1853. if (listeners[uid] && listeners[uid][eventName]) {
  1854. for (var i = 0, len = listeners[uid][eventName].length; i < len; i++) {
  1855. listener = listeners[uid][eventName][i];
  1856. if (listener && listener.handler === handler) {
  1857. element.detachEvent('on' + eventName, listener.wrappedHandler);
  1858. listeners[uid][eventName][i] = null;
  1859. }
  1860. }
  1861. }
  1862. };
  1863. }
  1864. else {
  1865. /** @ignore */
  1866. addListener = function (element, eventName, handler) {
  1867. var uid = getUniqueId(element);
  1868. if (!handlers[uid]) {
  1869. handlers[uid] = { };
  1870. }
  1871. if (!handlers[uid][eventName]) {
  1872. handlers[uid][eventName] = [];
  1873. var existingHandler = element['on' + eventName];
  1874. if (existingHandler) {
  1875. handlers[uid][eventName].push(existingHandler);
  1876. }
  1877. element['on' + eventName] = createDispatcher(uid, eventName);
  1878. }
  1879. handlers[uid][eventName].push(handler);
  1880. };
  1881. /** @ignore */
  1882. removeListener = function (element, eventName, handler) {
  1883. var uid = getUniqueId(element);
  1884. if (handlers[uid] && handlers[uid][eventName]) {
  1885. var handlersForEvent = handlers[uid][eventName];
  1886. for (var i = 0, len = handlersForEvent.length; i < len; i++) {
  1887. if (handlersForEvent[i] === handler) {
  1888. handlersForEvent.splice(i, 1);
  1889. }
  1890. }
  1891. }
  1892. };
  1893. }
  1894. /**
  1895. * Adds an event listener to an element
  1896. * @function
  1897. * @memberOf fabric.util
  1898. * @param {HTMLElement} element
  1899. * @param {String} eventName
  1900. * @param {Function} handler
  1901. */
  1902. fabric.util.addListener = addListener;
  1903. /**
  1904. * Removes an event listener from an element
  1905. * @function
  1906. * @memberOf fabric.util
  1907. * @param {HTMLElement} element
  1908. * @param {String} eventName
  1909. * @param {Function} handler
  1910. */
  1911. fabric.util.removeListener = removeListener;
  1912. /**
  1913. * Cross-browser wrapper for getting event's coordinates
  1914. * @memberOf fabric.util
  1915. * @param {Event} event Event object
  1916. */
  1917. function getPointer(event) {
  1918. event || (event = fabric.window.event);
  1919. var element = event.target ||
  1920. (typeof event.srcElement !== unknown ? event.srcElement : null),
  1921. scroll = fabric.util.getScrollLeftTop(element);
  1922. return {
  1923. x: pointerX(event) + scroll.left,
  1924. y: pointerY(event) + scroll.top
  1925. };
  1926. }
  1927. var pointerX = function(event) {
  1928. // looks like in IE (<9) clientX at certain point (apparently when mouseup fires on VML element)
  1929. // is represented as COM object, with all the consequences, like "unknown" type and error on [[Get]]
  1930. // need to investigate later
  1931. return (typeof event.clientX !== unknown ? event.clientX : 0);
  1932. },
  1933. pointerY = function(event) {
  1934. return (typeof event.clientY !== unknown ? event.clientY : 0);
  1935. };
  1936. function _getPointer(event, pageProp, clientProp) {
  1937. var touchProp = event.type === 'touchend' ? 'changedTouches' : 'touches';
  1938. return (event[touchProp] && event[touchProp][0]
  1939. ? (event[touchProp][0][pageProp] - (event[touchProp][0][pageProp] - event[touchProp][0][clientProp]))
  1940. || event[clientProp]
  1941. : event[clientProp]);
  1942. }
  1943. if (fabric.isTouchSupported) {
  1944. pointerX = function(event) {
  1945. return _getPointer(event, 'pageX', 'clientX');
  1946. };
  1947. pointerY = function(event) {
  1948. return _getPointer(event, 'pageY', 'clientY');
  1949. };
  1950. }
  1951. fabric.util.getPointer = getPointer;
  1952. fabric.util.object.extend(fabric.util, fabric.Observable);
  1953. })();
  1954. (function () {
  1955. /**
  1956. * Cross-browser wrapper for setting element's style
  1957. * @memberOf fabric.util
  1958. * @param {HTMLElement} element
  1959. * @param {Object} styles
  1960. * @return {HTMLElement} Element that was passed as a first argument
  1961. */
  1962. function setStyle(element, styles) {
  1963. var elementStyle = element.style;
  1964. if (!elementStyle) {
  1965. return element;
  1966. }
  1967. if (typeof styles === 'string') {
  1968. element.style.cssText += ';' + styles;
  1969. return styles.indexOf('opacity') > -1
  1970. ? setOpacity(element, styles.match(/opacity:\s*(\d?\.?\d*)/)[1])
  1971. : element;
  1972. }
  1973. for (var property in styles) {
  1974. if (property === 'opacity') {
  1975. setOpacity(element, styles[property]);
  1976. }
  1977. else {
  1978. var normalizedProperty = (property === 'float' || property === 'cssFloat')
  1979. ? (typeof elementStyle.styleFloat === 'undefined' ? 'cssFloat' : 'styleFloat')
  1980. : property;
  1981. elementStyle[normalizedProperty] = styles[property];
  1982. }
  1983. }
  1984. return element;
  1985. }
  1986. var parseEl = fabric.document.createElement('div'),
  1987. supportsOpacity = typeof parseEl.style.opacity === 'string',
  1988. supportsFilters = typeof parseEl.style.filter === 'string',
  1989. reOpacity = /alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/,
  1990. /** @ignore */
  1991. setOpacity = function (element) { return element; };
  1992. if (supportsOpacity) {
  1993. /** @ignore */
  1994. setOpacity = function(element, value) {
  1995. element.style.opacity = value;
  1996. return element;
  1997. };
  1998. }
  1999. else if (supportsFilters) {
  2000. /** @ignore */
  2001. setOpacity = function(element, value) {
  2002. var es = element.style;
  2003. if (element.currentStyle && !element.currentStyle.hasLayout) {
  2004. es.zoom = 1;
  2005. }
  2006. if (reOpacity.test(es.filter)) {
  2007. value = value >= 0.9999 ? '' : ('alpha(opacity=' + (value * 100) + ')');
  2008. es.filter = es.filter.replace(reOpacity, value);
  2009. }
  2010. else {
  2011. es.filter += ' alpha(opacity=' + (value * 100) + ')';
  2012. }
  2013. return element;
  2014. };
  2015. }
  2016. fabric.util.setStyle = setStyle;
  2017. })();
  2018. (function() {
  2019. var _slice = Array.prototype.slice;
  2020. /**
  2021. * Takes id and returns an element with that id (if one exists in a document)
  2022. * @memberOf fabric.util
  2023. * @param {String|HTMLElement} id
  2024. * @return {HTMLElement|null}
  2025. */
  2026. function getById(id) {
  2027. return typeof id === 'string' ? fabric.document.getElementById(id) : id;
  2028. }
  2029. var sliceCanConvertNodelists,
  2030. /**
  2031. * Converts an array-like object (e.g. arguments or NodeList) to an array
  2032. * @memberOf fabric.util
  2033. * @param {Object} arrayLike
  2034. * @return {Array}
  2035. */
  2036. toArray = function(arrayLike) {
  2037. return _slice.call(arrayLike, 0);
  2038. };
  2039. try {
  2040. sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array;
  2041. }
  2042. catch (err) { }
  2043. if (!sliceCanConvertNodelists) {
  2044. toArray = function(arrayLike) {
  2045. var arr = new Array(arrayLike.length), i = arrayLike.length;
  2046. while (i--) {
  2047. arr[i] = arrayLike[i];
  2048. }
  2049. return arr;
  2050. };
  2051. }
  2052. /**
  2053. * Creates specified element with specified attributes
  2054. * @memberOf fabric.util
  2055. * @param {String} tagName Type of an element to create
  2056. * @param {Object} [attributes] Attributes to set on an element
  2057. * @return {HTMLElement} Newly created element
  2058. */
  2059. function makeElement(tagName, attributes) {
  2060. var el = fabric.document.createElement(tagName);
  2061. for (var prop in attributes) {
  2062. if (prop === 'class') {
  2063. el.className = attributes[prop];
  2064. }
  2065. else if (prop === 'for') {
  2066. el.htmlFor = attributes[prop];
  2067. }
  2068. else {
  2069. el.setAttribute(prop, attributes[prop]);
  2070. }
  2071. }
  2072. return el;
  2073. }
  2074. /**
  2075. * Adds class to an element
  2076. * @memberOf fabric.util
  2077. * @param {HTMLElement} element Element to add class to
  2078. * @param {String} className Class to add to an element
  2079. */
  2080. function addClass(element, className) {
  2081. if (element && (' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) {
  2082. element.className += (element.className ? ' ' : '') + className;
  2083. }
  2084. }
  2085. /**
  2086. * Wraps element with another element
  2087. * @memberOf fabric.util
  2088. * @param {HTMLElement} element Element to wrap
  2089. * @param {HTMLElement|String} wrapper Element to wrap with
  2090. * @param {Object} [attributes] Attributes to set on a wrapper
  2091. * @return {HTMLElement} wrapper
  2092. */
  2093. function wrapElement(element, wrapper, attributes) {
  2094. if (typeof wrapper === 'string') {
  2095. wrapper = makeElement(wrapper, attributes);
  2096. }
  2097. if (element.parentNode) {
  2098. element.parentNode.replaceChild(wrapper, element);
  2099. }
  2100. wrapper.appendChild(element);
  2101. return wrapper;
  2102. }
  2103. /**
  2104. * Returns element scroll offsets
  2105. * @memberOf fabric.util
  2106. * @param {HTMLElement} element Element to operate on
  2107. * @return {Object} Object with left/top values
  2108. */
  2109. function getScrollLeftTop(element) {
  2110. var left = 0,
  2111. top = 0,
  2112. docElement = fabric.document.documentElement,
  2113. body = fabric.document.body || {
  2114. scrollLeft: 0, scrollTop: 0
  2115. };
  2116. // While loop checks (and then sets element to) .parentNode OR .host
  2117. // to account for ShadowDOM. We still want to traverse up out of ShadowDOM,
  2118. // but the .parentNode of a root ShadowDOM node will always be null, instead
  2119. // it should be accessed through .host. See http://stackoverflow.com/a/24765528/4383938
  2120. while (element && (element.parentNode || element.host)) {
  2121. // Set element to element parent, or 'host' in case of ShadowDOM
  2122. element = element.parentNode || element.host;
  2123. if (element === fabric.document) {
  2124. left = body.scrollLeft || docElement.scrollLeft || 0;
  2125. top = body.scrollTop || docElement.scrollTop || 0;
  2126. }
  2127. else {
  2128. left += element.scrollLeft || 0;
  2129. top += element.scrollTop || 0;
  2130. }
  2131. if (element.nodeType === 1 &&
  2132. fabric.util.getElementStyle(element, 'position') === 'fixed') {
  2133. break;
  2134. }
  2135. }
  2136. return { left: left, top: top };
  2137. }
  2138. /**
  2139. * Returns offset for a given element
  2140. * @function
  2141. * @memberOf fabric.util
  2142. * @param {HTMLElement} element Element to get offset for
  2143. * @return {Object} Object with "left" and "top" properties
  2144. */
  2145. function getElementOffset(element) {
  2146. var docElem,
  2147. doc = element && element.ownerDocument,
  2148. box = { left: 0, top: 0 },
  2149. offset = { left: 0, top: 0 },
  2150. scrollLeftTop,
  2151. offsetAttributes = {
  2152. borderLeftWidth: 'left',
  2153. borderTopWidth: 'top',
  2154. paddingLeft: 'left',
  2155. paddingTop: 'top'
  2156. };
  2157. if (!doc) {
  2158. return offset;
  2159. }
  2160. for (var attr in offsetAttributes) {
  2161. offset[offsetAttributes[attr]] += parseInt(getElementStyle(element, attr), 10) || 0;
  2162. }
  2163. docElem = doc.documentElement;
  2164. if ( typeof element.getBoundingClientRect !== 'undefined' ) {
  2165. box = element.getBoundingClientRect();
  2166. }
  2167. scrollLeftTop = getScrollLeftTop(element);
  2168. return {
  2169. left: box.left + scrollLeftTop.left - (docElem.clientLeft || 0) + offset.left,
  2170. top: box.top + scrollLeftTop.top - (docElem.clientTop || 0) + offset.top
  2171. };
  2172. }
  2173. /**
  2174. * Returns style attribute value of a given element
  2175. * @memberOf fabric.util
  2176. * @param {HTMLElement} element Element to get style attribute for
  2177. * @param {String} attr Style attribute to get for element
  2178. * @return {String} Style attribute value of the given element.
  2179. */
  2180. var getElementStyle;
  2181. if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) {
  2182. getElementStyle = function(element, attr) {
  2183. var style = fabric.document.defaultView.getComputedStyle(element, null);
  2184. return style ? style[attr] : undefined;
  2185. };
  2186. }
  2187. else {
  2188. getElementStyle = function(element, attr) {
  2189. var value = element.style[attr];
  2190. if (!value && element.currentStyle) {
  2191. value = element.currentStyle[attr];
  2192. }
  2193. return value;
  2194. };
  2195. }
  2196. (function () {
  2197. var style = fabric.document.documentElement.style,
  2198. selectProp = 'userSelect' in style
  2199. ? 'userSelect'
  2200. : 'MozUserSelect' in style
  2201. ? 'MozUserSelect'
  2202. : 'WebkitUserSelect' in style
  2203. ? 'WebkitUserSelect'
  2204. : 'KhtmlUserSelect' in style
  2205. ? 'KhtmlUserSelect'
  2206. : '';
  2207. /**
  2208. * Makes element unselectable
  2209. * @memberOf fabric.util
  2210. * @param {HTMLElement} element Element to make unselectable
  2211. * @return {HTMLElement} Element that was passed in
  2212. */
  2213. function makeElementUnselectable(element) {
  2214. if (typeof element.onselectstart !== 'undefined') {
  2215. element.onselectstart = fabric.util.falseFunction;
  2216. }
  2217. if (selectProp) {
  2218. element.style[selectProp] = 'none';
  2219. }
  2220. else if (typeof element.unselectable === 'string') {
  2221. element.unselectable = 'on';
  2222. }
  2223. return element;
  2224. }
  2225. /**
  2226. * Makes element selectable
  2227. * @memberOf fabric.util
  2228. * @param {HTMLElement} element Element to make selectable
  2229. * @return {HTMLElement} Element that was passed in
  2230. */
  2231. function makeElementSelectable(element) {
  2232. if (typeof element.onselectstart !== 'undefined') {
  2233. element.onselectstart = null;
  2234. }
  2235. if (selectProp) {
  2236. element.style[selectProp] = '';
  2237. }
  2238. else if (typeof element.unselectable === 'string') {
  2239. element.unselectable = '';
  2240. }
  2241. return element;
  2242. }
  2243. fabric.util.makeElementUnselectable = makeElementUnselectable;
  2244. fabric.util.makeElementSelectable = makeElementSelectable;
  2245. })();
  2246. (function() {
  2247. /**
  2248. * Inserts a script element with a given url into a document; invokes callback, when that script is finished loading
  2249. * @memberOf fabric.util
  2250. * @param {String} url URL of a script to load
  2251. * @param {Function} callback Callback to execute when script is finished loading
  2252. */
  2253. function getScript(url, callback) {
  2254. var headEl = fabric.document.getElementsByTagName('head')[0],
  2255. scriptEl = fabric.document.createElement('script'),
  2256. loading = true;
  2257. /** @ignore */
  2258. scriptEl.onload = /** @ignore */ scriptEl.onreadystatechange = function(e) {
  2259. if (loading) {
  2260. if (typeof this.readyState === 'string' &&
  2261. this.readyState !== 'loaded' &&
  2262. this.readyState !== 'complete') {
  2263. return;
  2264. }
  2265. loading = false;
  2266. callback(e || fabric.window.event);
  2267. scriptEl = scriptEl.onload = scriptEl.onreadystatechange = null;
  2268. }
  2269. };
  2270. scriptEl.src = url;
  2271. headEl.appendChild(scriptEl);
  2272. // causes issue in Opera
  2273. // headEl.removeChild(scriptEl);
  2274. }
  2275. fabric.util.getScript = getScript;
  2276. })();
  2277. fabric.util.getById = getById;
  2278. fabric.util.toArray = toArray;
  2279. fabric.util.makeElement = makeElement;
  2280. fabric.util.addClass = addClass;
  2281. fabric.util.wrapElement = wrapElement;
  2282. fabric.util.getScrollLeftTop = getScrollLeftTop;
  2283. fabric.util.getElementOffset = getElementOffset;
  2284. fabric.util.getElementStyle = getElementStyle;
  2285. })();
  2286. (function() {
  2287. function addParamToUrl(url, param) {
  2288. return url + (/\?/.test(url) ? '&' : '?') + param;
  2289. }
  2290. var makeXHR = (function() {
  2291. var factories = [
  2292. function() { return new ActiveXObject('Microsoft.XMLHTTP'); },
  2293. function() { return new ActiveXObject('Msxml2.XMLHTTP'); },
  2294. function() { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); },
  2295. function() { return new XMLHttpRequest(); }
  2296. ];
  2297. for (var i = factories.length; i--; ) {
  2298. try {
  2299. var req = factories[i]();
  2300. if (req) {
  2301. return factories[i];
  2302. }
  2303. }
  2304. catch (err) { }
  2305. }
  2306. })();
  2307. function emptyFn() { }
  2308. /**
  2309. * Cross-browser abstraction for sending XMLHttpRequest
  2310. * @memberOf fabric.util
  2311. * @param {String} url URL to send XMLHttpRequest to
  2312. * @param {Object} [options] Options object
  2313. * @param {String} [options.method="GET"]
  2314. * @param {String} [options.parameters] parameters to append to url in GET or in body
  2315. * @param {String} [options.body] body to send with POST or PUT request
  2316. * @param {Function} options.onComplete Callback to invoke when request is completed
  2317. * @return {XMLHttpRequest} request
  2318. */
  2319. function request(url, options) {
  2320. options || (options = { });
  2321. var method = options.method ? options.method.toUpperCase() : 'GET',
  2322. onComplete = options.onComplete || function() { },
  2323. xhr = makeXHR(),
  2324. body = options.body || options.parameters;
  2325. /** @ignore */
  2326. xhr.onreadystatechange = function() {
  2327. if (xhr.readyState === 4) {
  2328. onComplete(xhr);
  2329. xhr.onreadystatechange = emptyFn;
  2330. }
  2331. };
  2332. if (method === 'GET') {
  2333. body = null;
  2334. if (typeof options.parameters === 'string') {
  2335. url = addParamToUrl(url, options.parameters);
  2336. }
  2337. }
  2338. xhr.open(method, url, true);
  2339. if (method === 'POST' || method === 'PUT') {
  2340. xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  2341. }
  2342. xhr.send(body);
  2343. return xhr;
  2344. }
  2345. fabric.util.request = request;
  2346. })();
  2347. /**
  2348. * Wrapper around `console.log` (when available)
  2349. * @param {*} [values] Values to log
  2350. */
  2351. fabric.log = function() { };
  2352. /**
  2353. * Wrapper around `console.warn` (when available)
  2354. * @param {*} [values] Values to log as a warning
  2355. */
  2356. fabric.warn = function() { };
  2357. /* eslint-disable */
  2358. if (typeof console !== 'undefined') {
  2359. ['log', 'warn'].forEach(function(methodName) {
  2360. if (typeof console[methodName] !== 'undefined' &&
  2361. typeof console[methodName].apply === 'function') {
  2362. fabric[methodName] = function() {
  2363. return console[methodName].apply(console, arguments);
  2364. };
  2365. }
  2366. });
  2367. }
  2368. /* eslint-enable */
  2369. (function() {
  2370. /**
  2371. * Changes value from one to another within certain period of time, invoking callbacks as value is being changed.
  2372. * @memberOf fabric.util
  2373. * @param {Object} [options] Animation options
  2374. * @param {Function} [options.onChange] Callback; invoked on every value change
  2375. * @param {Function} [options.onComplete] Callback; invoked when value change is completed
  2376. * @param {Number} [options.startValue=0] Starting value
  2377. * @param {Number} [options.endValue=100] Ending value
  2378. * @param {Number} [options.byValue=100] Value to modify the property by
  2379. * @param {Function} [options.easing] Easing function
  2380. * @param {Number} [options.duration=500] Duration of change (in ms)
  2381. */
  2382. function animate(options) {
  2383. requestAnimFrame(function(timestamp) {
  2384. options || (options = { });
  2385. var start = timestamp || +new Date(),
  2386. duration = options.duration || 500,
  2387. finish = start + duration, time,
  2388. onChange = options.onChange || function() { },
  2389. abort = options.abort || function() { return false; },
  2390. easing = options.easing || function(t, b, c, d) {return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;},
  2391. startValue = 'startValue' in options ? options.startValue : 0,
  2392. endValue = 'endValue' in options ? options.endValue : 100,
  2393. byValue = options.byValue || endValue - startValue;
  2394. options.onStart && options.onStart();
  2395. (function tick(ticktime) {
  2396. time = ticktime || +new Date();
  2397. var currentTime = time > finish ? duration : (time - start);
  2398. if (abort()) {
  2399. options.onComplete && options.onComplete();
  2400. return;
  2401. }
  2402. onChange(easing(currentTime, startValue, byValue, duration));
  2403. if (time > finish) {
  2404. options.onComplete && options.onComplete();
  2405. return;
  2406. }
  2407. requestAnimFrame(tick);
  2408. })(start);
  2409. });
  2410. }
  2411. var _requestAnimFrame = fabric.window.requestAnimationFrame ||
  2412. fabric.window.webkitRequestAnimationFrame ||
  2413. fabric.window.mozRequestAnimationFrame ||
  2414. fabric.window.oRequestAnimationFrame ||
  2415. fabric.window.msRequestAnimationFrame ||
  2416. function(callback) {
  2417. fabric.window.setTimeout(callback, 1000 / 60);
  2418. };
  2419. /**
  2420. * requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/
  2421. * In order to get a precise start time, `requestAnimFrame` should be called as an entry into the method
  2422. * @memberOf fabric.util
  2423. * @param {Function} callback Callback to invoke
  2424. * @param {DOMElement} element optional Element to associate with animation
  2425. */
  2426. function requestAnimFrame() {
  2427. return _requestAnimFrame.apply(fabric.window, arguments);
  2428. }
  2429. fabric.util.animate = animate;
  2430. fabric.util.requestAnimFrame = requestAnimFrame;
  2431. })();
  2432. (function() {
  2433. // Calculate an in-between color. Returns a "rgba()" string.
  2434. // Credit: Edwin Martin <edwin@bitstorm.org>
  2435. // http://www.bitstorm.org/jquery/color-animation/jquery.animate-colors.js
  2436. function calculateColor(begin, end, pos) {
  2437. var color = 'rgba('
  2438. + parseInt((begin[0] + pos * (end[0] - begin[0])), 10) + ','
  2439. + parseInt((begin[1] + pos * (end[1] - begin[1])), 10) + ','
  2440. + parseInt((begin[2] + pos * (end[2] - begin[2])), 10);
  2441. color += ',' + (begin && end ? parseFloat(begin[3] + pos * (end[3] - begin[3])) : 1);
  2442. color += ')';
  2443. return color;
  2444. }
  2445. /**
  2446. * Changes the color from one to another within certain period of time, invoking callbacks as value is being changed.
  2447. * @memberOf fabric.util
  2448. * @param {String} fromColor The starting color in hex or rgb(a) format.
  2449. * @param {String} toColor The starting color in hex or rgb(a) format.
  2450. * @param {Number} [duration] Duration of change (in ms).
  2451. * @param {Object} [options] Animation options
  2452. * @param {Function} [options.onChange] Callback; invoked on every value change
  2453. * @param {Function} [options.onComplete] Callback; invoked when value change is completed
  2454. * @param {Function} [options.colorEasing] Easing function. Note that this function only take two arguments (currentTime, duration). Thus the regular animation easing functions cannot be used.
  2455. */
  2456. function animateColor(fromColor, toColor, duration, options) {
  2457. var startColor = new fabric.Color(fromColor).getSource(),
  2458. endColor = new fabric.Color(toColor).getSource();
  2459. options = options || {};
  2460. fabric.util.animate(fabric.util.object.extend(options, {
  2461. duration: duration || 500,
  2462. startValue: startColor,
  2463. endValue: endColor,
  2464. byValue: endColor,
  2465. easing: function (currentTime, startValue, byValue, duration) {
  2466. var posValue = options.colorEasing
  2467. ? options.colorEasing(currentTime, duration)
  2468. : 1 - Math.cos(currentTime / duration * (Math.PI / 2));
  2469. return calculateColor(startValue, byValue, posValue);
  2470. }
  2471. }));
  2472. }
  2473. fabric.util.animateColor = animateColor;
  2474. })();
  2475. (function() {
  2476. function normalize(a, c, p, s) {
  2477. if (a < Math.abs(c)) {
  2478. a = c;
  2479. s = p / 4;
  2480. }
  2481. else {
  2482. //handle the 0/0 case:
  2483. if (c === 0 && a === 0) {
  2484. s = p / (2 * Math.PI) * Math.asin(1);
  2485. }
  2486. else {
  2487. s = p / (2 * Math.PI) * Math.asin(c / a);
  2488. }
  2489. }
  2490. return { a: a, c: c, p: p, s: s };
  2491. }
  2492. function elastic(opts, t, d) {
  2493. return opts.a *
  2494. Math.pow(2, 10 * (t -= 1)) *
  2495. Math.sin( (t * d - opts.s) * (2 * Math.PI) / opts.p );
  2496. }
  2497. /**
  2498. * Cubic easing out
  2499. * @memberOf fabric.util.ease
  2500. */
  2501. function easeOutCubic(t, b, c, d) {
  2502. return c * ((t = t / d - 1) * t * t + 1) + b;
  2503. }
  2504. /**
  2505. * Cubic easing in and out
  2506. * @memberOf fabric.util.ease
  2507. */
  2508. function easeInOutCubic(t, b, c, d) {
  2509. t /= d / 2;
  2510. if (t < 1) {
  2511. return c / 2 * t * t * t + b;
  2512. }
  2513. return c / 2 * ((t -= 2) * t * t + 2) + b;
  2514. }
  2515. /**
  2516. * Quartic easing in
  2517. * @memberOf fabric.util.ease
  2518. */
  2519. function easeInQuart(t, b, c, d) {
  2520. return c * (t /= d) * t * t * t + b;
  2521. }
  2522. /**
  2523. * Quartic easing out
  2524. * @memberOf fabric.util.ease
  2525. */
  2526. function easeOutQuart(t, b, c, d) {
  2527. return -c * ((t = t / d - 1) * t * t * t - 1) + b;
  2528. }
  2529. /**
  2530. * Quartic easing in and out
  2531. * @memberOf fabric.util.ease
  2532. */
  2533. function easeInOutQuart(t, b, c, d) {
  2534. t /= d / 2;
  2535. if (t < 1) {
  2536. return c / 2 * t * t * t * t + b;
  2537. }
  2538. return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
  2539. }
  2540. /**
  2541. * Quintic easing in
  2542. * @memberOf fabric.util.ease
  2543. */
  2544. function easeInQuint(t, b, c, d) {
  2545. return c * (t /= d) * t * t * t * t + b;
  2546. }
  2547. /**
  2548. * Quintic easing out
  2549. * @memberOf fabric.util.ease
  2550. */
  2551. function easeOutQuint(t, b, c, d) {
  2552. return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
  2553. }
  2554. /**
  2555. * Quintic easing in and out
  2556. * @memberOf fabric.util.ease
  2557. */
  2558. function easeInOutQuint(t, b, c, d) {
  2559. t /= d / 2;
  2560. if (t < 1) {
  2561. return c / 2 * t * t * t * t * t + b;
  2562. }
  2563. return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;
  2564. }
  2565. /**
  2566. * Sinusoidal easing in
  2567. * @memberOf fabric.util.ease
  2568. */
  2569. function easeInSine(t, b, c, d) {
  2570. return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;
  2571. }
  2572. /**
  2573. * Sinusoidal easing out
  2574. * @memberOf fabric.util.ease
  2575. */
  2576. function easeOutSine(t, b, c, d) {
  2577. return c * Math.sin(t / d * (Math.PI / 2)) + b;
  2578. }
  2579. /**
  2580. * Sinusoidal easing in and out
  2581. * @memberOf fabric.util.ease
  2582. */
  2583. function easeInOutSine(t, b, c, d) {
  2584. return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;
  2585. }
  2586. /**
  2587. * Exponential easing in
  2588. * @memberOf fabric.util.ease
  2589. */
  2590. function easeInExpo(t, b, c, d) {
  2591. return (t === 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b;
  2592. }
  2593. /**
  2594. * Exponential easing out
  2595. * @memberOf fabric.util.ease
  2596. */
  2597. function easeOutExpo(t, b, c, d) {
  2598. return (t === d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
  2599. }
  2600. /**
  2601. * Exponential easing in and out
  2602. * @memberOf fabric.util.ease
  2603. */
  2604. function easeInOutExpo(t, b, c, d) {
  2605. if (t === 0) {
  2606. return b;
  2607. }
  2608. if (t === d) {
  2609. return b + c;
  2610. }
  2611. t /= d / 2;
  2612. if (t < 1) {
  2613. return c / 2 * Math.pow(2, 10 * (t - 1)) + b;
  2614. }
  2615. return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b;
  2616. }
  2617. /**
  2618. * Circular easing in
  2619. * @memberOf fabric.util.ease
  2620. */
  2621. function easeInCirc(t, b, c, d) {
  2622. return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
  2623. }
  2624. /**
  2625. * Circular easing out
  2626. * @memberOf fabric.util.ease
  2627. */
  2628. function easeOutCirc(t, b, c, d) {
  2629. return c * Math.sqrt(1 - (t = t / d - 1) * t) + b;
  2630. }
  2631. /**
  2632. * Circular easing in and out
  2633. * @memberOf fabric.util.ease
  2634. */
  2635. function easeInOutCirc(t, b, c, d) {
  2636. t /= d / 2;
  2637. if (t < 1) {
  2638. return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b;
  2639. }
  2640. return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b;
  2641. }
  2642. /**
  2643. * Elastic easing in
  2644. * @memberOf fabric.util.ease
  2645. */
  2646. function easeInElastic(t, b, c, d) {
  2647. var s = 1.70158, p = 0, a = c;
  2648. if (t === 0) {
  2649. return b;
  2650. }
  2651. t /= d;
  2652. if (t === 1) {
  2653. return b + c;
  2654. }
  2655. if (!p) {
  2656. p = d * 0.3;
  2657. }
  2658. var opts = normalize(a, c, p, s);
  2659. return -elastic(opts, t, d) + b;
  2660. }
  2661. /**
  2662. * Elastic easing out
  2663. * @memberOf fabric.util.ease
  2664. */
  2665. function easeOutElastic(t, b, c, d) {
  2666. var s = 1.70158, p = 0, a = c;
  2667. if (t === 0) {
  2668. return b;
  2669. }
  2670. t /= d;
  2671. if (t === 1) {
  2672. return b + c;
  2673. }
  2674. if (!p) {
  2675. p = d * 0.3;
  2676. }
  2677. var opts = normalize(a, c, p, s);
  2678. return opts.a * Math.pow(2, -10 * t) * Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) + opts.c + b;
  2679. }
  2680. /**
  2681. * Elastic easing in and out
  2682. * @memberOf fabric.util.ease
  2683. */
  2684. function easeInOutElastic(t, b, c, d) {
  2685. var s = 1.70158, p = 0, a = c;
  2686. if (t === 0) {
  2687. return b;
  2688. }
  2689. t /= d / 2;
  2690. if (t === 2) {
  2691. return b + c;
  2692. }
  2693. if (!p) {
  2694. p = d * (0.3 * 1.5);
  2695. }
  2696. var opts = normalize(a, c, p, s);
  2697. if (t < 1) {
  2698. return -0.5 * elastic(opts, t, d) + b;
  2699. }
  2700. return opts.a * Math.pow(2, -10 * (t -= 1)) *
  2701. Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) * 0.5 + opts.c + b;
  2702. }
  2703. /**
  2704. * Backwards easing in
  2705. * @memberOf fabric.util.ease
  2706. */
  2707. function easeInBack(t, b, c, d, s) {
  2708. if (s === undefined) {
  2709. s = 1.70158;
  2710. }
  2711. return c * (t /= d) * t * ((s + 1) * t - s) + b;
  2712. }
  2713. /**
  2714. * Backwards easing out
  2715. * @memberOf fabric.util.ease
  2716. */
  2717. function easeOutBack(t, b, c, d, s) {
  2718. if (s === undefined) {
  2719. s = 1.70158;
  2720. }
  2721. return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
  2722. }
  2723. /**
  2724. * Backwards easing in and out
  2725. * @memberOf fabric.util.ease
  2726. */
  2727. function easeInOutBack(t, b, c, d, s) {
  2728. if (s === undefined) {
  2729. s = 1.70158;
  2730. }
  2731. t /= d / 2;
  2732. if (t < 1) {
  2733. return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
  2734. }
  2735. return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
  2736. }
  2737. /**
  2738. * Bouncing easing in
  2739. * @memberOf fabric.util.ease
  2740. */
  2741. function easeInBounce(t, b, c, d) {
  2742. return c - easeOutBounce (d - t, 0, c, d) + b;
  2743. }
  2744. /**
  2745. * Bouncing easing out
  2746. * @memberOf fabric.util.ease
  2747. */
  2748. function easeOutBounce(t, b, c, d) {
  2749. if ((t /= d) < (1 / 2.75)) {
  2750. return c * (7.5625 * t * t) + b;
  2751. }
  2752. else if (t < (2 / 2.75)) {
  2753. return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b;
  2754. }
  2755. else if (t < (2.5 / 2.75)) {
  2756. return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b;
  2757. }
  2758. else {
  2759. return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b;
  2760. }
  2761. }
  2762. /**
  2763. * Bouncing easing in and out
  2764. * @memberOf fabric.util.ease
  2765. */
  2766. function easeInOutBounce(t, b, c, d) {
  2767. if (t < d / 2) {
  2768. return easeInBounce (t * 2, 0, c, d) * 0.5 + b;
  2769. }
  2770. return easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b;
  2771. }
  2772. /**
  2773. * Easing functions
  2774. * See <a href="http://gizma.com/easing/">Easing Equations by Robert Penner</a>
  2775. * @namespace fabric.util.ease
  2776. */
  2777. fabric.util.ease = {
  2778. /**
  2779. * Quadratic easing in
  2780. * @memberOf fabric.util.ease
  2781. */
  2782. easeInQuad: function(t, b, c, d) {
  2783. return c * (t /= d) * t + b;
  2784. },
  2785. /**
  2786. * Quadratic easing out
  2787. * @memberOf fabric.util.ease
  2788. */
  2789. easeOutQuad: function(t, b, c, d) {
  2790. return -c * (t /= d) * (t - 2) + b;
  2791. },
  2792. /**
  2793. * Quadratic easing in and out
  2794. * @memberOf fabric.util.ease
  2795. */
  2796. easeInOutQuad: function(t, b, c, d) {
  2797. t /= (d / 2);
  2798. if (t < 1) {
  2799. return c / 2 * t * t + b;
  2800. }
  2801. return -c / 2 * ((--t) * (t - 2) - 1) + b;
  2802. },
  2803. /**
  2804. * Cubic easing in
  2805. * @memberOf fabric.util.ease
  2806. */
  2807. easeInCubic: function(t, b, c, d) {
  2808. return c * (t /= d) * t * t + b;
  2809. },
  2810. easeOutCubic: easeOutCubic,
  2811. easeInOutCubic: easeInOutCubic,
  2812. easeInQuart: easeInQuart,
  2813. easeOutQuart: easeOutQuart,
  2814. easeInOutQuart: easeInOutQuart,
  2815. easeInQuint: easeInQuint,
  2816. easeOutQuint: easeOutQuint,
  2817. easeInOutQuint: easeInOutQuint,
  2818. easeInSine: easeInSine,
  2819. easeOutSine: easeOutSine,
  2820. easeInOutSine: easeInOutSine,
  2821. easeInExpo: easeInExpo,
  2822. easeOutExpo: easeOutExpo,
  2823. easeInOutExpo: easeInOutExpo,
  2824. easeInCirc: easeInCirc,
  2825. easeOutCirc: easeOutCirc,
  2826. easeInOutCirc: easeInOutCirc,
  2827. easeInElastic: easeInElastic,
  2828. easeOutElastic: easeOutElastic,
  2829. easeInOutElastic: easeInOutElastic,
  2830. easeInBack: easeInBack,
  2831. easeOutBack: easeOutBack,
  2832. easeInOutBack: easeInOutBack,
  2833. easeInBounce: easeInBounce,
  2834. easeOutBounce: easeOutBounce,
  2835. easeInOutBounce: easeInOutBounce
  2836. };
  2837. })();
  2838. (function(global) {
  2839. 'use strict';
  2840. /**
  2841. * @name fabric
  2842. * @namespace
  2843. */
  2844. var fabric = global.fabric || (global.fabric = { }),
  2845. extend = fabric.util.object.extend,
  2846. clone = fabric.util.object.clone,
  2847. toFixed = fabric.util.toFixed,
  2848. parseUnit = fabric.util.parseUnit,
  2849. multiplyTransformMatrices = fabric.util.multiplyTransformMatrices,
  2850. reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/i,
  2851. reViewBoxTagNames = /^(symbol|image|marker|pattern|view|svg)$/i,
  2852. reNotAllowedAncestors = /^(?:pattern|defs|symbol|metadata|clipPath|mask)$/i,
  2853. reAllowedParents = /^(symbol|g|a|svg)$/i,
  2854. attributesMap = {
  2855. cx: 'left',
  2856. x: 'left',
  2857. r: 'radius',
  2858. cy: 'top',
  2859. y: 'top',
  2860. display: 'visible',
  2861. visibility: 'visible',
  2862. transform: 'transformMatrix',
  2863. 'fill-opacity': 'fillOpacity',
  2864. 'fill-rule': 'fillRule',
  2865. 'font-family': 'fontFamily',
  2866. 'font-size': 'fontSize',
  2867. 'font-style': 'fontStyle',
  2868. 'font-weight': 'fontWeight',
  2869. 'stroke-dasharray': 'strokeDashArray',
  2870. 'stroke-linecap': 'strokeLineCap',
  2871. 'stroke-linejoin': 'strokeLineJoin',
  2872. 'stroke-miterlimit': 'strokeMiterLimit',
  2873. 'stroke-opacity': 'strokeOpacity',
  2874. 'stroke-width': 'strokeWidth',
  2875. 'text-decoration': 'textDecoration',
  2876. 'text-anchor': 'originX',
  2877. opacity: 'opacity'
  2878. },
  2879. colorAttributes = {
  2880. stroke: 'strokeOpacity',
  2881. fill: 'fillOpacity'
  2882. };
  2883. fabric.cssRules = { };
  2884. fabric.gradientDefs = { };
  2885. function normalizeAttr(attr) {
  2886. // transform attribute names
  2887. if (attr in attributesMap) {
  2888. return attributesMap[attr];
  2889. }
  2890. return attr;
  2891. }
  2892. function normalizeValue(attr, value, parentAttributes, fontSize) {
  2893. var isArray = Object.prototype.toString.call(value) === '[object Array]',
  2894. parsed;
  2895. if ((attr === 'fill' || attr === 'stroke') && value === 'none') {
  2896. value = '';
  2897. }
  2898. else if (attr === 'strokeDashArray') {
  2899. if (value === 'none') {
  2900. value = null;
  2901. }
  2902. else {
  2903. value = value.replace(/,/g, ' ').split(/\s+/).map(function(n) {
  2904. return parseFloat(n);
  2905. });
  2906. }
  2907. }
  2908. else if (attr === 'transformMatrix') {
  2909. if (parentAttributes && parentAttributes.transformMatrix) {
  2910. value = multiplyTransformMatrices(
  2911. parentAttributes.transformMatrix, fabric.parseTransformAttribute(value));
  2912. }
  2913. else {
  2914. value = fabric.parseTransformAttribute(value);
  2915. }
  2916. }
  2917. else if (attr === 'visible') {
  2918. value = (value === 'none' || value === 'hidden') ? false : true;
  2919. // display=none on parent element always takes precedence over child element
  2920. if (parentAttributes && parentAttributes.visible === false) {
  2921. value = false;
  2922. }
  2923. }
  2924. else if (attr === 'opacity') {
  2925. value = parseFloat(value);
  2926. if (parentAttributes && typeof parentAttributes.opacity !== 'undefined') {
  2927. value *= parentAttributes.opacity;
  2928. }
  2929. }
  2930. else if (attr === 'originX' /* text-anchor */) {
  2931. value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center';
  2932. }
  2933. else {
  2934. parsed = isArray ? value.map(parseUnit) : parseUnit(value, fontSize);
  2935. }
  2936. return (!isArray && isNaN(parsed) ? value : parsed);
  2937. }
  2938. /**
  2939. * @private
  2940. * @param {Object} attributes Array of attributes to parse
  2941. */
  2942. function _setStrokeFillOpacity(attributes) {
  2943. for (var attr in colorAttributes) {
  2944. if (typeof attributes[colorAttributes[attr]] === 'undefined' || attributes[attr] === '') {
  2945. continue;
  2946. }
  2947. if (typeof attributes[attr] === 'undefined') {
  2948. if (!fabric.Object.prototype[attr]) {
  2949. continue;
  2950. }
  2951. attributes[attr] = fabric.Object.prototype[attr];
  2952. }
  2953. if (attributes[attr].indexOf('url(') === 0) {
  2954. continue;
  2955. }
  2956. var color = new fabric.Color(attributes[attr]);
  2957. attributes[attr] = color.setAlpha(toFixed(color.getAlpha() * attributes[colorAttributes[attr]], 2)).toRgba();
  2958. }
  2959. return attributes;
  2960. }
  2961. /**
  2962. * @private
  2963. */
  2964. function _getMultipleNodes(doc, nodeNames) {
  2965. var nodeName, nodeArray = [], nodeList;
  2966. for (var i = 0; i < nodeNames.length; i++) {
  2967. nodeName = nodeNames[i];
  2968. nodeList = doc.getElementsByTagName(nodeName);
  2969. nodeArray = nodeArray.concat(Array.prototype.slice.call(nodeList));
  2970. }
  2971. return nodeArray;
  2972. }
  2973. /**
  2974. * Parses "transform" attribute, returning an array of values
  2975. * @static
  2976. * @function
  2977. * @memberOf fabric
  2978. * @param {String} attributeValue String containing attribute value
  2979. * @return {Array} Array of 6 elements representing transformation matrix
  2980. */
  2981. fabric.parseTransformAttribute = (function() {
  2982. function rotateMatrix(matrix, args) {
  2983. var cos = Math.cos(args[0]), sin = Math.sin(args[0]),
  2984. x = 0, y = 0;
  2985. if (args.length === 3) {
  2986. x = args[1];
  2987. y = args[2];
  2988. }
  2989. matrix[0] = cos;
  2990. matrix[1] = sin;
  2991. matrix[2] = -sin;
  2992. matrix[3] = cos;
  2993. matrix[4] = x - (cos * x - sin * y);
  2994. matrix[5] = y - (sin * x + cos * y);
  2995. }
  2996. function scaleMatrix(matrix, args) {
  2997. var multiplierX = args[0],
  2998. multiplierY = (args.length === 2) ? args[1] : args[0];
  2999. matrix[0] = multiplierX;
  3000. matrix[3] = multiplierY;
  3001. }
  3002. function skewMatrix(matrix, args, pos) {
  3003. matrix[pos] = Math.tan(fabric.util.degreesToRadians(args[0]));
  3004. }
  3005. function translateMatrix(matrix, args) {
  3006. matrix[4] = args[0];
  3007. if (args.length === 2) {
  3008. matrix[5] = args[1];
  3009. }
  3010. }
  3011. // identity matrix
  3012. var iMatrix = [
  3013. 1, // a
  3014. 0, // b
  3015. 0, // c
  3016. 1, // d
  3017. 0, // e
  3018. 0 // f
  3019. ],
  3020. // == begin transform regexp
  3021. number = fabric.reNum,
  3022. commaWsp = '(?:\\s+,?\\s*|,\\s*)',
  3023. skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))',
  3024. skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))',
  3025. rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' +
  3026. commaWsp + '(' + number + ')' +
  3027. commaWsp + '(' + number + '))?\\s*\\))',
  3028. scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' +
  3029. commaWsp + '(' + number + '))?\\s*\\))',
  3030. translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' +
  3031. commaWsp + '(' + number + '))?\\s*\\))',
  3032. matrix = '(?:(matrix)\\s*\\(\\s*' +
  3033. '(' + number + ')' + commaWsp +
  3034. '(' + number + ')' + commaWsp +
  3035. '(' + number + ')' + commaWsp +
  3036. '(' + number + ')' + commaWsp +
  3037. '(' + number + ')' + commaWsp +
  3038. '(' + number + ')' +
  3039. '\\s*\\))',
  3040. transform = '(?:' +
  3041. matrix + '|' +
  3042. translate + '|' +
  3043. scale + '|' +
  3044. rotate + '|' +
  3045. skewX + '|' +
  3046. skewY +
  3047. ')',
  3048. transforms = '(?:' + transform + '(?:' + commaWsp + '*' + transform + ')*' + ')',
  3049. transformList = '^\\s*(?:' + transforms + '?)\\s*$',
  3050. // http://www.w3.org/TR/SVG/coords.html#TransformAttribute
  3051. reTransformList = new RegExp(transformList),
  3052. // == end transform regexp
  3053. reTransform = new RegExp(transform, 'g');
  3054. return function(attributeValue) {
  3055. // start with identity matrix
  3056. var matrix = iMatrix.concat(),
  3057. matrices = [];
  3058. // return if no argument was given or
  3059. // an argument does not match transform attribute regexp
  3060. if (!attributeValue || (attributeValue && !reTransformList.test(attributeValue))) {
  3061. return matrix;
  3062. }
  3063. attributeValue.replace(reTransform, function(match) {
  3064. var m = new RegExp(transform).exec(match).filter(function (match) {
  3065. // match !== '' && match != null
  3066. return (!!match);
  3067. }),
  3068. operation = m[1],
  3069. args = m.slice(2).map(parseFloat);
  3070. switch (operation) {
  3071. case 'translate':
  3072. translateMatrix(matrix, args);
  3073. break;
  3074. case 'rotate':
  3075. args[0] = fabric.util.degreesToRadians(args[0]);
  3076. rotateMatrix(matrix, args);
  3077. break;
  3078. case 'scale':
  3079. scaleMatrix(matrix, args);
  3080. break;
  3081. case 'skewX':
  3082. skewMatrix(matrix, args, 2);
  3083. break;
  3084. case 'skewY':
  3085. skewMatrix(matrix, args, 1);
  3086. break;
  3087. case 'matrix':
  3088. matrix = args;
  3089. break;
  3090. }
  3091. // snapshot current matrix into matrices array
  3092. matrices.push(matrix.concat());
  3093. // reset
  3094. matrix = iMatrix.concat();
  3095. });
  3096. var combinedMatrix = matrices[0];
  3097. while (matrices.length > 1) {
  3098. matrices.shift();
  3099. combinedMatrix = fabric.util.multiplyTransformMatrices(combinedMatrix, matrices[0]);
  3100. }
  3101. return combinedMatrix;
  3102. };
  3103. })();
  3104. /**
  3105. * @private
  3106. */
  3107. function parseStyleString(style, oStyle) {
  3108. var attr, value;
  3109. style.replace(/;\s*$/, '').split(';').forEach(function (chunk) {
  3110. var pair = chunk.split(':');
  3111. attr = pair[0].trim().toLowerCase();
  3112. value = pair[1].trim();
  3113. oStyle[attr] = value;
  3114. });
  3115. }
  3116. /**
  3117. * @private
  3118. */
  3119. function parseStyleObject(style, oStyle) {
  3120. var attr, value;
  3121. for (var prop in style) {
  3122. if (typeof style[prop] === 'undefined') {
  3123. continue;
  3124. }
  3125. attr = prop.toLowerCase();
  3126. value = style[prop];
  3127. oStyle[attr] = value;
  3128. }
  3129. }
  3130. /**
  3131. * @private
  3132. */
  3133. function getGlobalStylesForElement(element, svgUid) {
  3134. var styles = { };
  3135. for (var rule in fabric.cssRules[svgUid]) {
  3136. if (elementMatchesRule(element, rule.split(' '))) {
  3137. for (var property in fabric.cssRules[svgUid][rule]) {
  3138. styles[property] = fabric.cssRules[svgUid][rule][property];
  3139. }
  3140. }
  3141. }
  3142. return styles;
  3143. }
  3144. /**
  3145. * @private
  3146. */
  3147. function elementMatchesRule(element, selectors) {
  3148. var firstMatching, parentMatching = true;
  3149. //start from rightmost selector.
  3150. firstMatching = selectorMatches(element, selectors.pop());
  3151. if (firstMatching && selectors.length) {
  3152. parentMatching = doesSomeParentMatch(element, selectors);
  3153. }
  3154. return firstMatching && parentMatching && (selectors.length === 0);
  3155. }
  3156. function doesSomeParentMatch(element, selectors) {
  3157. var selector, parentMatching = true;
  3158. while (element.parentNode && element.parentNode.nodeType === 1 && selectors.length) {
  3159. if (parentMatching) {
  3160. selector = selectors.pop();
  3161. }
  3162. element = element.parentNode;
  3163. parentMatching = selectorMatches(element, selector);
  3164. }
  3165. return selectors.length === 0;
  3166. }
  3167. /**
  3168. * @private
  3169. */
  3170. function selectorMatches(element, selector) {
  3171. var nodeName = element.nodeName,
  3172. classNames = element.getAttribute('class'),
  3173. id = element.getAttribute('id'), matcher;
  3174. // i check if a selector matches slicing away part from it.
  3175. // if i get empty string i should match
  3176. matcher = new RegExp('^' + nodeName, 'i');
  3177. selector = selector.replace(matcher, '');
  3178. if (id && selector.length) {
  3179. matcher = new RegExp('#' + id + '(?![a-zA-Z\\-]+)', 'i');
  3180. selector = selector.replace(matcher, '');
  3181. }
  3182. if (classNames && selector.length) {
  3183. classNames = classNames.split(' ');
  3184. for (var i = classNames.length; i--;) {
  3185. matcher = new RegExp('\\.' + classNames[i] + '(?![a-zA-Z\\-]+)', 'i');
  3186. selector = selector.replace(matcher, '');
  3187. }
  3188. }
  3189. return selector.length === 0;
  3190. }
  3191. /**
  3192. * @private
  3193. * to support IE8 missing getElementById on SVGdocument
  3194. */
  3195. function elementById(doc, id) {
  3196. var el;
  3197. doc.getElementById && (el = doc.getElementById(id));
  3198. if (el) {
  3199. return el;
  3200. }
  3201. var node, i, nodelist = doc.getElementsByTagName('*');
  3202. for (i = 0; i < nodelist.length; i++) {
  3203. node = nodelist[i];
  3204. if (id === node.getAttribute('id')) {
  3205. return node;
  3206. }
  3207. }
  3208. }
  3209. /**
  3210. * @private
  3211. */
  3212. function parseUseDirectives(doc) {
  3213. var nodelist = _getMultipleNodes(doc, ['use', 'svg:use']), i = 0;
  3214. while (nodelist.length && i < nodelist.length) {
  3215. var el = nodelist[i],
  3216. xlink = el.getAttribute('xlink:href').substr(1),
  3217. x = el.getAttribute('x') || 0,
  3218. y = el.getAttribute('y') || 0,
  3219. el2 = elementById(doc, xlink).cloneNode(true),
  3220. currentTrans = (el2.getAttribute('transform') || '') + ' translate(' + x + ', ' + y + ')',
  3221. parentNode, oldLength = nodelist.length, attr, j, attrs, l;
  3222. applyViewboxTransform(el2);
  3223. if (/^svg$/i.test(el2.nodeName)) {
  3224. var el3 = el2.ownerDocument.createElement('g');
  3225. for (j = 0, attrs = el2.attributes, l = attrs.length; j < l; j++) {
  3226. attr = attrs.item(j);
  3227. el3.setAttribute(attr.nodeName, attr.nodeValue);
  3228. }
  3229. // el2.firstChild != null
  3230. while (el2.firstChild) {
  3231. el3.appendChild(el2.firstChild);
  3232. }
  3233. el2 = el3;
  3234. }
  3235. for (j = 0, attrs = el.attributes, l = attrs.length; j < l; j++) {
  3236. attr = attrs.item(j);
  3237. if (attr.nodeName === 'x' || attr.nodeName === 'y' || attr.nodeName === 'xlink:href') {
  3238. continue;
  3239. }
  3240. if (attr.nodeName === 'transform') {
  3241. currentTrans = attr.nodeValue + ' ' + currentTrans;
  3242. }
  3243. else {
  3244. el2.setAttribute(attr.nodeName, attr.nodeValue);
  3245. }
  3246. }
  3247. el2.setAttribute('transform', currentTrans);
  3248. el2.setAttribute('instantiated_by_use', '1');
  3249. el2.removeAttribute('id');
  3250. parentNode = el.parentNode;
  3251. parentNode.replaceChild(el2, el);
  3252. // some browsers do not shorten nodelist after replaceChild (IE8)
  3253. if (nodelist.length === oldLength) {
  3254. i++;
  3255. }
  3256. }
  3257. }
  3258. // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
  3259. // matches, e.g.: +14.56e-12, etc.
  3260. var reViewBoxAttrValue = new RegExp(
  3261. '^' +
  3262. '\\s*(' + fabric.reNum + '+)\\s*,?' +
  3263. '\\s*(' + fabric.reNum + '+)\\s*,?' +
  3264. '\\s*(' + fabric.reNum + '+)\\s*,?' +
  3265. '\\s*(' + fabric.reNum + '+)\\s*' +
  3266. '$'
  3267. );
  3268. /**
  3269. * Add a <g> element that envelop all child elements and makes the viewbox transformMatrix descend on all elements
  3270. */
  3271. function applyViewboxTransform(element) {
  3272. var viewBoxAttr = element.getAttribute('viewBox'),
  3273. scaleX = 1,
  3274. scaleY = 1,
  3275. minX = 0,
  3276. minY = 0,
  3277. viewBoxWidth, viewBoxHeight, matrix, el,
  3278. widthAttr = element.getAttribute('width'),
  3279. heightAttr = element.getAttribute('height'),
  3280. x = element.getAttribute('x') || 0,
  3281. y = element.getAttribute('y') || 0,
  3282. preserveAspectRatio = element.getAttribute('preserveAspectRatio') || '',
  3283. missingViewBox = (!viewBoxAttr || !reViewBoxTagNames.test(element.nodeName)
  3284. || !(viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))),
  3285. missingDimAttr = (!widthAttr || !heightAttr || widthAttr === '100%' || heightAttr === '100%'),
  3286. toBeParsed = missingViewBox && missingDimAttr,
  3287. parsedDim = { }, translateMatrix = '';
  3288. parsedDim.width = 0;
  3289. parsedDim.height = 0;
  3290. parsedDim.toBeParsed = toBeParsed;
  3291. if (toBeParsed) {
  3292. return parsedDim;
  3293. }
  3294. if (missingViewBox) {
  3295. parsedDim.width = parseUnit(widthAttr);
  3296. parsedDim.height = parseUnit(heightAttr);
  3297. return parsedDim;
  3298. }
  3299. minX = -parseFloat(viewBoxAttr[1]);
  3300. minY = -parseFloat(viewBoxAttr[2]);
  3301. viewBoxWidth = parseFloat(viewBoxAttr[3]);
  3302. viewBoxHeight = parseFloat(viewBoxAttr[4]);
  3303. if (!missingDimAttr) {
  3304. parsedDim.width = parseUnit(widthAttr);
  3305. parsedDim.height = parseUnit(heightAttr);
  3306. scaleX = parsedDim.width / viewBoxWidth;
  3307. scaleY = parsedDim.height / viewBoxHeight;
  3308. }
  3309. else {
  3310. parsedDim.width = viewBoxWidth;
  3311. parsedDim.height = viewBoxHeight;
  3312. }
  3313. // default is to preserve aspect ratio
  3314. preserveAspectRatio = fabric.util.parsePreserveAspectRatioAttribute(preserveAspectRatio);
  3315. if (preserveAspectRatio.alignX !== 'none') {
  3316. //translate all container for the effect of Mid, Min, Max
  3317. scaleY = scaleX = (scaleX > scaleY ? scaleY : scaleX);
  3318. }
  3319. if (scaleX === 1 && scaleY === 1 && minX === 0 && minY === 0 && x === 0 && y === 0) {
  3320. return parsedDim;
  3321. }
  3322. if (x || y) {
  3323. translateMatrix = ' translate(' + parseUnit(x) + ' ' + parseUnit(y) + ') ';
  3324. }
  3325. matrix = translateMatrix + ' matrix(' + scaleX +
  3326. ' 0' +
  3327. ' 0 ' +
  3328. scaleY + ' ' +
  3329. (minX * scaleX) + ' ' +
  3330. (minY * scaleY) + ') ';
  3331. if (element.nodeName === 'svg') {
  3332. el = element.ownerDocument.createElement('g');
  3333. // element.firstChild != null
  3334. while (element.firstChild) {
  3335. el.appendChild(element.firstChild);
  3336. }
  3337. element.appendChild(el);
  3338. }
  3339. else {
  3340. el = element;
  3341. matrix = el.getAttribute('transform') + matrix;
  3342. }
  3343. el.setAttribute('transform', matrix);
  3344. return parsedDim;
  3345. }
  3346. function hasAncestorWithNodeName(element, nodeName) {
  3347. while (element && (element = element.parentNode)) {
  3348. if (element.nodeName && nodeName.test(element.nodeName.replace('svg:', ''))
  3349. && !element.getAttribute('instantiated_by_use')) {
  3350. return true;
  3351. }
  3352. }
  3353. return false;
  3354. }
  3355. /**
  3356. * Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback
  3357. * @static
  3358. * @function
  3359. * @memberOf fabric
  3360. * @param {SVGDocument} doc SVG document to parse
  3361. * @param {Function} callback Callback to call when parsing is finished;
  3362. * It's being passed an array of elements (parsed from a document).
  3363. * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
  3364. * @param {Object} [parsingOptions] options for parsing document
  3365. * @param {String} [parsingOptions.crossOrigin] crossOrigin settings
  3366. */
  3367. fabric.parseSVGDocument = function(doc, callback, reviver, parsingOptions) {
  3368. if (!doc) {
  3369. return;
  3370. }
  3371. parseUseDirectives(doc);
  3372. var svgUid = fabric.Object.__uid++,
  3373. options = applyViewboxTransform(doc),
  3374. descendants = fabric.util.toArray(doc.getElementsByTagName('*'));
  3375. options.crossOrigin = parsingOptions && parsingOptions.crossOrigin;
  3376. options.svgUid = svgUid;
  3377. if (descendants.length === 0 && fabric.isLikelyNode) {
  3378. // we're likely in node, where "o3-xml" library fails to gEBTN("*")
  3379. // https://github.com/ajaxorg/node-o3-xml/issues/21
  3380. descendants = doc.selectNodes('//*[name(.)!="svg"]');
  3381. var arr = [];
  3382. for (var i = 0, len = descendants.length; i < len; i++) {
  3383. arr[i] = descendants[i];
  3384. }
  3385. descendants = arr;
  3386. }
  3387. var elements = descendants.filter(function(el) {
  3388. applyViewboxTransform(el);
  3389. return reAllowedSVGTagNames.test(el.nodeName.replace('svg:', '')) &&
  3390. !hasAncestorWithNodeName(el, reNotAllowedAncestors); // http://www.w3.org/TR/SVG/struct.html#DefsElement
  3391. });
  3392. if (!elements || (elements && !elements.length)) {
  3393. callback && callback([], {});
  3394. return;
  3395. }
  3396. fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc);
  3397. fabric.cssRules[svgUid] = fabric.getCSSRules(doc);
  3398. // Precedence of rules: style > class > attribute
  3399. fabric.parseElements(elements, function(instances) {
  3400. if (callback) {
  3401. callback(instances, options);
  3402. }
  3403. }, clone(options), reviver, parsingOptions);
  3404. };
  3405. var reFontDeclaration = new RegExp(
  3406. '(normal|italic)?\\s*(normal|small-caps)?\\s*' +
  3407. '(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\\s*(' +
  3408. fabric.reNum +
  3409. '(?:px|cm|mm|em|pt|pc|in)*)(?:\\/(normal|' + fabric.reNum + '))?\\s+(.*)');
  3410. extend(fabric, {
  3411. /**
  3412. * Parses a short font declaration, building adding its properties to a style object
  3413. * @static
  3414. * @function
  3415. * @memberOf fabric
  3416. * @param {String} value font declaration
  3417. * @param {Object} oStyle definition
  3418. */
  3419. parseFontDeclaration: function(value, oStyle) {
  3420. var match = value.match(reFontDeclaration);
  3421. if (!match) {
  3422. return;
  3423. }
  3424. var fontStyle = match[1],
  3425. // font variant is not used
  3426. // fontVariant = match[2],
  3427. fontWeight = match[3],
  3428. fontSize = match[4],
  3429. lineHeight = match[5],
  3430. fontFamily = match[6];
  3431. if (fontStyle) {
  3432. oStyle.fontStyle = fontStyle;
  3433. }
  3434. if (fontWeight) {
  3435. oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight);
  3436. }
  3437. if (fontSize) {
  3438. oStyle.fontSize = parseUnit(fontSize);
  3439. }
  3440. if (fontFamily) {
  3441. oStyle.fontFamily = fontFamily;
  3442. }
  3443. if (lineHeight) {
  3444. oStyle.lineHeight = lineHeight === 'normal' ? 1 : lineHeight;
  3445. }
  3446. },
  3447. /**
  3448. * Parses an SVG document, returning all of the gradient declarations found in it
  3449. * @static
  3450. * @function
  3451. * @memberOf fabric
  3452. * @param {SVGDocument} doc SVG document to parse
  3453. * @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element
  3454. */
  3455. getGradientDefs: function(doc) {
  3456. var tagArray = [
  3457. 'linearGradient',
  3458. 'radialGradient',
  3459. 'svg:linearGradient',
  3460. 'svg:radialGradient'],
  3461. elList = _getMultipleNodes(doc, tagArray),
  3462. el, j = 0, id, xlink,
  3463. gradientDefs = { }, idsToXlinkMap = { };
  3464. j = elList.length;
  3465. while (j--) {
  3466. el = elList[j];
  3467. xlink = el.getAttribute('xlink:href');
  3468. id = el.getAttribute('id');
  3469. if (xlink) {
  3470. idsToXlinkMap[id] = xlink.substr(1);
  3471. }
  3472. gradientDefs[id] = el;
  3473. }
  3474. for (id in idsToXlinkMap) {
  3475. var el2 = gradientDefs[idsToXlinkMap[id]].cloneNode(true);
  3476. el = gradientDefs[id];
  3477. while (el2.firstChild) {
  3478. el.appendChild(el2.firstChild);
  3479. }
  3480. }
  3481. return gradientDefs;
  3482. },
  3483. /**
  3484. * Returns an object of attributes' name/value, given element and an array of attribute names;
  3485. * Parses parent "g" nodes recursively upwards.
  3486. * @static
  3487. * @memberOf fabric
  3488. * @param {DOMElement} element Element to parse
  3489. * @param {Array} attributes Array of attributes to parse
  3490. * @return {Object} object containing parsed attributes' names/values
  3491. */
  3492. parseAttributes: function(element, attributes, svgUid) {
  3493. if (!element) {
  3494. return;
  3495. }
  3496. var value,
  3497. parentAttributes = { },
  3498. fontSize;
  3499. if (typeof svgUid === 'undefined') {
  3500. svgUid = element.getAttribute('svgUid');
  3501. }
  3502. // if there's a parent container (`g` or `a` or `symbol` node), parse its attributes recursively upwards
  3503. if (element.parentNode && reAllowedParents.test(element.parentNode.nodeName)) {
  3504. parentAttributes = fabric.parseAttributes(element.parentNode, attributes, svgUid);
  3505. }
  3506. fontSize = (parentAttributes && parentAttributes.fontSize ) ||
  3507. element.getAttribute('font-size') || fabric.Text.DEFAULT_SVG_FONT_SIZE;
  3508. var ownAttributes = attributes.reduce(function(memo, attr) {
  3509. value = element.getAttribute(attr);
  3510. if (value) { // eslint-disable-line
  3511. memo[attr] = value;
  3512. }
  3513. return memo;
  3514. }, { });
  3515. // add values parsed from style, which take precedence over attributes
  3516. // (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes)
  3517. ownAttributes = extend(ownAttributes,
  3518. extend(getGlobalStylesForElement(element, svgUid), fabric.parseStyleAttribute(element)));
  3519. var normalizedAttr, normalizedValue, normalizedStyle = {};
  3520. for (var attr in ownAttributes) {
  3521. normalizedAttr = normalizeAttr(attr);
  3522. normalizedValue = normalizeValue(normalizedAttr, ownAttributes[attr], parentAttributes, fontSize);
  3523. normalizedStyle[normalizedAttr] = normalizedValue;
  3524. }
  3525. if (normalizedStyle && normalizedStyle.font) {
  3526. fabric.parseFontDeclaration(normalizedStyle.font, normalizedStyle);
  3527. }
  3528. var mergedAttrs = extend(parentAttributes, normalizedStyle);
  3529. return reAllowedParents.test(element.nodeName) ? mergedAttrs : _setStrokeFillOpacity(mergedAttrs);
  3530. },
  3531. /**
  3532. * Transforms an array of svg elements to corresponding fabric.* instances
  3533. * @static
  3534. * @memberOf fabric
  3535. * @param {Array} elements Array of elements to parse
  3536. * @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements)
  3537. * @param {Object} [options] Options object
  3538. * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
  3539. */
  3540. parseElements: function(elements, callback, options, reviver, parsingOptions) {
  3541. new fabric.ElementsParser(elements, callback, options, reviver, parsingOptions).parse();
  3542. },
  3543. /**
  3544. * Parses "style" attribute, retuning an object with values
  3545. * @static
  3546. * @memberOf fabric
  3547. * @param {SVGElement} element Element to parse
  3548. * @return {Object} Objects with values parsed from style attribute of an element
  3549. */
  3550. parseStyleAttribute: function(element) {
  3551. var oStyle = { },
  3552. style = element.getAttribute('style');
  3553. if (!style) {
  3554. return oStyle;
  3555. }
  3556. if (typeof style === 'string') {
  3557. parseStyleString(style, oStyle);
  3558. }
  3559. else {
  3560. parseStyleObject(style, oStyle);
  3561. }
  3562. return oStyle;
  3563. },
  3564. /**
  3565. * Parses "points" attribute, returning an array of values
  3566. * @static
  3567. * @memberOf fabric
  3568. * @param {String} points points attribute string
  3569. * @return {Array} array of points
  3570. */
  3571. parsePointsAttribute: function(points) {
  3572. // points attribute is required and must not be empty
  3573. if (!points) {
  3574. return null;
  3575. }
  3576. // replace commas with whitespace and remove bookending whitespace
  3577. points = points.replace(/,/g, ' ').trim();
  3578. points = points.split(/\s+/);
  3579. var parsedPoints = [], i, len;
  3580. i = 0;
  3581. len = points.length;
  3582. for (; i < len; i += 2) {
  3583. parsedPoints.push({
  3584. x: parseFloat(points[i]),
  3585. y: parseFloat(points[i + 1])
  3586. });
  3587. }
  3588. // odd number of points is an error
  3589. // if (parsedPoints.length % 2 !== 0) {
  3590. // return null;
  3591. // }
  3592. return parsedPoints;
  3593. },
  3594. /**
  3595. * Returns CSS rules for a given SVG document
  3596. * @static
  3597. * @function
  3598. * @memberOf fabric
  3599. * @param {SVGDocument} doc SVG document to parse
  3600. * @return {Object} CSS rules of this document
  3601. */
  3602. getCSSRules: function(doc) {
  3603. var styles = doc.getElementsByTagName('style'),
  3604. allRules = { }, rules;
  3605. // very crude parsing of style contents
  3606. for (var i = 0, len = styles.length; i < len; i++) {
  3607. // IE9 doesn't support textContent, but provides text instead.
  3608. var styleContents = styles[i].textContent || styles[i].text;
  3609. // remove comments
  3610. styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, '');
  3611. if (styleContents.trim() === '') {
  3612. continue;
  3613. }
  3614. rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g);
  3615. rules = rules.map(function(rule) { return rule.trim(); });
  3616. rules.forEach(function(rule) {
  3617. var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/),
  3618. ruleObj = { }, declaration = match[2].trim(),
  3619. propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/);
  3620. for (var i = 0, len = propertyValuePairs.length; i < len; i++) {
  3621. var pair = propertyValuePairs[i].split(/\s*:\s*/),
  3622. property = pair[0],
  3623. value = pair[1];
  3624. ruleObj[property] = value;
  3625. }
  3626. rule = match[1];
  3627. rule.split(',').forEach(function(_rule) {
  3628. _rule = _rule.replace(/^svg/i, '').trim();
  3629. if (_rule === '') {
  3630. return;
  3631. }
  3632. if (allRules[_rule]) {
  3633. fabric.util.object.extend(allRules[_rule], ruleObj);
  3634. }
  3635. else {
  3636. allRules[_rule] = fabric.util.object.clone(ruleObj);
  3637. }
  3638. });
  3639. });
  3640. }
  3641. return allRules;
  3642. },
  3643. /**
  3644. * Takes url corresponding to an SVG document, and parses it into a set of fabric objects.
  3645. * Note that SVG is fetched via XMLHttpRequest, so it needs to conform to SOP (Same Origin Policy)
  3646. * @memberOf fabric
  3647. * @param {String} url
  3648. * @param {Function} callback
  3649. * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
  3650. * @param {Object} [options] Object containing options for parsing
  3651. * @param {String} [options.crossOrigin] crossOrigin crossOrigin setting to use for external resources
  3652. */
  3653. loadSVGFromURL: function(url, callback, reviver, options) {
  3654. url = url.replace(/^\n\s*/, '').trim();
  3655. new fabric.util.request(url, {
  3656. method: 'get',
  3657. onComplete: onComplete
  3658. });
  3659. function onComplete(r) {
  3660. var xml = r.responseXML;
  3661. if (xml && !xml.documentElement && fabric.window.ActiveXObject && r.responseText) {
  3662. xml = new ActiveXObject('Microsoft.XMLDOM');
  3663. xml.async = 'false';
  3664. //IE chokes on DOCTYPE
  3665. xml.loadXML(r.responseText.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, ''));
  3666. }
  3667. if (!xml || !xml.documentElement) {
  3668. callback && callback(null);
  3669. }
  3670. fabric.parseSVGDocument(xml.documentElement, function (results, _options) {
  3671. callback && callback(results, _options);
  3672. }, reviver, options);
  3673. }
  3674. },
  3675. /**
  3676. * Takes string corresponding to an SVG document, and parses it into a set of fabric objects
  3677. * @memberOf fabric
  3678. * @param {String} string
  3679. * @param {Function} callback
  3680. * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
  3681. * @param {Object} [options] Object containing options for parsing
  3682. * @param {String} [options.crossOrigin] crossOrigin crossOrigin setting to use for external resources
  3683. */
  3684. loadSVGFromString: function(string, callback, reviver, options) {
  3685. string = string.trim();
  3686. var doc;
  3687. if (typeof DOMParser !== 'undefined') {
  3688. var parser = new DOMParser();
  3689. if (parser && parser.parseFromString) {
  3690. doc = parser.parseFromString(string, 'text/xml');
  3691. }
  3692. }
  3693. else if (fabric.window.ActiveXObject) {
  3694. doc = new ActiveXObject('Microsoft.XMLDOM');
  3695. doc.async = 'false';
  3696. // IE chokes on DOCTYPE
  3697. doc.loadXML(string.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, ''));
  3698. }
  3699. fabric.parseSVGDocument(doc.documentElement, function (results, _options) {
  3700. callback(results, _options);
  3701. }, reviver, options);
  3702. }
  3703. });
  3704. })(typeof exports !== 'undefined' ? exports : this);
  3705. fabric.ElementsParser = function(elements, callback, options, reviver, parsingOptions) {
  3706. this.elements = elements;
  3707. this.callback = callback;
  3708. this.options = options;
  3709. this.reviver = reviver;
  3710. this.svgUid = (options && options.svgUid) || 0;
  3711. this.parsingOptions = parsingOptions;
  3712. };
  3713. fabric.ElementsParser.prototype.parse = function() {
  3714. this.instances = new Array(this.elements.length);
  3715. this.numElements = this.elements.length;
  3716. this.createObjects();
  3717. };
  3718. fabric.ElementsParser.prototype.createObjects = function() {
  3719. for (var i = 0, len = this.elements.length; i < len; i++) {
  3720. this.elements[i].setAttribute('svgUid', this.svgUid);
  3721. (function(_obj, i) {
  3722. setTimeout(function() {
  3723. _obj.createObject(_obj.elements[i], i);
  3724. }, 0);
  3725. })(this, i);
  3726. }
  3727. };
  3728. fabric.ElementsParser.prototype.createObject = function(el, index) {
  3729. var klass = fabric[fabric.util.string.capitalize(el.tagName.replace('svg:', ''))];
  3730. if (klass && klass.fromElement) {
  3731. try {
  3732. this._createObject(klass, el, index);
  3733. }
  3734. catch (err) {
  3735. fabric.log(err);
  3736. }
  3737. }
  3738. else {
  3739. this.checkIfDone();
  3740. }
  3741. };
  3742. fabric.ElementsParser.prototype._createObject = function(klass, el, index) {
  3743. if (klass.async) {
  3744. klass.fromElement(el, this.createCallback(index, el), this.options);
  3745. }
  3746. else {
  3747. var obj = klass.fromElement(el, this.options);
  3748. this.resolveGradient(obj, 'fill');
  3749. this.resolveGradient(obj, 'stroke');
  3750. this.reviver && this.reviver(el, obj);
  3751. this.instances[index] = obj;
  3752. this.checkIfDone();
  3753. }
  3754. };
  3755. fabric.ElementsParser.prototype.createCallback = function(index, el) {
  3756. var _this = this;
  3757. return function(obj) {
  3758. _this.resolveGradient(obj, 'fill');
  3759. _this.resolveGradient(obj, 'stroke');
  3760. _this.reviver && _this.reviver(el, obj);
  3761. _this.instances[index] = obj;
  3762. _this.checkIfDone();
  3763. };
  3764. };
  3765. fabric.ElementsParser.prototype.resolveGradient = function(obj, property) {
  3766. var instanceFillValue = obj.get(property);
  3767. if (!(/^url\(/).test(instanceFillValue)) {
  3768. return;
  3769. }
  3770. var gradientId = instanceFillValue.slice(5, instanceFillValue.length - 1);
  3771. if (fabric.gradientDefs[this.svgUid][gradientId]) {
  3772. obj.set(property,
  3773. fabric.Gradient.fromElement(fabric.gradientDefs[this.svgUid][gradientId], obj));
  3774. }
  3775. };
  3776. fabric.ElementsParser.prototype.checkIfDone = function() {
  3777. if (--this.numElements === 0) {
  3778. this.instances = this.instances.filter(function(el) {
  3779. // eslint-disable-next-line no-eq-null, eqeqeq
  3780. return el != null;
  3781. });
  3782. this.callback(this.instances);
  3783. }
  3784. };
  3785. (function(global) {
  3786. 'use strict';
  3787. /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */
  3788. var fabric = global.fabric || (global.fabric = { });
  3789. if (fabric.Point) {
  3790. fabric.warn('fabric.Point is already defined');
  3791. return;
  3792. }
  3793. fabric.Point = Point;
  3794. /**
  3795. * Point class
  3796. * @class fabric.Point
  3797. * @memberOf fabric
  3798. * @constructor
  3799. * @param {Number} x
  3800. * @param {Number} y
  3801. * @return {fabric.Point} thisArg
  3802. */
  3803. function Point(x, y) {
  3804. this.x = x;
  3805. this.y = y;
  3806. }
  3807. Point.prototype = /** @lends fabric.Point.prototype */ {
  3808. type: 'point',
  3809. constructor: Point,
  3810. /**
  3811. * Adds another point to this one and returns another one
  3812. * @param {fabric.Point} that
  3813. * @return {fabric.Point} new Point instance with added values
  3814. */
  3815. add: function (that) {
  3816. return new Point(this.x + that.x, this.y + that.y);
  3817. },
  3818. /**
  3819. * Adds another point to this one
  3820. * @param {fabric.Point} that
  3821. * @return {fabric.Point} thisArg
  3822. * @chainable
  3823. */
  3824. addEquals: function (that) {
  3825. this.x += that.x;
  3826. this.y += that.y;
  3827. return this;
  3828. },
  3829. /**
  3830. * Adds value to this point and returns a new one
  3831. * @param {Number} scalar
  3832. * @return {fabric.Point} new Point with added value
  3833. */
  3834. scalarAdd: function (scalar) {
  3835. return new Point(this.x + scalar, this.y + scalar);
  3836. },
  3837. /**
  3838. * Adds value to this point
  3839. * @param {Number} scalar
  3840. * @return {fabric.Point} thisArg
  3841. * @chainable
  3842. */
  3843. scalarAddEquals: function (scalar) {
  3844. this.x += scalar;
  3845. this.y += scalar;
  3846. return this;
  3847. },
  3848. /**
  3849. * Subtracts another point from this point and returns a new one
  3850. * @param {fabric.Point} that
  3851. * @return {fabric.Point} new Point object with subtracted values
  3852. */
  3853. subtract: function (that) {
  3854. return new Point(this.x - that.x, this.y - that.y);
  3855. },
  3856. /**
  3857. * Subtracts another point from this point
  3858. * @param {fabric.Point} that
  3859. * @return {fabric.Point} thisArg
  3860. * @chainable
  3861. */
  3862. subtractEquals: function (that) {
  3863. this.x -= that.x;
  3864. this.y -= that.y;
  3865. return this;
  3866. },
  3867. /**
  3868. * Subtracts value from this point and returns a new one
  3869. * @param {Number} scalar
  3870. * @return {fabric.Point}
  3871. */
  3872. scalarSubtract: function (scalar) {
  3873. return new Point(this.x - scalar, this.y - scalar);
  3874. },
  3875. /**
  3876. * Subtracts value from this point
  3877. * @param {Number} scalar
  3878. * @return {fabric.Point} thisArg
  3879. * @chainable
  3880. */
  3881. scalarSubtractEquals: function (scalar) {
  3882. this.x -= scalar;
  3883. this.y -= scalar;
  3884. return this;
  3885. },
  3886. /**
  3887. * Miltiplies this point by a value and returns a new one
  3888. * TODO: rename in scalarMultiply in 2.0
  3889. * @param {Number} scalar
  3890. * @return {fabric.Point}
  3891. */
  3892. multiply: function (scalar) {
  3893. return new Point(this.x * scalar, this.y * scalar);
  3894. },
  3895. /**
  3896. * Miltiplies this point by a value
  3897. * TODO: rename in scalarMultiplyEquals in 2.0
  3898. * @param {Number} scalar
  3899. * @return {fabric.Point} thisArg
  3900. * @chainable
  3901. */
  3902. multiplyEquals: function (scalar) {
  3903. this.x *= scalar;
  3904. this.y *= scalar;
  3905. return this;
  3906. },
  3907. /**
  3908. * Divides this point by a value and returns a new one
  3909. * TODO: rename in scalarDivide in 2.0
  3910. * @param {Number} scalar
  3911. * @return {fabric.Point}
  3912. */
  3913. divide: function (scalar) {
  3914. return new Point(this.x / scalar, this.y / scalar);
  3915. },
  3916. /**
  3917. * Divides this point by a value
  3918. * TODO: rename in scalarDivideEquals in 2.0
  3919. * @param {Number} scalar
  3920. * @return {fabric.Point} thisArg
  3921. * @chainable
  3922. */
  3923. divideEquals: function (scalar) {
  3924. this.x /= scalar;
  3925. this.y /= scalar;
  3926. return this;
  3927. },
  3928. /**
  3929. * Returns true if this point is equal to another one
  3930. * @param {fabric.Point} that
  3931. * @return {Boolean}
  3932. */
  3933. eq: function (that) {
  3934. return (this.x === that.x && this.y === that.y);
  3935. },
  3936. /**
  3937. * Returns true if this point is less than another one
  3938. * @param {fabric.Point} that
  3939. * @return {Boolean}
  3940. */
  3941. lt: function (that) {
  3942. return (this.x < that.x && this.y < that.y);
  3943. },
  3944. /**
  3945. * Returns true if this point is less than or equal to another one
  3946. * @param {fabric.Point} that
  3947. * @return {Boolean}
  3948. */
  3949. lte: function (that) {
  3950. return (this.x <= that.x && this.y <= that.y);
  3951. },
  3952. /**
  3953. * Returns true if this point is greater another one
  3954. * @param {fabric.Point} that
  3955. * @return {Boolean}
  3956. */
  3957. gt: function (that) {
  3958. return (this.x > that.x && this.y > that.y);
  3959. },
  3960. /**
  3961. * Returns true if this point is greater than or equal to another one
  3962. * @param {fabric.Point} that
  3963. * @return {Boolean}
  3964. */
  3965. gte: function (that) {
  3966. return (this.x >= that.x && this.y >= that.y);
  3967. },
  3968. /**
  3969. * Returns new point which is the result of linear interpolation with this one and another one
  3970. * @param {fabric.Point} that
  3971. * @param {Number} t , position of interpolation, between 0 and 1 default 0.5
  3972. * @return {fabric.Point}
  3973. */
  3974. lerp: function (that, t) {
  3975. if (typeof t === 'undefined') {
  3976. t = 0.5;
  3977. }
  3978. t = Math.max(Math.min(1, t), 0);
  3979. return new Point(this.x + (that.x - this.x) * t, this.y + (that.y - this.y) * t);
  3980. },
  3981. /**
  3982. * Returns distance from this point and another one
  3983. * @param {fabric.Point} that
  3984. * @return {Number}
  3985. */
  3986. distanceFrom: function (that) {
  3987. var dx = this.x - that.x,
  3988. dy = this.y - that.y;
  3989. return Math.sqrt(dx * dx + dy * dy);
  3990. },
  3991. /**
  3992. * Returns the point between this point and another one
  3993. * @param {fabric.Point} that
  3994. * @return {fabric.Point}
  3995. */
  3996. midPointFrom: function (that) {
  3997. return this.lerp(that);
  3998. },
  3999. /**
  4000. * Returns a new point which is the min of this and another one
  4001. * @param {fabric.Point} that
  4002. * @return {fabric.Point}
  4003. */
  4004. min: function (that) {
  4005. return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y));
  4006. },
  4007. /**
  4008. * Returns a new point which is the max of this and another one
  4009. * @param {fabric.Point} that
  4010. * @return {fabric.Point}
  4011. */
  4012. max: function (that) {
  4013. return new Point(Math.max(this.x, that.x), Math.max(this.y, that.y));
  4014. },
  4015. /**
  4016. * Returns string representation of this point
  4017. * @return {String}
  4018. */
  4019. toString: function () {
  4020. return this.x + ',' + this.y;
  4021. },
  4022. /**
  4023. * Sets x/y of this point
  4024. * @param {Number} x
  4025. * @param {Number} y
  4026. * @chainable
  4027. */
  4028. setXY: function (x, y) {
  4029. this.x = x;
  4030. this.y = y;
  4031. return this;
  4032. },
  4033. /**
  4034. * Sets x of this point
  4035. * @param {Number} x
  4036. * @chainable
  4037. */
  4038. setX: function (x) {
  4039. this.x = x;
  4040. return this;
  4041. },
  4042. /**
  4043. * Sets y of this point
  4044. * @param {Number} y
  4045. * @chainable
  4046. */
  4047. setY: function (y) {
  4048. this.y = y;
  4049. return this;
  4050. },
  4051. /**
  4052. * Sets x/y of this point from another point
  4053. * @param {fabric.Point} that
  4054. * @chainable
  4055. */
  4056. setFromPoint: function (that) {
  4057. this.x = that.x;
  4058. this.y = that.y;
  4059. return this;
  4060. },
  4061. /**
  4062. * Swaps x/y of this point and another point
  4063. * @param {fabric.Point} that
  4064. */
  4065. swap: function (that) {
  4066. var x = this.x,
  4067. y = this.y;
  4068. this.x = that.x;
  4069. this.y = that.y;
  4070. that.x = x;
  4071. that.y = y;
  4072. },
  4073. /**
  4074. * return a cloned instance of the point
  4075. * @return {fabric.Point}
  4076. */
  4077. clone: function () {
  4078. return new Point(this.x, this.y);
  4079. }
  4080. };
  4081. })(typeof exports !== 'undefined' ? exports : this);
  4082. (function(global) {
  4083. 'use strict';
  4084. /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */
  4085. var fabric = global.fabric || (global.fabric = { });
  4086. if (fabric.Intersection) {
  4087. fabric.warn('fabric.Intersection is already defined');
  4088. return;
  4089. }
  4090. /**
  4091. * Intersection class
  4092. * @class fabric.Intersection
  4093. * @memberOf fabric
  4094. * @constructor
  4095. */
  4096. function Intersection(status) {
  4097. this.status = status;
  4098. this.points = [];
  4099. }
  4100. fabric.Intersection = Intersection;
  4101. fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ {
  4102. constructor: Intersection,
  4103. /**
  4104. * Appends a point to intersection
  4105. * @param {fabric.Point} point
  4106. * @return {fabric.Intersection} thisArg
  4107. * @chainable
  4108. */
  4109. appendPoint: function (point) {
  4110. this.points.push(point);
  4111. return this;
  4112. },
  4113. /**
  4114. * Appends points to intersection
  4115. * @param {Array} points
  4116. * @return {fabric.Intersection} thisArg
  4117. * @chainable
  4118. */
  4119. appendPoints: function (points) {
  4120. this.points = this.points.concat(points);
  4121. return this;
  4122. }
  4123. };
  4124. /**
  4125. * Checks if one line intersects another
  4126. * TODO: rename in intersectSegmentSegment
  4127. * @static
  4128. * @param {fabric.Point} a1
  4129. * @param {fabric.Point} a2
  4130. * @param {fabric.Point} b1
  4131. * @param {fabric.Point} b2
  4132. * @return {fabric.Intersection}
  4133. */
  4134. fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) {
  4135. var result,
  4136. uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x),
  4137. ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x),
  4138. uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);
  4139. if (uB !== 0) {
  4140. var ua = uaT / uB,
  4141. ub = ubT / uB;
  4142. if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
  4143. result = new Intersection('Intersection');
  4144. result.appendPoint(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y)));
  4145. }
  4146. else {
  4147. result = new Intersection();
  4148. }
  4149. }
  4150. else {
  4151. if (uaT === 0 || ubT === 0) {
  4152. result = new Intersection('Coincident');
  4153. }
  4154. else {
  4155. result = new Intersection('Parallel');
  4156. }
  4157. }
  4158. return result;
  4159. };
  4160. /**
  4161. * Checks if line intersects polygon
  4162. * TODO: rename in intersectSegmentPolygon
  4163. * fix detection of coincident
  4164. * @static
  4165. * @param {fabric.Point} a1
  4166. * @param {fabric.Point} a2
  4167. * @param {Array} points
  4168. * @return {fabric.Intersection}
  4169. */
  4170. fabric.Intersection.intersectLinePolygon = function(a1, a2, points) {
  4171. var result = new Intersection(),
  4172. length = points.length,
  4173. b1, b2, inter;
  4174. for (var i = 0; i < length; i++) {
  4175. b1 = points[i];
  4176. b2 = points[(i + 1) % length];
  4177. inter = Intersection.intersectLineLine(a1, a2, b1, b2);
  4178. result.appendPoints(inter.points);
  4179. }
  4180. if (result.points.length > 0) {
  4181. result.status = 'Intersection';
  4182. }
  4183. return result;
  4184. };
  4185. /**
  4186. * Checks if polygon intersects another polygon
  4187. * @static
  4188. * @param {Array} points1
  4189. * @param {Array} points2
  4190. * @return {fabric.Intersection}
  4191. */
  4192. fabric.Intersection.intersectPolygonPolygon = function (points1, points2) {
  4193. var result = new Intersection(),
  4194. length = points1.length;
  4195. for (var i = 0; i < length; i++) {
  4196. var a1 = points1[i],
  4197. a2 = points1[(i + 1) % length],
  4198. inter = Intersection.intersectLinePolygon(a1, a2, points2);
  4199. result.appendPoints(inter.points);
  4200. }
  4201. if (result.points.length > 0) {
  4202. result.status = 'Intersection';
  4203. }
  4204. return result;
  4205. };
  4206. /**
  4207. * Checks if polygon intersects rectangle
  4208. * @static
  4209. * @param {Array} points
  4210. * @param {fabric.Point} r1
  4211. * @param {fabric.Point} r2
  4212. * @return {fabric.Intersection}
  4213. */
  4214. fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) {
  4215. var min = r1.min(r2),
  4216. max = r1.max(r2),
  4217. topRight = new fabric.Point(max.x, min.y),
  4218. bottomLeft = new fabric.Point(min.x, max.y),
  4219. inter1 = Intersection.intersectLinePolygon(min, topRight, points),
  4220. inter2 = Intersection.intersectLinePolygon(topRight, max, points),
  4221. inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points),
  4222. inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points),
  4223. result = new Intersection();
  4224. result.appendPoints(inter1.points);
  4225. result.appendPoints(inter2.points);
  4226. result.appendPoints(inter3.points);
  4227. result.appendPoints(inter4.points);
  4228. if (result.points.length > 0) {
  4229. result.status = 'Intersection';
  4230. }
  4231. return result;
  4232. };
  4233. })(typeof exports !== 'undefined' ? exports : this);
  4234. (function(global) {
  4235. 'use strict';
  4236. var fabric = global.fabric || (global.fabric = { });
  4237. if (fabric.Color) {
  4238. fabric.warn('fabric.Color is already defined.');
  4239. return;
  4240. }
  4241. /**
  4242. * Color class
  4243. * The purpose of {@link fabric.Color} is to abstract and encapsulate common color operations;
  4244. * {@link fabric.Color} is a constructor and creates instances of {@link fabric.Color} objects.
  4245. *
  4246. * @class fabric.Color
  4247. * @param {String} color optional in hex or rgb(a) or hsl format or from known color list
  4248. * @return {fabric.Color} thisArg
  4249. * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors}
  4250. */
  4251. function Color(color) {
  4252. if (!color) {
  4253. this.setSource([0, 0, 0, 1]);
  4254. }
  4255. else {
  4256. this._tryParsingColor(color);
  4257. }
  4258. }
  4259. fabric.Color = Color;
  4260. fabric.Color.prototype = /** @lends fabric.Color.prototype */ {
  4261. /**
  4262. * @private
  4263. * @param {String|Array} color Color value to parse
  4264. */
  4265. _tryParsingColor: function(color) {
  4266. var source;
  4267. if (color in Color.colorNameMap) {
  4268. color = Color.colorNameMap[color];
  4269. }
  4270. if (color === 'transparent') {
  4271. source = [255, 255, 255, 0];
  4272. }
  4273. if (!source) {
  4274. source = Color.sourceFromHex(color);
  4275. }
  4276. if (!source) {
  4277. source = Color.sourceFromRgb(color);
  4278. }
  4279. if (!source) {
  4280. source = Color.sourceFromHsl(color);
  4281. }
  4282. if (!source) {
  4283. //if color is not recognize let's make black as canvas does
  4284. source = [0, 0, 0, 1];
  4285. }
  4286. if (source) {
  4287. this.setSource(source);
  4288. }
  4289. },
  4290. /**
  4291. * Adapted from <a href="https://rawgithub.com/mjijackson/mjijackson.github.com/master/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript.html">https://github.com/mjijackson</a>
  4292. * @private
  4293. * @param {Number} r Red color value
  4294. * @param {Number} g Green color value
  4295. * @param {Number} b Blue color value
  4296. * @return {Array} Hsl color
  4297. */
  4298. _rgbToHsl: function(r, g, b) {
  4299. r /= 255; g /= 255; b /= 255;
  4300. var h, s, l,
  4301. max = fabric.util.array.max([r, g, b]),
  4302. min = fabric.util.array.min([r, g, b]);
  4303. l = (max + min) / 2;
  4304. if (max === min) {
  4305. h = s = 0; // achromatic
  4306. }
  4307. else {
  4308. var d = max - min;
  4309. s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
  4310. switch (max) {
  4311. case r:
  4312. h = (g - b) / d + (g < b ? 6 : 0);
  4313. break;
  4314. case g:
  4315. h = (b - r) / d + 2;
  4316. break;
  4317. case b:
  4318. h = (r - g) / d + 4;
  4319. break;
  4320. }
  4321. h /= 6;
  4322. }
  4323. return [
  4324. Math.round(h * 360),
  4325. Math.round(s * 100),
  4326. Math.round(l * 100)
  4327. ];
  4328. },
  4329. /**
  4330. * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1])
  4331. * @return {Array}
  4332. */
  4333. getSource: function() {
  4334. return this._source;
  4335. },
  4336. /**
  4337. * Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1])
  4338. * @param {Array} source
  4339. */
  4340. setSource: function(source) {
  4341. this._source = source;
  4342. },
  4343. /**
  4344. * Returns color representation in RGB format
  4345. * @return {String} ex: rgb(0-255,0-255,0-255)
  4346. */
  4347. toRgb: function() {
  4348. var source = this.getSource();
  4349. return 'rgb(' + source[0] + ',' + source[1] + ',' + source[2] + ')';
  4350. },
  4351. /**
  4352. * Returns color representation in RGBA format
  4353. * @return {String} ex: rgba(0-255,0-255,0-255,0-1)
  4354. */
  4355. toRgba: function() {
  4356. var source = this.getSource();
  4357. return 'rgba(' + source[0] + ',' + source[1] + ',' + source[2] + ',' + source[3] + ')';
  4358. },
  4359. /**
  4360. * Returns color representation in HSL format
  4361. * @return {String} ex: hsl(0-360,0%-100%,0%-100%)
  4362. */
  4363. toHsl: function() {
  4364. var source = this.getSource(),
  4365. hsl = this._rgbToHsl(source[0], source[1], source[2]);
  4366. return 'hsl(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%)';
  4367. },
  4368. /**
  4369. * Returns color representation in HSLA format
  4370. * @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1)
  4371. */
  4372. toHsla: function() {
  4373. var source = this.getSource(),
  4374. hsl = this._rgbToHsl(source[0], source[1], source[2]);
  4375. return 'hsla(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%,' + source[3] + ')';
  4376. },
  4377. /**
  4378. * Returns color representation in HEX format
  4379. * @return {String} ex: FF5555
  4380. */
  4381. toHex: function() {
  4382. var source = this.getSource(), r, g, b;
  4383. r = source[0].toString(16);
  4384. r = (r.length === 1) ? ('0' + r) : r;
  4385. g = source[1].toString(16);
  4386. g = (g.length === 1) ? ('0' + g) : g;
  4387. b = source[2].toString(16);
  4388. b = (b.length === 1) ? ('0' + b) : b;
  4389. return r.toUpperCase() + g.toUpperCase() + b.toUpperCase();
  4390. },
  4391. /**
  4392. * Returns color representation in HEXA format
  4393. * @return {String} ex: FF5555CC
  4394. */
  4395. toHexa: function() {
  4396. var source = this.getSource(), a;
  4397. a = source[3] * 255;
  4398. a = a.toString(16);
  4399. a = (a.length === 1) ? ('0' + a) : a;
  4400. return this.toHex() + a.toUpperCase();
  4401. },
  4402. /**
  4403. * Gets value of alpha channel for this color
  4404. * @return {Number} 0-1
  4405. */
  4406. getAlpha: function() {
  4407. return this.getSource()[3];
  4408. },
  4409. /**
  4410. * Sets value of alpha channel for this color
  4411. * @param {Number} alpha Alpha value 0-1
  4412. * @return {fabric.Color} thisArg
  4413. */
  4414. setAlpha: function(alpha) {
  4415. var source = this.getSource();
  4416. source[3] = alpha;
  4417. this.setSource(source);
  4418. return this;
  4419. },
  4420. /**
  4421. * Transforms color to its grayscale representation
  4422. * @return {fabric.Color} thisArg
  4423. */
  4424. toGrayscale: function() {
  4425. var source = this.getSource(),
  4426. average = parseInt((source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), 10),
  4427. currentAlpha = source[3];
  4428. this.setSource([average, average, average, currentAlpha]);
  4429. return this;
  4430. },
  4431. /**
  4432. * Transforms color to its black and white representation
  4433. * @param {Number} threshold
  4434. * @return {fabric.Color} thisArg
  4435. */
  4436. toBlackWhite: function(threshold) {
  4437. var source = this.getSource(),
  4438. average = (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0),
  4439. currentAlpha = source[3];
  4440. threshold = threshold || 127;
  4441. average = (Number(average) < Number(threshold)) ? 0 : 255;
  4442. this.setSource([average, average, average, currentAlpha]);
  4443. return this;
  4444. },
  4445. /**
  4446. * Overlays color with another color
  4447. * @param {String|fabric.Color} otherColor
  4448. * @return {fabric.Color} thisArg
  4449. */
  4450. overlayWith: function(otherColor) {
  4451. if (!(otherColor instanceof Color)) {
  4452. otherColor = new Color(otherColor);
  4453. }
  4454. var result = [],
  4455. alpha = this.getAlpha(),
  4456. otherAlpha = 0.5,
  4457. source = this.getSource(),
  4458. otherSource = otherColor.getSource();
  4459. for (var i = 0; i < 3; i++) {
  4460. result.push(Math.round((source[i] * (1 - otherAlpha)) + (otherSource[i] * otherAlpha)));
  4461. }
  4462. result[3] = alpha;
  4463. this.setSource(result);
  4464. return this;
  4465. }
  4466. };
  4467. /**
  4468. * Regex matching color in RGB or RGBA formats (ex: rgb(0, 0, 0), rgba(255, 100, 10, 0.5), rgba( 255 , 100 , 10 , 0.5 ), rgb(1,1,1), rgba(100%, 60%, 10%, 0.5))
  4469. * @static
  4470. * @field
  4471. * @memberOf fabric.Color
  4472. */
  4473. // eslint-disable-next-line max-len
  4474. fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/;
  4475. /**
  4476. * Regex matching color in HSL or HSLA formats (ex: hsl(200, 80%, 10%), hsla(300, 50%, 80%, 0.5), hsla( 300 , 50% , 80% , 0.5 ))
  4477. * @static
  4478. * @field
  4479. * @memberOf fabric.Color
  4480. */
  4481. fabric.Color.reHSLa = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/;
  4482. /**
  4483. * Regex matching color in HEX format (ex: #FF5544CC, #FF5555, 010155, aff)
  4484. * @static
  4485. * @field
  4486. * @memberOf fabric.Color
  4487. */
  4488. fabric.Color.reHex = /^#?([0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{4}|[0-9a-f]{3})$/i;
  4489. /**
  4490. * Map of the 17 basic color names with HEX code
  4491. * @static
  4492. * @field
  4493. * @memberOf fabric.Color
  4494. * @see: http://www.w3.org/TR/CSS2/syndata.html#color-units
  4495. */
  4496. fabric.Color.colorNameMap = {
  4497. aqua: '#00FFFF',
  4498. black: '#000000',
  4499. blue: '#0000FF',
  4500. fuchsia: '#FF00FF',
  4501. gray: '#808080',
  4502. grey: '#808080',
  4503. green: '#008000',
  4504. lime: '#00FF00',
  4505. maroon: '#800000',
  4506. navy: '#000080',
  4507. olive: '#808000',
  4508. orange: '#FFA500',
  4509. purple: '#800080',
  4510. red: '#FF0000',
  4511. silver: '#C0C0C0',
  4512. teal: '#008080',
  4513. white: '#FFFFFF',
  4514. yellow: '#FFFF00'
  4515. };
  4516. /**
  4517. * @private
  4518. * @param {Number} p
  4519. * @param {Number} q
  4520. * @param {Number} t
  4521. * @return {Number}
  4522. */
  4523. function hue2rgb(p, q, t) {
  4524. if (t < 0) {
  4525. t += 1;
  4526. }
  4527. if (t > 1) {
  4528. t -= 1;
  4529. }
  4530. if (t < 1 / 6) {
  4531. return p + (q - p) * 6 * t;
  4532. }
  4533. if (t < 1 / 2) {
  4534. return q;
  4535. }
  4536. if (t < 2 / 3) {
  4537. return p + (q - p) * (2 / 3 - t) * 6;
  4538. }
  4539. return p;
  4540. }
  4541. /**
  4542. * Returns new color object, when given a color in RGB format
  4543. * @memberOf fabric.Color
  4544. * @param {String} color Color value ex: rgb(0-255,0-255,0-255)
  4545. * @return {fabric.Color}
  4546. */
  4547. fabric.Color.fromRgb = function(color) {
  4548. return Color.fromSource(Color.sourceFromRgb(color));
  4549. };
  4550. /**
  4551. * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format
  4552. * @memberOf fabric.Color
  4553. * @param {String} color Color value ex: rgb(0-255,0-255,0-255), rgb(0%-100%,0%-100%,0%-100%)
  4554. * @return {Array} source
  4555. */
  4556. fabric.Color.sourceFromRgb = function(color) {
  4557. var match = color.match(Color.reRGBa);
  4558. if (match) {
  4559. var r = parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1) * (/%$/.test(match[1]) ? 255 : 1),
  4560. g = parseInt(match[2], 10) / (/%$/.test(match[2]) ? 100 : 1) * (/%$/.test(match[2]) ? 255 : 1),
  4561. b = parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1) * (/%$/.test(match[3]) ? 255 : 1);
  4562. return [
  4563. parseInt(r, 10),
  4564. parseInt(g, 10),
  4565. parseInt(b, 10),
  4566. match[4] ? parseFloat(match[4]) : 1
  4567. ];
  4568. }
  4569. };
  4570. /**
  4571. * Returns new color object, when given a color in RGBA format
  4572. * @static
  4573. * @function
  4574. * @memberOf fabric.Color
  4575. * @param {String} color
  4576. * @return {fabric.Color}
  4577. */
  4578. fabric.Color.fromRgba = Color.fromRgb;
  4579. /**
  4580. * Returns new color object, when given a color in HSL format
  4581. * @param {String} color Color value ex: hsl(0-260,0%-100%,0%-100%)
  4582. * @memberOf fabric.Color
  4583. * @return {fabric.Color}
  4584. */
  4585. fabric.Color.fromHsl = function(color) {
  4586. return Color.fromSource(Color.sourceFromHsl(color));
  4587. };
  4588. /**
  4589. * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in HSL or HSLA format.
  4590. * Adapted from <a href="https://rawgithub.com/mjijackson/mjijackson.github.com/master/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript.html">https://github.com/mjijackson</a>
  4591. * @memberOf fabric.Color
  4592. * @param {String} color Color value ex: hsl(0-360,0%-100%,0%-100%) or hsla(0-360,0%-100%,0%-100%, 0-1)
  4593. * @return {Array} source
  4594. * @see http://http://www.w3.org/TR/css3-color/#hsl-color
  4595. */
  4596. fabric.Color.sourceFromHsl = function(color) {
  4597. var match = color.match(Color.reHSLa);
  4598. if (!match) {
  4599. return;
  4600. }
  4601. var h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360,
  4602. s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1),
  4603. l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1),
  4604. r, g, b;
  4605. if (s === 0) {
  4606. r = g = b = l;
  4607. }
  4608. else {
  4609. var q = l <= 0.5 ? l * (s + 1) : l + s - l * s,
  4610. p = l * 2 - q;
  4611. r = hue2rgb(p, q, h + 1 / 3);
  4612. g = hue2rgb(p, q, h);
  4613. b = hue2rgb(p, q, h - 1 / 3);
  4614. }
  4615. return [
  4616. Math.round(r * 255),
  4617. Math.round(g * 255),
  4618. Math.round(b * 255),
  4619. match[4] ? parseFloat(match[4]) : 1
  4620. ];
  4621. };
  4622. /**
  4623. * Returns new color object, when given a color in HSLA format
  4624. * @static
  4625. * @function
  4626. * @memberOf fabric.Color
  4627. * @param {String} color
  4628. * @return {fabric.Color}
  4629. */
  4630. fabric.Color.fromHsla = Color.fromHsl;
  4631. /**
  4632. * Returns new color object, when given a color in HEX format
  4633. * @static
  4634. * @memberOf fabric.Color
  4635. * @param {String} color Color value ex: FF5555
  4636. * @return {fabric.Color}
  4637. */
  4638. fabric.Color.fromHex = function(color) {
  4639. return Color.fromSource(Color.sourceFromHex(color));
  4640. };
  4641. /**
  4642. * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in HEX format
  4643. * @static
  4644. * @memberOf fabric.Color
  4645. * @param {String} color ex: FF5555 or FF5544CC (RGBa)
  4646. * @return {Array} source
  4647. */
  4648. fabric.Color.sourceFromHex = function(color) {
  4649. if (color.match(Color.reHex)) {
  4650. var value = color.slice(color.indexOf('#') + 1),
  4651. isShortNotation = (value.length === 3 || value.length === 4),
  4652. isRGBa = (value.length === 8 || value.length === 4),
  4653. r = isShortNotation ? (value.charAt(0) + value.charAt(0)) : value.substring(0, 2),
  4654. g = isShortNotation ? (value.charAt(1) + value.charAt(1)) : value.substring(2, 4),
  4655. b = isShortNotation ? (value.charAt(2) + value.charAt(2)) : value.substring(4, 6),
  4656. a = isRGBa ? (isShortNotation ? (value.charAt(3) + value.charAt(3)) : value.substring(6, 8)) : 'FF';
  4657. return [
  4658. parseInt(r, 16),
  4659. parseInt(g, 16),
  4660. parseInt(b, 16),
  4661. parseFloat((parseInt(a, 16) / 255).toFixed(2))
  4662. ];
  4663. }
  4664. };
  4665. /**
  4666. * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5])
  4667. * @static
  4668. * @memberOf fabric.Color
  4669. * @param {Array} source
  4670. * @return {fabric.Color}
  4671. */
  4672. fabric.Color.fromSource = function(source) {
  4673. var oColor = new Color();
  4674. oColor.setSource(source);
  4675. return oColor;
  4676. };
  4677. })(typeof exports !== 'undefined' ? exports : this);
  4678. (function() {
  4679. /* _FROM_SVG_START_ */
  4680. function getColorStop(el) {
  4681. var style = el.getAttribute('style'),
  4682. offset = el.getAttribute('offset') || 0,
  4683. color, colorAlpha, opacity;
  4684. // convert percents to absolute values
  4685. offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1);
  4686. offset = offset < 0 ? 0 : offset > 1 ? 1 : offset;
  4687. if (style) {
  4688. var keyValuePairs = style.split(/\s*;\s*/);
  4689. if (keyValuePairs[keyValuePairs.length - 1] === '') {
  4690. keyValuePairs.pop();
  4691. }
  4692. for (var i = keyValuePairs.length; i--; ) {
  4693. var split = keyValuePairs[i].split(/\s*:\s*/),
  4694. key = split[0].trim(),
  4695. value = split[1].trim();
  4696. if (key === 'stop-color') {
  4697. color = value;
  4698. }
  4699. else if (key === 'stop-opacity') {
  4700. opacity = value;
  4701. }
  4702. }
  4703. }
  4704. if (!color) {
  4705. color = el.getAttribute('stop-color') || 'rgb(0,0,0)';
  4706. }
  4707. if (!opacity) {
  4708. opacity = el.getAttribute('stop-opacity');
  4709. }
  4710. color = new fabric.Color(color);
  4711. colorAlpha = color.getAlpha();
  4712. opacity = isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity);
  4713. opacity *= colorAlpha;
  4714. return {
  4715. offset: offset,
  4716. color: color.toRgb(),
  4717. opacity: opacity
  4718. };
  4719. }
  4720. function getLinearCoords(el) {
  4721. return {
  4722. x1: el.getAttribute('x1') || 0,
  4723. y1: el.getAttribute('y1') || 0,
  4724. x2: el.getAttribute('x2') || '100%',
  4725. y2: el.getAttribute('y2') || 0
  4726. };
  4727. }
  4728. function getRadialCoords(el) {
  4729. return {
  4730. x1: el.getAttribute('fx') || el.getAttribute('cx') || '50%',
  4731. y1: el.getAttribute('fy') || el.getAttribute('cy') || '50%',
  4732. r1: 0,
  4733. x2: el.getAttribute('cx') || '50%',
  4734. y2: el.getAttribute('cy') || '50%',
  4735. r2: el.getAttribute('r') || '50%'
  4736. };
  4737. }
  4738. /* _FROM_SVG_END_ */
  4739. /**
  4740. * Gradient class
  4741. * @class fabric.Gradient
  4742. * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#gradients}
  4743. * @see {@link fabric.Gradient#initialize} for constructor definition
  4744. */
  4745. fabric.Gradient = fabric.util.createClass(/** @lends fabric.Gradient.prototype */ {
  4746. /**
  4747. * Horizontal offset for aligning gradients coming from SVG when outside pathgroups
  4748. * @type Number
  4749. * @default 0
  4750. */
  4751. offsetX: 0,
  4752. /**
  4753. * Vertical offset for aligning gradients coming from SVG when outside pathgroups
  4754. * @type Number
  4755. * @default 0
  4756. */
  4757. offsetY: 0,
  4758. /**
  4759. * Constructor
  4760. * @param {Object} [options] Options object with type, coords, gradientUnits and colorStops
  4761. * @return {fabric.Gradient} thisArg
  4762. */
  4763. initialize: function(options) {
  4764. options || (options = { });
  4765. var coords = { };
  4766. this.id = fabric.Object.__uid++;
  4767. this.type = options.type || 'linear';
  4768. coords = {
  4769. x1: options.coords.x1 || 0,
  4770. y1: options.coords.y1 || 0,
  4771. x2: options.coords.x2 || 0,
  4772. y2: options.coords.y2 || 0
  4773. };
  4774. if (this.type === 'radial') {
  4775. coords.r1 = options.coords.r1 || 0;
  4776. coords.r2 = options.coords.r2 || 0;
  4777. }
  4778. this.coords = coords;
  4779. this.colorStops = options.colorStops.slice();
  4780. if (options.gradientTransform) {
  4781. this.gradientTransform = options.gradientTransform;
  4782. }
  4783. this.offsetX = options.offsetX || this.offsetX;
  4784. this.offsetY = options.offsetY || this.offsetY;
  4785. },
  4786. /**
  4787. * Adds another colorStop
  4788. * @param {Object} colorStop Object with offset and color
  4789. * @return {fabric.Gradient} thisArg
  4790. */
  4791. addColorStop: function(colorStops) {
  4792. for (var position in colorStops) {
  4793. var color = new fabric.Color(colorStops[position]);
  4794. this.colorStops.push({
  4795. offset: position,
  4796. color: color.toRgb(),
  4797. opacity: color.getAlpha()
  4798. });
  4799. }
  4800. return this;
  4801. },
  4802. /**
  4803. * Returns object representation of a gradient
  4804. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  4805. * @return {Object}
  4806. */
  4807. toObject: function(propertiesToInclude) {
  4808. var object = {
  4809. type: this.type,
  4810. coords: this.coords,
  4811. colorStops: this.colorStops,
  4812. offsetX: this.offsetX,
  4813. offsetY: this.offsetY,
  4814. gradientTransform: this.gradientTransform ? this.gradientTransform.concat() : this.gradientTransform
  4815. };
  4816. fabric.util.populateWithProperties(this, object, propertiesToInclude);
  4817. return object;
  4818. },
  4819. /* _TO_SVG_START_ */
  4820. /**
  4821. * Returns SVG representation of an gradient
  4822. * @param {Object} object Object to create a gradient for
  4823. * @return {String} SVG representation of an gradient (linear/radial)
  4824. */
  4825. toSVG: function(object) {
  4826. var coords = fabric.util.object.clone(this.coords),
  4827. markup, commonAttributes;
  4828. // colorStops must be sorted ascending
  4829. this.colorStops.sort(function(a, b) {
  4830. return a.offset - b.offset;
  4831. });
  4832. if (!(object.group && object.group.type === 'path-group')) {
  4833. for (var prop in coords) {
  4834. if (prop === 'x1' || prop === 'x2' || prop === 'r2') {
  4835. coords[prop] += this.offsetX - object.width / 2;
  4836. }
  4837. else if (prop === 'y1' || prop === 'y2') {
  4838. coords[prop] += this.offsetY - object.height / 2;
  4839. }
  4840. }
  4841. }
  4842. commonAttributes = 'id="SVGID_' + this.id +
  4843. '" gradientUnits="userSpaceOnUse"';
  4844. if (this.gradientTransform) {
  4845. commonAttributes += ' gradientTransform="matrix(' + this.gradientTransform.join(' ') + ')" ';
  4846. }
  4847. if (this.type === 'linear') {
  4848. markup = [
  4849. '<linearGradient ',
  4850. commonAttributes,
  4851. ' x1="', coords.x1,
  4852. '" y1="', coords.y1,
  4853. '" x2="', coords.x2,
  4854. '" y2="', coords.y2,
  4855. '">\n'
  4856. ];
  4857. }
  4858. else if (this.type === 'radial') {
  4859. markup = [
  4860. '<radialGradient ',
  4861. commonAttributes,
  4862. ' cx="', coords.x2,
  4863. '" cy="', coords.y2,
  4864. '" r="', coords.r2,
  4865. '" fx="', coords.x1,
  4866. '" fy="', coords.y1,
  4867. '">\n'
  4868. ];
  4869. }
  4870. for (var i = 0; i < this.colorStops.length; i++) {
  4871. markup.push(
  4872. '<stop ',
  4873. 'offset="', (this.colorStops[i].offset * 100) + '%',
  4874. '" style="stop-color:', this.colorStops[i].color,
  4875. (this.colorStops[i].opacity !== null ? ';stop-opacity: ' + this.colorStops[i].opacity : ';'),
  4876. '"/>\n'
  4877. );
  4878. }
  4879. markup.push((this.type === 'linear' ? '</linearGradient>\n' : '</radialGradient>\n'));
  4880. return markup.join('');
  4881. },
  4882. /* _TO_SVG_END_ */
  4883. /**
  4884. * Returns an instance of CanvasGradient
  4885. * @param {CanvasRenderingContext2D} ctx Context to render on
  4886. * @param {Object} object
  4887. * @return {CanvasGradient}
  4888. */
  4889. toLive: function(ctx, object) {
  4890. var gradient, prop, coords = fabric.util.object.clone(this.coords);
  4891. if (!this.type) {
  4892. return;
  4893. }
  4894. if (object.group && object.group.type === 'path-group') {
  4895. for (prop in coords) {
  4896. if (prop === 'x1' || prop === 'x2') {
  4897. coords[prop] += -this.offsetX + object.width / 2;
  4898. }
  4899. else if (prop === 'y1' || prop === 'y2') {
  4900. coords[prop] += -this.offsetY + object.height / 2;
  4901. }
  4902. }
  4903. }
  4904. if (this.type === 'linear') {
  4905. gradient = ctx.createLinearGradient(
  4906. coords.x1, coords.y1, coords.x2, coords.y2);
  4907. }
  4908. else if (this.type === 'radial') {
  4909. gradient = ctx.createRadialGradient(
  4910. coords.x1, coords.y1, coords.r1, coords.x2, coords.y2, coords.r2);
  4911. }
  4912. for (var i = 0, len = this.colorStops.length; i < len; i++) {
  4913. var color = this.colorStops[i].color,
  4914. opacity = this.colorStops[i].opacity,
  4915. offset = this.colorStops[i].offset;
  4916. if (typeof opacity !== 'undefined') {
  4917. color = new fabric.Color(color).setAlpha(opacity).toRgba();
  4918. }
  4919. gradient.addColorStop(parseFloat(offset), color);
  4920. }
  4921. return gradient;
  4922. }
  4923. });
  4924. fabric.util.object.extend(fabric.Gradient, {
  4925. /* _FROM_SVG_START_ */
  4926. /**
  4927. * Returns {@link fabric.Gradient} instance from an SVG element
  4928. * @static
  4929. * @memberOf fabric.Gradient
  4930. * @param {SVGGradientElement} el SVG gradient element
  4931. * @param {fabric.Object} instance
  4932. * @return {fabric.Gradient} Gradient instance
  4933. * @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement
  4934. * @see http://www.w3.org/TR/SVG/pservers.html#RadialGradientElement
  4935. */
  4936. fromElement: function(el, instance) {
  4937. /**
  4938. * @example:
  4939. *
  4940. * <linearGradient id="linearGrad1">
  4941. * <stop offset="0%" stop-color="white"/>
  4942. * <stop offset="100%" stop-color="black"/>
  4943. * </linearGradient>
  4944. *
  4945. * OR
  4946. *
  4947. * <linearGradient id="linearGrad2">
  4948. * <stop offset="0" style="stop-color:rgb(255,255,255)"/>
  4949. * <stop offset="1" style="stop-color:rgb(0,0,0)"/>
  4950. * </linearGradient>
  4951. *
  4952. * OR
  4953. *
  4954. * <radialGradient id="radialGrad1">
  4955. * <stop offset="0%" stop-color="white" stop-opacity="1" />
  4956. * <stop offset="50%" stop-color="black" stop-opacity="0.5" />
  4957. * <stop offset="100%" stop-color="white" stop-opacity="1" />
  4958. * </radialGradient>
  4959. *
  4960. * OR
  4961. *
  4962. * <radialGradient id="radialGrad2">
  4963. * <stop offset="0" stop-color="rgb(255,255,255)" />
  4964. * <stop offset="0.5" stop-color="rgb(0,0,0)" />
  4965. * <stop offset="1" stop-color="rgb(255,255,255)" />
  4966. * </radialGradient>
  4967. *
  4968. */
  4969. var colorStopEls = el.getElementsByTagName('stop'),
  4970. type,
  4971. gradientUnits = el.getAttribute('gradientUnits') || 'objectBoundingBox',
  4972. gradientTransform = el.getAttribute('gradientTransform'),
  4973. colorStops = [],
  4974. coords, ellipseMatrix;
  4975. if (el.nodeName === 'linearGradient' || el.nodeName === 'LINEARGRADIENT') {
  4976. type = 'linear';
  4977. }
  4978. else {
  4979. type = 'radial';
  4980. }
  4981. if (type === 'linear') {
  4982. coords = getLinearCoords(el);
  4983. }
  4984. else if (type === 'radial') {
  4985. coords = getRadialCoords(el);
  4986. }
  4987. for (var i = colorStopEls.length; i--; ) {
  4988. colorStops.push(getColorStop(colorStopEls[i]));
  4989. }
  4990. ellipseMatrix = _convertPercentUnitsToValues(instance, coords, gradientUnits);
  4991. var gradient = new fabric.Gradient({
  4992. type: type,
  4993. coords: coords,
  4994. colorStops: colorStops,
  4995. offsetX: -instance.left,
  4996. offsetY: -instance.top
  4997. });
  4998. if (gradientTransform || ellipseMatrix !== '') {
  4999. gradient.gradientTransform = fabric.parseTransformAttribute((gradientTransform || '') + ellipseMatrix);
  5000. }
  5001. return gradient;
  5002. },
  5003. /* _FROM_SVG_END_ */
  5004. /**
  5005. * Returns {@link fabric.Gradient} instance from its object representation
  5006. * @static
  5007. * @memberOf fabric.Gradient
  5008. * @param {Object} obj
  5009. * @param {Object} [options] Options object
  5010. */
  5011. forObject: function(obj, options) {
  5012. options || (options = { });
  5013. _convertPercentUnitsToValues(obj, options.coords, 'userSpaceOnUse');
  5014. return new fabric.Gradient(options);
  5015. }
  5016. });
  5017. /**
  5018. * @private
  5019. */
  5020. function _convertPercentUnitsToValues(object, options, gradientUnits) {
  5021. var propValue, addFactor = 0, multFactor = 1, ellipseMatrix = '';
  5022. for (var prop in options) {
  5023. if (options[prop] === 'Infinity') {
  5024. options[prop] = 1;
  5025. }
  5026. else if (options[prop] === '-Infinity') {
  5027. options[prop] = 0;
  5028. }
  5029. propValue = parseFloat(options[prop], 10);
  5030. if (typeof options[prop] === 'string' && /^\d+%$/.test(options[prop])) {
  5031. multFactor = 0.01;
  5032. }
  5033. else {
  5034. multFactor = 1;
  5035. }
  5036. if (prop === 'x1' || prop === 'x2' || prop === 'r2') {
  5037. multFactor *= gradientUnits === 'objectBoundingBox' ? object.width : 1;
  5038. addFactor = gradientUnits === 'objectBoundingBox' ? object.left || 0 : 0;
  5039. }
  5040. else if (prop === 'y1' || prop === 'y2') {
  5041. multFactor *= gradientUnits === 'objectBoundingBox' ? object.height : 1;
  5042. addFactor = gradientUnits === 'objectBoundingBox' ? object.top || 0 : 0;
  5043. }
  5044. options[prop] = propValue * multFactor + addFactor;
  5045. }
  5046. if (object.type === 'ellipse' &&
  5047. options.r2 !== null &&
  5048. gradientUnits === 'objectBoundingBox' &&
  5049. object.rx !== object.ry) {
  5050. var scaleFactor = object.ry / object.rx;
  5051. ellipseMatrix = ' scale(1, ' + scaleFactor + ')';
  5052. if (options.y1) {
  5053. options.y1 /= scaleFactor;
  5054. }
  5055. if (options.y2) {
  5056. options.y2 /= scaleFactor;
  5057. }
  5058. }
  5059. return ellipseMatrix;
  5060. }
  5061. })();
  5062. (function() {
  5063. 'use strict';
  5064. var toFixed = fabric.util.toFixed;
  5065. /**
  5066. * Pattern class
  5067. * @class fabric.Pattern
  5068. * @see {@link http://fabricjs.com/patterns|Pattern demo}
  5069. * @see {@link http://fabricjs.com/dynamic-patterns|DynamicPattern demo}
  5070. * @see {@link fabric.Pattern#initialize} for constructor definition
  5071. */
  5072. fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ {
  5073. /**
  5074. * Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat)
  5075. * @type String
  5076. * @default
  5077. */
  5078. repeat: 'repeat',
  5079. /**
  5080. * Pattern horizontal offset from object's left/top corner
  5081. * @type Number
  5082. * @default
  5083. */
  5084. offsetX: 0,
  5085. /**
  5086. * Pattern vertical offset from object's left/top corner
  5087. * @type Number
  5088. * @default
  5089. */
  5090. offsetY: 0,
  5091. /**
  5092. * Constructor
  5093. * @param {Object} [options] Options object
  5094. * @param {Function} [callback] function to invoke after callback init.
  5095. * @return {fabric.Pattern} thisArg
  5096. */
  5097. initialize: function(options, callback) {
  5098. options || (options = { });
  5099. this.id = fabric.Object.__uid++;
  5100. this.setOptions(options);
  5101. if (!options.source || (options.source && typeof options.source !== 'string')) {
  5102. callback && callback(this);
  5103. return;
  5104. }
  5105. // function string
  5106. if (typeof fabric.util.getFunctionBody(options.source) !== 'undefined') {
  5107. this.source = new Function(fabric.util.getFunctionBody(options.source));
  5108. callback && callback(this);
  5109. }
  5110. else {
  5111. // img src string
  5112. var _this = this;
  5113. this.source = fabric.util.createImage();
  5114. fabric.util.loadImage(options.source, function(img) {
  5115. _this.source = img;
  5116. callback && callback(_this);
  5117. });
  5118. }
  5119. },
  5120. /**
  5121. * Returns object representation of a pattern
  5122. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  5123. * @return {Object} Object representation of a pattern instance
  5124. */
  5125. toObject: function(propertiesToInclude) {
  5126. var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS,
  5127. source, object;
  5128. // callback
  5129. if (typeof this.source === 'function') {
  5130. source = String(this.source);
  5131. }
  5132. // <img> element
  5133. else if (typeof this.source.src === 'string') {
  5134. source = this.source.src;
  5135. }
  5136. // <canvas> element
  5137. else if (typeof this.source === 'object' && this.source.toDataURL) {
  5138. source = this.source.toDataURL();
  5139. }
  5140. object = {
  5141. type: 'pattern',
  5142. source: source,
  5143. repeat: this.repeat,
  5144. offsetX: toFixed(this.offsetX, NUM_FRACTION_DIGITS),
  5145. offsetY: toFixed(this.offsetY, NUM_FRACTION_DIGITS),
  5146. };
  5147. fabric.util.populateWithProperties(this, object, propertiesToInclude);
  5148. return object;
  5149. },
  5150. /* _TO_SVG_START_ */
  5151. /**
  5152. * Returns SVG representation of a pattern
  5153. * @param {fabric.Object} object
  5154. * @return {String} SVG representation of a pattern
  5155. */
  5156. toSVG: function(object) {
  5157. var patternSource = typeof this.source === 'function' ? this.source() : this.source,
  5158. patternWidth = patternSource.width / object.width,
  5159. patternHeight = patternSource.height / object.height,
  5160. patternOffsetX = this.offsetX / object.width,
  5161. patternOffsetY = this.offsetY / object.height,
  5162. patternImgSrc = '';
  5163. if (this.repeat === 'repeat-x' || this.repeat === 'no-repeat') {
  5164. patternHeight = 1;
  5165. }
  5166. if (this.repeat === 'repeat-y' || this.repeat === 'no-repeat') {
  5167. patternWidth = 1;
  5168. }
  5169. if (patternSource.src) {
  5170. patternImgSrc = patternSource.src;
  5171. }
  5172. else if (patternSource.toDataURL) {
  5173. patternImgSrc = patternSource.toDataURL();
  5174. }
  5175. return '<pattern id="SVGID_' + this.id +
  5176. '" x="' + patternOffsetX +
  5177. '" y="' + patternOffsetY +
  5178. '" width="' + patternWidth +
  5179. '" height="' + patternHeight + '">\n' +
  5180. '<image x="0" y="0"' +
  5181. ' width="' + patternSource.width +
  5182. '" height="' + patternSource.height +
  5183. '" xlink:href="' + patternImgSrc +
  5184. '"></image>\n' +
  5185. '</pattern>\n';
  5186. },
  5187. /* _TO_SVG_END_ */
  5188. setOptions: function(options) {
  5189. for (var prop in options) {
  5190. this[prop] = options[prop];
  5191. }
  5192. },
  5193. /**
  5194. * Returns an instance of CanvasPattern
  5195. * @param {CanvasRenderingContext2D} ctx Context to create pattern
  5196. * @return {CanvasPattern}
  5197. */
  5198. toLive: function(ctx) {
  5199. var source = typeof this.source === 'function' ? this.source() : this.source;
  5200. // if the image failed to load, return, and allow rest to continue loading
  5201. if (!source) {
  5202. return '';
  5203. }
  5204. // if an image
  5205. if (typeof source.src !== 'undefined') {
  5206. if (!source.complete) {
  5207. return '';
  5208. }
  5209. if (source.naturalWidth === 0 || source.naturalHeight === 0) {
  5210. return '';
  5211. }
  5212. }
  5213. return ctx.createPattern(source, this.repeat);
  5214. }
  5215. });
  5216. })();
  5217. (function(global) {
  5218. 'use strict';
  5219. var fabric = global.fabric || (global.fabric = { }),
  5220. toFixed = fabric.util.toFixed;
  5221. if (fabric.Shadow) {
  5222. fabric.warn('fabric.Shadow is already defined.');
  5223. return;
  5224. }
  5225. /**
  5226. * Shadow class
  5227. * @class fabric.Shadow
  5228. * @see {@link http://fabricjs.com/shadows|Shadow demo}
  5229. * @see {@link fabric.Shadow#initialize} for constructor definition
  5230. */
  5231. fabric.Shadow = fabric.util.createClass(/** @lends fabric.Shadow.prototype */ {
  5232. /**
  5233. * Shadow color
  5234. * @type String
  5235. * @default
  5236. */
  5237. color: 'rgb(0,0,0)',
  5238. /**
  5239. * Shadow blur
  5240. * @type Number
  5241. */
  5242. blur: 0,
  5243. /**
  5244. * Shadow horizontal offset
  5245. * @type Number
  5246. * @default
  5247. */
  5248. offsetX: 0,
  5249. /**
  5250. * Shadow vertical offset
  5251. * @type Number
  5252. * @default
  5253. */
  5254. offsetY: 0,
  5255. /**
  5256. * Whether the shadow should affect stroke operations
  5257. * @type Boolean
  5258. * @default
  5259. */
  5260. affectStroke: false,
  5261. /**
  5262. * Indicates whether toObject should include default values
  5263. * @type Boolean
  5264. * @default
  5265. */
  5266. includeDefaultValues: true,
  5267. /**
  5268. * Constructor
  5269. * @param {Object|String} [options] Options object with any of color, blur, offsetX, offsetX properties or string (e.g. "rgba(0,0,0,0.2) 2px 2px 10px, "2px 2px 10px rgba(0,0,0,0.2)")
  5270. * @return {fabric.Shadow} thisArg
  5271. */
  5272. initialize: function(options) {
  5273. if (typeof options === 'string') {
  5274. options = this._parseShadow(options);
  5275. }
  5276. for (var prop in options) {
  5277. this[prop] = options[prop];
  5278. }
  5279. this.id = fabric.Object.__uid++;
  5280. },
  5281. /**
  5282. * @private
  5283. * @param {String} shadow Shadow value to parse
  5284. * @return {Object} Shadow object with color, offsetX, offsetY and blur
  5285. */
  5286. _parseShadow: function(shadow) {
  5287. var shadowStr = shadow.trim(),
  5288. offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [],
  5289. color = shadowStr.replace(fabric.Shadow.reOffsetsAndBlur, '') || 'rgb(0,0,0)';
  5290. return {
  5291. color: color.trim(),
  5292. offsetX: parseInt(offsetsAndBlur[1], 10) || 0,
  5293. offsetY: parseInt(offsetsAndBlur[2], 10) || 0,
  5294. blur: parseInt(offsetsAndBlur[3], 10) || 0
  5295. };
  5296. },
  5297. /**
  5298. * Returns a string representation of an instance
  5299. * @see http://www.w3.org/TR/css-text-decor-3/#text-shadow
  5300. * @return {String} Returns CSS3 text-shadow declaration
  5301. */
  5302. toString: function() {
  5303. return [this.offsetX, this.offsetY, this.blur, this.color].join('px ');
  5304. },
  5305. /* _TO_SVG_START_ */
  5306. /**
  5307. * Returns SVG representation of a shadow
  5308. * @param {fabric.Object} object
  5309. * @return {String} SVG representation of a shadow
  5310. */
  5311. toSVG: function(object) {
  5312. var fBoxX = 40, fBoxY = 40, NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS,
  5313. offset = fabric.util.rotateVector(
  5314. { x: this.offsetX, y: this.offsetY },
  5315. fabric.util.degreesToRadians(-object.angle)),
  5316. BLUR_BOX = 20;
  5317. if (object.width && object.height) {
  5318. //http://www.w3.org/TR/SVG/filters.html#FilterEffectsRegion
  5319. // we add some extra space to filter box to contain the blur ( 20 )
  5320. fBoxX = toFixed((Math.abs(offset.x) + this.blur) / object.width, NUM_FRACTION_DIGITS) * 100 + BLUR_BOX;
  5321. fBoxY = toFixed((Math.abs(offset.y) + this.blur) / object.height, NUM_FRACTION_DIGITS) * 100 + BLUR_BOX;
  5322. }
  5323. if (object.flipX) {
  5324. offset.x *= -1;
  5325. }
  5326. if (object.flipY) {
  5327. offset.y *= -1;
  5328. }
  5329. return (
  5330. '<filter id="SVGID_' + this.id + '" y="-' + fBoxY + '%" height="' + (100 + 2 * fBoxY) + '%" ' +
  5331. 'x="-' + fBoxX + '%" width="' + (100 + 2 * fBoxX) + '%" ' + '>\n' +
  5332. '\t<feGaussianBlur in="SourceAlpha" stdDeviation="' +
  5333. toFixed(this.blur ? this.blur / 2 : 0, NUM_FRACTION_DIGITS) + '"></feGaussianBlur>\n' +
  5334. '\t<feOffset dx="' + toFixed(offset.x, NUM_FRACTION_DIGITS) +
  5335. '" dy="' + toFixed(offset.y, NUM_FRACTION_DIGITS) + '" result="oBlur" ></feOffset>\n' +
  5336. '\t<feFlood flood-color="' + this.color + '"/>\n' +
  5337. '\t<feComposite in2="oBlur" operator="in" />\n' +
  5338. '\t<feMerge>\n' +
  5339. '\t\t<feMergeNode></feMergeNode>\n' +
  5340. '\t\t<feMergeNode in="SourceGraphic"></feMergeNode>\n' +
  5341. '\t</feMerge>\n' +
  5342. '</filter>\n');
  5343. },
  5344. /* _TO_SVG_END_ */
  5345. /**
  5346. * Returns object representation of a shadow
  5347. * @return {Object} Object representation of a shadow instance
  5348. */
  5349. toObject: function() {
  5350. if (this.includeDefaultValues) {
  5351. return {
  5352. color: this.color,
  5353. blur: this.blur,
  5354. offsetX: this.offsetX,
  5355. offsetY: this.offsetY,
  5356. affectStroke: this.affectStroke
  5357. };
  5358. }
  5359. var obj = { }, proto = fabric.Shadow.prototype;
  5360. ['color', 'blur', 'offsetX', 'offsetY', 'affectStroke'].forEach(function(prop) {
  5361. if (this[prop] !== proto[prop]) {
  5362. obj[prop] = this[prop];
  5363. }
  5364. }, this);
  5365. return obj;
  5366. }
  5367. });
  5368. /**
  5369. * Regex matching shadow offsetX, offsetY and blur (ex: "2px 2px 10px rgba(0,0,0,0.2)", "rgb(0,255,0) 2px 2px")
  5370. * @static
  5371. * @field
  5372. * @memberOf fabric.Shadow
  5373. */
  5374. // eslint-disable-next-line max-len
  5375. fabric.Shadow.reOffsetsAndBlur = /(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/;
  5376. })(typeof exports !== 'undefined' ? exports : this);
  5377. (function () {
  5378. 'use strict';
  5379. if (fabric.StaticCanvas) {
  5380. fabric.warn('fabric.StaticCanvas is already defined.');
  5381. return;
  5382. }
  5383. // aliases for faster resolution
  5384. var extend = fabric.util.object.extend,
  5385. getElementOffset = fabric.util.getElementOffset,
  5386. removeFromArray = fabric.util.removeFromArray,
  5387. toFixed = fabric.util.toFixed,
  5388. transformPoint = fabric.util.transformPoint,
  5389. invertTransform = fabric.util.invertTransform,
  5390. CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element');
  5391. /**
  5392. * Static canvas class
  5393. * @class fabric.StaticCanvas
  5394. * @mixes fabric.Collection
  5395. * @mixes fabric.Observable
  5396. * @see {@link http://fabricjs.com/static_canvas|StaticCanvas demo}
  5397. * @see {@link fabric.StaticCanvas#initialize} for constructor definition
  5398. * @fires before:render
  5399. * @fires after:render
  5400. * @fires canvas:cleared
  5401. * @fires object:added
  5402. * @fires object:removed
  5403. */
  5404. fabric.StaticCanvas = fabric.util.createClass(fabric.CommonMethods, /** @lends fabric.StaticCanvas.prototype */ {
  5405. /**
  5406. * Constructor
  5407. * @param {HTMLElement | String} el &lt;canvas> element to initialize instance on
  5408. * @param {Object} [options] Options object
  5409. * @return {Object} thisArg
  5410. */
  5411. initialize: function(el, options) {
  5412. options || (options = { });
  5413. this._initStatic(el, options);
  5414. },
  5415. /**
  5416. * Background color of canvas instance.
  5417. * Should be set via {@link fabric.StaticCanvas#setBackgroundColor}.
  5418. * @type {(String|fabric.Pattern)}
  5419. * @default
  5420. */
  5421. backgroundColor: '',
  5422. /**
  5423. * Background image of canvas instance.
  5424. * Should be set via {@link fabric.StaticCanvas#setBackgroundImage}.
  5425. * <b>Backwards incompatibility note:</b> The "backgroundImageOpacity"
  5426. * and "backgroundImageStretch" properties are deprecated since 1.3.9.
  5427. * Use {@link fabric.Image#opacity}, {@link fabric.Image#width} and {@link fabric.Image#height}.
  5428. * @type fabric.Image
  5429. * @default
  5430. */
  5431. backgroundImage: null,
  5432. /**
  5433. * Overlay color of canvas instance.
  5434. * Should be set via {@link fabric.StaticCanvas#setOverlayColor}
  5435. * @since 1.3.9
  5436. * @type {(String|fabric.Pattern)}
  5437. * @default
  5438. */
  5439. overlayColor: '',
  5440. /**
  5441. * Overlay image of canvas instance.
  5442. * Should be set via {@link fabric.StaticCanvas#setOverlayImage}.
  5443. * <b>Backwards incompatibility note:</b> The "overlayImageLeft"
  5444. * and "overlayImageTop" properties are deprecated since 1.3.9.
  5445. * Use {@link fabric.Image#left} and {@link fabric.Image#top}.
  5446. * @type fabric.Image
  5447. * @default
  5448. */
  5449. overlayImage: null,
  5450. /**
  5451. * Indicates whether toObject/toDatalessObject should include default values
  5452. * @type Boolean
  5453. * @default
  5454. */
  5455. includeDefaultValues: true,
  5456. /**
  5457. * Indicates whether objects' state should be saved
  5458. * @type Boolean
  5459. * @default
  5460. */
  5461. stateful: false,
  5462. /**
  5463. * Indicates whether {@link fabric.Collection.add}, {@link fabric.Collection.insertAt} and {@link fabric.Collection.remove} should also re-render canvas.
  5464. * Disabling this option could give a great performance boost when adding/removing a lot of objects to/from canvas at once
  5465. * (followed by a manual rendering after addition/deletion)
  5466. * @type Boolean
  5467. * @default
  5468. */
  5469. renderOnAddRemove: true,
  5470. /**
  5471. * Function that determines clipping of entire canvas area
  5472. * Being passed context as first argument. See clipping canvas area in {@link https://github.com/kangax/fabric.js/wiki/FAQ}
  5473. * @type Function
  5474. * @default
  5475. */
  5476. clipTo: null,
  5477. /**
  5478. * Indicates whether object controls (borders/controls) are rendered above overlay image
  5479. * @type Boolean
  5480. * @default
  5481. */
  5482. controlsAboveOverlay: false,
  5483. /**
  5484. * Indicates whether the browser can be scrolled when using a touchscreen and dragging on the canvas
  5485. * @type Boolean
  5486. * @default
  5487. */
  5488. allowTouchScrolling: false,
  5489. /**
  5490. * Indicates whether this canvas will use image smoothing, this is on by default in browsers
  5491. * @type Boolean
  5492. * @default
  5493. */
  5494. imageSmoothingEnabled: true,
  5495. /**
  5496. * The transformation (in the format of Canvas transform) which focuses the viewport
  5497. * @type Array
  5498. * @default
  5499. */
  5500. viewportTransform: fabric.iMatrix.concat(),
  5501. /**
  5502. * if set to false background image is not affected by viewport transform
  5503. * @since 1.6.3
  5504. * @type Boolean
  5505. * @default
  5506. */
  5507. backgroundVpt: true,
  5508. /**
  5509. * if set to false overlya image is not affected by viewport transform
  5510. * @since 1.6.3
  5511. * @type Boolean
  5512. * @default
  5513. */
  5514. overlayVpt: true,
  5515. /**
  5516. * Callback; invoked right before object is about to be scaled/rotated
  5517. */
  5518. onBeforeScaleRotate: function () {
  5519. /* NOOP */
  5520. },
  5521. /**
  5522. * When true, canvas is scaled by devicePixelRatio for better rendering on retina screens
  5523. */
  5524. enableRetinaScaling: true,
  5525. /**
  5526. * Describe canvas element extension over design
  5527. * properties are tl,tr,bl,br.
  5528. * if canvas is not zoomed/panned those points are the four corner of canvas
  5529. * if canvas is viewportTransformed you those points indicate the extension
  5530. * of canvas element in plain untrasformed coordinates
  5531. * The coordinates get updated with @method calcViewportBoundaries.
  5532. * @memberOf fabric.StaticCanvas.prototype
  5533. */
  5534. vptCoords: { },
  5535. /**
  5536. * Based on vptCoords and object.aCoords, skip rendering of objects that
  5537. * are not included in current viewport.
  5538. * May greatly help in applications with crowded canvas and use of zoom/pan
  5539. * If One of the corner of the bounding box of the object is on the canvas
  5540. * the objects get rendered.
  5541. * @memberOf fabric.StaticCanvas.prototype
  5542. */
  5543. skipOffscreen: false,
  5544. /**
  5545. * @private
  5546. * @param {HTMLElement | String} el &lt;canvas> element to initialize instance on
  5547. * @param {Object} [options] Options object
  5548. */
  5549. _initStatic: function(el, options) {
  5550. var cb = fabric.StaticCanvas.prototype.renderAll.bind(this);
  5551. this._objects = [];
  5552. this._createLowerCanvas(el);
  5553. this._initOptions(options);
  5554. this._setImageSmoothing();
  5555. // only initialize retina scaling once
  5556. if (!this.interactive) {
  5557. this._initRetinaScaling();
  5558. }
  5559. if (options.overlayImage) {
  5560. this.setOverlayImage(options.overlayImage, cb);
  5561. }
  5562. if (options.backgroundImage) {
  5563. this.setBackgroundImage(options.backgroundImage, cb);
  5564. }
  5565. if (options.backgroundColor) {
  5566. this.setBackgroundColor(options.backgroundColor, cb);
  5567. }
  5568. if (options.overlayColor) {
  5569. this.setOverlayColor(options.overlayColor, cb);
  5570. }
  5571. this.calcOffset();
  5572. },
  5573. /**
  5574. * @private
  5575. */
  5576. _isRetinaScaling: function() {
  5577. return (fabric.devicePixelRatio !== 1 && this.enableRetinaScaling);
  5578. },
  5579. /**
  5580. * @private
  5581. * @return {Number} retinaScaling if applied, otherwise 1;
  5582. */
  5583. getRetinaScaling: function() {
  5584. return this._isRetinaScaling() ? fabric.devicePixelRatio : 1;
  5585. },
  5586. /**
  5587. * @private
  5588. */
  5589. _initRetinaScaling: function() {
  5590. if (!this._isRetinaScaling()) {
  5591. return;
  5592. }
  5593. this.lowerCanvasEl.setAttribute('width', this.width * fabric.devicePixelRatio);
  5594. this.lowerCanvasEl.setAttribute('height', this.height * fabric.devicePixelRatio);
  5595. this.contextContainer.scale(fabric.devicePixelRatio, fabric.devicePixelRatio);
  5596. },
  5597. /**
  5598. * Calculates canvas element offset relative to the document
  5599. * This method is also attached as "resize" event handler of window
  5600. * @return {fabric.Canvas} instance
  5601. * @chainable
  5602. */
  5603. calcOffset: function () {
  5604. this._offset = getElementOffset(this.lowerCanvasEl);
  5605. return this;
  5606. },
  5607. /**
  5608. * Sets {@link fabric.StaticCanvas#overlayImage|overlay image} for this canvas
  5609. * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set overlay to
  5610. * @param {Function} callback callback to invoke when image is loaded and set as an overlay
  5611. * @param {Object} [options] Optional options to set for the {@link fabric.Image|overlay image}.
  5612. * @return {fabric.Canvas} thisArg
  5613. * @chainable
  5614. * @see {@link http://jsfiddle.net/fabricjs/MnzHT/|jsFiddle demo}
  5615. * @example <caption>Normal overlayImage with left/top = 0</caption>
  5616. * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
  5617. * // Needed to position overlayImage at 0/0
  5618. * originX: 'left',
  5619. * originY: 'top'
  5620. * });
  5621. * @example <caption>overlayImage with different properties</caption>
  5622. * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
  5623. * opacity: 0.5,
  5624. * angle: 45,
  5625. * left: 400,
  5626. * top: 400,
  5627. * originX: 'left',
  5628. * originY: 'top'
  5629. * });
  5630. * @example <caption>Stretched overlayImage #1 - width/height correspond to canvas width/height</caption>
  5631. * fabric.Image.fromURL('http://fabricjs.com/assets/jail_cell_bars.png', function(img) {
  5632. * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'});
  5633. * canvas.setOverlayImage(img, canvas.renderAll.bind(canvas));
  5634. * });
  5635. * @example <caption>Stretched overlayImage #2 - width/height correspond to canvas width/height</caption>
  5636. * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
  5637. * width: canvas.width,
  5638. * height: canvas.height,
  5639. * // Needed to position overlayImage at 0/0
  5640. * originX: 'left',
  5641. * originY: 'top'
  5642. * });
  5643. * @example <caption>overlayImage loaded from cross-origin</caption>
  5644. * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
  5645. * opacity: 0.5,
  5646. * angle: 45,
  5647. * left: 400,
  5648. * top: 400,
  5649. * originX: 'left',
  5650. * originY: 'top',
  5651. * crossOrigin: 'anonymous'
  5652. * });
  5653. */
  5654. setOverlayImage: function (image, callback, options) {
  5655. return this.__setBgOverlayImage('overlayImage', image, callback, options);
  5656. },
  5657. /**
  5658. * Sets {@link fabric.StaticCanvas#backgroundImage|background image} for this canvas
  5659. * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background to
  5660. * @param {Function} callback Callback to invoke when image is loaded and set as background
  5661. * @param {Object} [options] Optional options to set for the {@link fabric.Image|background image}.
  5662. * @return {fabric.Canvas} thisArg
  5663. * @chainable
  5664. * @see {@link http://jsfiddle.net/fabricjs/YH9yD/|jsFiddle demo}
  5665. * @example <caption>Normal backgroundImage with left/top = 0</caption>
  5666. * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
  5667. * // Needed to position backgroundImage at 0/0
  5668. * originX: 'left',
  5669. * originY: 'top'
  5670. * });
  5671. * @example <caption>backgroundImage with different properties</caption>
  5672. * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
  5673. * opacity: 0.5,
  5674. * angle: 45,
  5675. * left: 400,
  5676. * top: 400,
  5677. * originX: 'left',
  5678. * originY: 'top'
  5679. * });
  5680. * @example <caption>Stretched backgroundImage #1 - width/height correspond to canvas width/height</caption>
  5681. * fabric.Image.fromURL('http://fabricjs.com/assets/honey_im_subtle.png', function(img) {
  5682. * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'});
  5683. * canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas));
  5684. * });
  5685. * @example <caption>Stretched backgroundImage #2 - width/height correspond to canvas width/height</caption>
  5686. * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
  5687. * width: canvas.width,
  5688. * height: canvas.height,
  5689. * // Needed to position backgroundImage at 0/0
  5690. * originX: 'left',
  5691. * originY: 'top'
  5692. * });
  5693. * @example <caption>backgroundImage loaded from cross-origin</caption>
  5694. * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
  5695. * opacity: 0.5,
  5696. * angle: 45,
  5697. * left: 400,
  5698. * top: 400,
  5699. * originX: 'left',
  5700. * originY: 'top',
  5701. * crossOrigin: 'anonymous'
  5702. * });
  5703. */
  5704. setBackgroundImage: function (image, callback, options) {
  5705. return this.__setBgOverlayImage('backgroundImage', image, callback, options);
  5706. },
  5707. /**
  5708. * Sets {@link fabric.StaticCanvas#overlayColor|background color} for this canvas
  5709. * @param {(String|fabric.Pattern)} overlayColor Color or pattern to set background color to
  5710. * @param {Function} callback Callback to invoke when background color is set
  5711. * @return {fabric.Canvas} thisArg
  5712. * @chainable
  5713. * @see {@link http://jsfiddle.net/fabricjs/pB55h/|jsFiddle demo}
  5714. * @example <caption>Normal overlayColor - color value</caption>
  5715. * canvas.setOverlayColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas));
  5716. * @example <caption>fabric.Pattern used as overlayColor</caption>
  5717. * canvas.setOverlayColor({
  5718. * source: 'http://fabricjs.com/assets/escheresque_ste.png'
  5719. * }, canvas.renderAll.bind(canvas));
  5720. * @example <caption>fabric.Pattern used as overlayColor with repeat and offset</caption>
  5721. * canvas.setOverlayColor({
  5722. * source: 'http://fabricjs.com/assets/escheresque_ste.png',
  5723. * repeat: 'repeat',
  5724. * offsetX: 200,
  5725. * offsetY: 100
  5726. * }, canvas.renderAll.bind(canvas));
  5727. */
  5728. setOverlayColor: function(overlayColor, callback) {
  5729. return this.__setBgOverlayColor('overlayColor', overlayColor, callback);
  5730. },
  5731. /**
  5732. * Sets {@link fabric.StaticCanvas#backgroundColor|background color} for this canvas
  5733. * @param {(String|fabric.Pattern)} backgroundColor Color or pattern to set background color to
  5734. * @param {Function} callback Callback to invoke when background color is set
  5735. * @return {fabric.Canvas} thisArg
  5736. * @chainable
  5737. * @see {@link http://jsfiddle.net/fabricjs/hXzvk/|jsFiddle demo}
  5738. * @example <caption>Normal backgroundColor - color value</caption>
  5739. * canvas.setBackgroundColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas));
  5740. * @example <caption>fabric.Pattern used as backgroundColor</caption>
  5741. * canvas.setBackgroundColor({
  5742. * source: 'http://fabricjs.com/assets/escheresque_ste.png'
  5743. * }, canvas.renderAll.bind(canvas));
  5744. * @example <caption>fabric.Pattern used as backgroundColor with repeat and offset</caption>
  5745. * canvas.setBackgroundColor({
  5746. * source: 'http://fabricjs.com/assets/escheresque_ste.png',
  5747. * repeat: 'repeat',
  5748. * offsetX: 200,
  5749. * offsetY: 100
  5750. * }, canvas.renderAll.bind(canvas));
  5751. */
  5752. setBackgroundColor: function(backgroundColor, callback) {
  5753. return this.__setBgOverlayColor('backgroundColor', backgroundColor, callback);
  5754. },
  5755. /**
  5756. * @private
  5757. * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-imagesmoothingenabled|WhatWG Canvas Standard}
  5758. */
  5759. _setImageSmoothing: function() {
  5760. var ctx = this.getContext();
  5761. ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled || ctx.webkitImageSmoothingEnabled
  5762. || ctx.mozImageSmoothingEnabled || ctx.msImageSmoothingEnabled || ctx.oImageSmoothingEnabled;
  5763. ctx.imageSmoothingEnabled = this.imageSmoothingEnabled;
  5764. },
  5765. /**
  5766. * @private
  5767. * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundImage|backgroundImage}
  5768. * or {@link fabric.StaticCanvas#overlayImage|overlayImage})
  5769. * @param {(fabric.Image|String|null)} image fabric.Image instance, URL of an image or null to set background or overlay to
  5770. * @param {Function} callback Callback to invoke when image is loaded and set as background or overlay
  5771. * @param {Object} [options] Optional options to set for the {@link fabric.Image|image}.
  5772. */
  5773. __setBgOverlayImage: function(property, image, callback, options) {
  5774. if (typeof image === 'string') {
  5775. fabric.util.loadImage(image, function(img) {
  5776. img && (this[property] = new fabric.Image(img, options));
  5777. callback && callback(img);
  5778. }, this, options && options.crossOrigin);
  5779. }
  5780. else {
  5781. options && image.setOptions(options);
  5782. this[property] = image;
  5783. callback && callback(image);
  5784. }
  5785. return this;
  5786. },
  5787. /**
  5788. * @private
  5789. * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundColor|backgroundColor}
  5790. * or {@link fabric.StaticCanvas#overlayColor|overlayColor})
  5791. * @param {(Object|String|null)} color Object with pattern information, color value or null
  5792. * @param {Function} [callback] Callback is invoked when color is set
  5793. */
  5794. __setBgOverlayColor: function(property, color, callback) {
  5795. this[property] = color;
  5796. this._initGradient(color, property);
  5797. this._initPattern(color, property, callback);
  5798. return this;
  5799. },
  5800. /**
  5801. * @private
  5802. */
  5803. _createCanvasElement: function(canvasEl) {
  5804. var element = fabric.util.createCanvasElement(canvasEl);
  5805. if (!element.style) {
  5806. element.style = { };
  5807. }
  5808. if (!element) {
  5809. throw CANVAS_INIT_ERROR;
  5810. }
  5811. if (typeof element.getContext === 'undefined') {
  5812. throw CANVAS_INIT_ERROR;
  5813. }
  5814. return element;
  5815. },
  5816. /**
  5817. * @private
  5818. * @param {Object} [options] Options object
  5819. */
  5820. _initOptions: function (options) {
  5821. this._setOptions(options);
  5822. this.width = this.width || parseInt(this.lowerCanvasEl.width, 10) || 0;
  5823. this.height = this.height || parseInt(this.lowerCanvasEl.height, 10) || 0;
  5824. if (!this.lowerCanvasEl.style) {
  5825. return;
  5826. }
  5827. this.lowerCanvasEl.width = this.width;
  5828. this.lowerCanvasEl.height = this.height;
  5829. this.lowerCanvasEl.style.width = this.width + 'px';
  5830. this.lowerCanvasEl.style.height = this.height + 'px';
  5831. this.viewportTransform = this.viewportTransform.slice();
  5832. },
  5833. /**
  5834. * Creates a bottom canvas
  5835. * @private
  5836. * @param {HTMLElement} [canvasEl]
  5837. */
  5838. _createLowerCanvas: function (canvasEl) {
  5839. this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement(canvasEl);
  5840. fabric.util.addClass(this.lowerCanvasEl, 'lower-canvas');
  5841. if (this.interactive) {
  5842. this._applyCanvasStyle(this.lowerCanvasEl);
  5843. }
  5844. this.contextContainer = this.lowerCanvasEl.getContext('2d');
  5845. },
  5846. /**
  5847. * Returns canvas width (in px)
  5848. * @return {Number}
  5849. */
  5850. getWidth: function () {
  5851. return this.width;
  5852. },
  5853. /**
  5854. * Returns canvas height (in px)
  5855. * @return {Number}
  5856. */
  5857. getHeight: function () {
  5858. return this.height;
  5859. },
  5860. /**
  5861. * Sets width of this canvas instance
  5862. * @param {Number|String} value Value to set width to
  5863. * @param {Object} [options] Options object
  5864. * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions
  5865. * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions
  5866. * @return {fabric.Canvas} instance
  5867. * @chainable true
  5868. */
  5869. setWidth: function (value, options) {
  5870. return this.setDimensions({ width: value }, options);
  5871. },
  5872. /**
  5873. * Sets height of this canvas instance
  5874. * @param {Number|String} value Value to set height to
  5875. * @param {Object} [options] Options object
  5876. * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions
  5877. * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions
  5878. * @return {fabric.Canvas} instance
  5879. * @chainable true
  5880. */
  5881. setHeight: function (value, options) {
  5882. return this.setDimensions({ height: value }, options);
  5883. },
  5884. /**
  5885. * Sets dimensions (width, height) of this canvas instance. when options.cssOnly flag active you should also supply the unit of measure (px/%/em)
  5886. * @param {Object} dimensions Object with width/height properties
  5887. * @param {Number|String} [dimensions.width] Width of canvas element
  5888. * @param {Number|String} [dimensions.height] Height of canvas element
  5889. * @param {Object} [options] Options object
  5890. * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions
  5891. * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions
  5892. * @return {fabric.Canvas} thisArg
  5893. * @chainable
  5894. */
  5895. setDimensions: function (dimensions, options) {
  5896. var cssValue;
  5897. options = options || {};
  5898. for (var prop in dimensions) {
  5899. cssValue = dimensions[prop];
  5900. if (!options.cssOnly) {
  5901. this._setBackstoreDimension(prop, dimensions[prop]);
  5902. cssValue += 'px';
  5903. }
  5904. if (!options.backstoreOnly) {
  5905. this._setCssDimension(prop, cssValue);
  5906. }
  5907. }
  5908. this._initRetinaScaling();
  5909. this._setImageSmoothing();
  5910. this.calcOffset();
  5911. if (!options.cssOnly) {
  5912. this.renderAll();
  5913. }
  5914. return this;
  5915. },
  5916. /**
  5917. * Helper for setting width/height
  5918. * @private
  5919. * @param {String} prop property (width|height)
  5920. * @param {Number} value value to set property to
  5921. * @return {fabric.Canvas} instance
  5922. * @chainable true
  5923. */
  5924. _setBackstoreDimension: function (prop, value) {
  5925. this.lowerCanvasEl[prop] = value;
  5926. if (this.upperCanvasEl) {
  5927. this.upperCanvasEl[prop] = value;
  5928. }
  5929. if (this.cacheCanvasEl) {
  5930. this.cacheCanvasEl[prop] = value;
  5931. }
  5932. this[prop] = value;
  5933. return this;
  5934. },
  5935. /**
  5936. * Helper for setting css width/height
  5937. * @private
  5938. * @param {String} prop property (width|height)
  5939. * @param {String} value value to set property to
  5940. * @return {fabric.Canvas} instance
  5941. * @chainable true
  5942. */
  5943. _setCssDimension: function (prop, value) {
  5944. this.lowerCanvasEl.style[prop] = value;
  5945. if (this.upperCanvasEl) {
  5946. this.upperCanvasEl.style[prop] = value;
  5947. }
  5948. if (this.wrapperEl) {
  5949. this.wrapperEl.style[prop] = value;
  5950. }
  5951. return this;
  5952. },
  5953. /**
  5954. * Returns canvas zoom level
  5955. * @return {Number}
  5956. */
  5957. getZoom: function () {
  5958. return this.viewportTransform[0];
  5959. },
  5960. /**
  5961. * Sets viewport transform of this canvas instance
  5962. * @param {Array} vpt the transform in the form of context.transform
  5963. * @return {fabric.Canvas} instance
  5964. * @chainable true
  5965. */
  5966. setViewportTransform: function (vpt) {
  5967. var activeGroup = this._activeGroup, object, ingoreVpt = false, skipAbsolute = true;
  5968. this.viewportTransform = vpt;
  5969. for (var i = 0, len = this._objects.length; i < len; i++) {
  5970. object = this._objects[i];
  5971. object.group || object.setCoords(ingoreVpt, skipAbsolute);
  5972. }
  5973. if (activeGroup) {
  5974. activeGroup.setCoords(ingoreVpt, skipAbsolute);
  5975. }
  5976. this.calcViewportBoundaries();
  5977. this.renderAll();
  5978. return this;
  5979. },
  5980. /**
  5981. * Sets zoom level of this canvas instance, zoom centered around point
  5982. * @param {fabric.Point} point to zoom with respect to
  5983. * @param {Number} value to set zoom to, less than 1 zooms out
  5984. * @return {fabric.Canvas} instance
  5985. * @chainable true
  5986. */
  5987. zoomToPoint: function (point, value) {
  5988. // TODO: just change the scale, preserve other transformations
  5989. var before = point, vpt = this.viewportTransform.slice(0);
  5990. point = transformPoint(point, invertTransform(this.viewportTransform));
  5991. vpt[0] = value;
  5992. vpt[3] = value;
  5993. var after = transformPoint(point, vpt);
  5994. vpt[4] += before.x - after.x;
  5995. vpt[5] += before.y - after.y;
  5996. return this.setViewportTransform(vpt);
  5997. },
  5998. /**
  5999. * Sets zoom level of this canvas instance
  6000. * @param {Number} value to set zoom to, less than 1 zooms out
  6001. * @return {fabric.Canvas} instance
  6002. * @chainable true
  6003. */
  6004. setZoom: function (value) {
  6005. this.zoomToPoint(new fabric.Point(0, 0), value);
  6006. return this;
  6007. },
  6008. /**
  6009. * Pan viewport so as to place point at top left corner of canvas
  6010. * @param {fabric.Point} point to move to
  6011. * @return {fabric.Canvas} instance
  6012. * @chainable true
  6013. */
  6014. absolutePan: function (point) {
  6015. var vpt = this.viewportTransform.slice(0);
  6016. vpt[4] = -point.x;
  6017. vpt[5] = -point.y;
  6018. return this.setViewportTransform(vpt);
  6019. },
  6020. /**
  6021. * Pans viewpoint relatively
  6022. * @param {fabric.Point} point (position vector) to move by
  6023. * @return {fabric.Canvas} instance
  6024. * @chainable true
  6025. */
  6026. relativePan: function (point) {
  6027. return this.absolutePan(new fabric.Point(
  6028. -point.x - this.viewportTransform[4],
  6029. -point.y - this.viewportTransform[5]
  6030. ));
  6031. },
  6032. /**
  6033. * Returns &lt;canvas> element corresponding to this instance
  6034. * @return {HTMLCanvasElement}
  6035. */
  6036. getElement: function () {
  6037. return this.lowerCanvasEl;
  6038. },
  6039. /**
  6040. * @private
  6041. * @param {fabric.Object} obj Object that was added
  6042. */
  6043. _onObjectAdded: function(obj) {
  6044. this.stateful && obj.setupState();
  6045. obj._set('canvas', this);
  6046. obj.setCoords();
  6047. this.fire('object:added', { target: obj });
  6048. obj.fire('added');
  6049. },
  6050. /**
  6051. * @private
  6052. * @param {fabric.Object} obj Object that was removed
  6053. */
  6054. _onObjectRemoved: function(obj) {
  6055. this.fire('object:removed', { target: obj });
  6056. obj.fire('removed');
  6057. delete obj.canvas;
  6058. },
  6059. /**
  6060. * Clears specified context of canvas element
  6061. * @param {CanvasRenderingContext2D} ctx Context to clear
  6062. * @return {fabric.Canvas} thisArg
  6063. * @chainable
  6064. */
  6065. clearContext: function(ctx) {
  6066. ctx.clearRect(0, 0, this.width, this.height);
  6067. return this;
  6068. },
  6069. /**
  6070. * Returns context of canvas where objects are drawn
  6071. * @return {CanvasRenderingContext2D}
  6072. */
  6073. getContext: function () {
  6074. return this.contextContainer;
  6075. },
  6076. /**
  6077. * Clears all contexts (background, main, top) of an instance
  6078. * @return {fabric.Canvas} thisArg
  6079. * @chainable
  6080. */
  6081. clear: function () {
  6082. this._objects.length = 0;
  6083. this.backgroundImage = null;
  6084. this.overlayImage = null;
  6085. this.backgroundColor = '';
  6086. this.overlayColor = '';
  6087. if (this._hasITextHandlers) {
  6088. this.off('mouse:up', this._mouseUpITextHandler);
  6089. this._iTextInstances = null;
  6090. this._hasITextHandlers = false;
  6091. }
  6092. this.clearContext(this.contextContainer);
  6093. this.fire('canvas:cleared');
  6094. this.renderAll();
  6095. return this;
  6096. },
  6097. /**
  6098. * Renders the canvas
  6099. * @return {fabric.Canvas} instance
  6100. * @chainable
  6101. */
  6102. renderAll: function () {
  6103. var canvasToDrawOn = this.contextContainer;
  6104. this.renderCanvas(canvasToDrawOn, this._objects);
  6105. return this;
  6106. },
  6107. /**
  6108. * Calculate the position of the 4 corner of canvas with current viewportTransform.
  6109. * helps to determinate when an object is in the current rendering viewport using
  6110. * object absolute coordinates ( aCoords )
  6111. * @return {Object} points.tl
  6112. * @chainable
  6113. */
  6114. calcViewportBoundaries: function() {
  6115. var points = { }, width = this.getWidth(), height = this.getHeight(),
  6116. iVpt = invertTransform(this.viewportTransform);
  6117. points.tl = transformPoint({ x: 0, y: 0 }, iVpt);
  6118. points.br = transformPoint({ x: width, y: height }, iVpt);
  6119. points.tr = new fabric.Point(points.br.x, points.tl.y);
  6120. points.bl = new fabric.Point(points.tl.x, points.br.y);
  6121. this.vptCoords = points;
  6122. return points;
  6123. },
  6124. /**
  6125. * Renders background, objects, overlay and controls.
  6126. * @param {CanvasRenderingContext2D} ctx
  6127. * @param {Array} objects to render
  6128. * @return {fabric.Canvas} instance
  6129. * @chainable
  6130. */
  6131. renderCanvas: function(ctx, objects) {
  6132. this.calcViewportBoundaries();
  6133. this.clearContext(ctx);
  6134. this.fire('before:render');
  6135. if (this.clipTo) {
  6136. fabric.util.clipContext(this, ctx);
  6137. }
  6138. this._renderBackground(ctx);
  6139. ctx.save();
  6140. //apply viewport transform once for all rendering process
  6141. ctx.transform.apply(ctx, this.viewportTransform);
  6142. this._renderObjects(ctx, objects);
  6143. ctx.restore();
  6144. if (!this.controlsAboveOverlay && this.interactive) {
  6145. this.drawControls(ctx);
  6146. }
  6147. if (this.clipTo) {
  6148. ctx.restore();
  6149. }
  6150. this._renderOverlay(ctx);
  6151. if (this.controlsAboveOverlay && this.interactive) {
  6152. this.drawControls(ctx);
  6153. }
  6154. this.fire('after:render');
  6155. },
  6156. /**
  6157. * @private
  6158. * @param {CanvasRenderingContext2D} ctx Context to render on
  6159. * @param {Array} objects to render
  6160. */
  6161. _renderObjects: function(ctx, objects) {
  6162. for (var i = 0, length = objects.length; i < length; ++i) {
  6163. objects[i] && objects[i].render(ctx);
  6164. }
  6165. },
  6166. /**
  6167. * @private
  6168. * @param {CanvasRenderingContext2D} ctx Context to render on
  6169. * @param {string} property 'background' or 'overlay'
  6170. */
  6171. _renderBackgroundOrOverlay: function(ctx, property) {
  6172. var object = this[property + 'Color'];
  6173. if (object) {
  6174. ctx.fillStyle = object.toLive
  6175. ? object.toLive(ctx, this)
  6176. : object;
  6177. ctx.fillRect(
  6178. object.offsetX || 0,
  6179. object.offsetY || 0,
  6180. this.width,
  6181. this.height);
  6182. }
  6183. object = this[property + 'Image'];
  6184. if (object) {
  6185. if (this[property + 'Vpt']) {
  6186. ctx.save();
  6187. ctx.transform.apply(ctx, this.viewportTransform);
  6188. }
  6189. object.render(ctx);
  6190. this[property + 'Vpt'] && ctx.restore();
  6191. }
  6192. },
  6193. /**
  6194. * @private
  6195. * @param {CanvasRenderingContext2D} ctx Context to render on
  6196. */
  6197. _renderBackground: function(ctx) {
  6198. this._renderBackgroundOrOverlay(ctx, 'background');
  6199. },
  6200. /**
  6201. * @private
  6202. * @param {CanvasRenderingContext2D} ctx Context to render on
  6203. */
  6204. _renderOverlay: function(ctx) {
  6205. this._renderBackgroundOrOverlay(ctx, 'overlay');
  6206. },
  6207. /**
  6208. * Returns coordinates of a center of canvas.
  6209. * Returned value is an object with top and left properties
  6210. * @return {Object} object with "top" and "left" number values
  6211. */
  6212. getCenter: function () {
  6213. return {
  6214. top: this.getHeight() / 2,
  6215. left: this.getWidth() / 2
  6216. };
  6217. },
  6218. /**
  6219. * Centers object horizontally in the canvas
  6220. * You might need to call `setCoords` on an object after centering, to update controls area.
  6221. * @param {fabric.Object} object Object to center horizontally
  6222. * @return {fabric.Canvas} thisArg
  6223. */
  6224. centerObjectH: function (object) {
  6225. return this._centerObject(object, new fabric.Point(this.getCenter().left, object.getCenterPoint().y));
  6226. },
  6227. /**
  6228. * Centers object vertically in the canvas
  6229. * You might need to call `setCoords` on an object after centering, to update controls area.
  6230. * @param {fabric.Object} object Object to center vertically
  6231. * @return {fabric.Canvas} thisArg
  6232. * @chainable
  6233. */
  6234. centerObjectV: function (object) {
  6235. return this._centerObject(object, new fabric.Point(object.getCenterPoint().x, this.getCenter().top));
  6236. },
  6237. /**
  6238. * Centers object vertically and horizontally in the canvas
  6239. * You might need to call `setCoords` on an object after centering, to update controls area.
  6240. * @param {fabric.Object} object Object to center vertically and horizontally
  6241. * @return {fabric.Canvas} thisArg
  6242. * @chainable
  6243. */
  6244. centerObject: function(object) {
  6245. var center = this.getCenter();
  6246. return this._centerObject(object, new fabric.Point(center.left, center.top));
  6247. },
  6248. /**
  6249. * Centers object vertically and horizontally in the viewport
  6250. * You might need to call `setCoords` on an object after centering, to update controls area.
  6251. * @param {fabric.Object} object Object to center vertically and horizontally
  6252. * @return {fabric.Canvas} thisArg
  6253. * @chainable
  6254. */
  6255. viewportCenterObject: function(object) {
  6256. var vpCenter = this.getVpCenter();
  6257. return this._centerObject(object, vpCenter);
  6258. },
  6259. /**
  6260. * Centers object horizontally in the viewport, object.top is unchanged
  6261. * You might need to call `setCoords` on an object after centering, to update controls area.
  6262. * @param {fabric.Object} object Object to center vertically and horizontally
  6263. * @return {fabric.Canvas} thisArg
  6264. * @chainable
  6265. */
  6266. viewportCenterObjectH: function(object) {
  6267. var vpCenter = this.getVpCenter();
  6268. this._centerObject(object, new fabric.Point(vpCenter.x, object.getCenterPoint().y));
  6269. return this;
  6270. },
  6271. /**
  6272. * Centers object Vertically in the viewport, object.top is unchanged
  6273. * You might need to call `setCoords` on an object after centering, to update controls area.
  6274. * @param {fabric.Object} object Object to center vertically and horizontally
  6275. * @return {fabric.Canvas} thisArg
  6276. * @chainable
  6277. */
  6278. viewportCenterObjectV: function(object) {
  6279. var vpCenter = this.getVpCenter();
  6280. return this._centerObject(object, new fabric.Point(object.getCenterPoint().x, vpCenter.y));
  6281. },
  6282. /**
  6283. * Calculate the point in canvas that correspond to the center of actual viewport.
  6284. * @return {fabric.Point} vpCenter, viewport center
  6285. * @chainable
  6286. */
  6287. getVpCenter: function() {
  6288. var center = this.getCenter(),
  6289. iVpt = invertTransform(this.viewportTransform);
  6290. return transformPoint({ x: center.left, y: center.top }, iVpt);
  6291. },
  6292. /**
  6293. * @private
  6294. * @param {fabric.Object} object Object to center
  6295. * @param {fabric.Point} center Center point
  6296. * @return {fabric.Canvas} thisArg
  6297. * @chainable
  6298. */
  6299. _centerObject: function(object, center) {
  6300. object.setPositionByOrigin(center, 'center', 'center');
  6301. this.renderAll();
  6302. return this;
  6303. },
  6304. /**
  6305. * Returs dataless JSON representation of canvas
  6306. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  6307. * @return {String} json string
  6308. */
  6309. toDatalessJSON: function (propertiesToInclude) {
  6310. return this.toDatalessObject(propertiesToInclude);
  6311. },
  6312. /**
  6313. * Returns object representation of canvas
  6314. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  6315. * @return {Object} object representation of an instance
  6316. */
  6317. toObject: function (propertiesToInclude) {
  6318. return this._toObjectMethod('toObject', propertiesToInclude);
  6319. },
  6320. /**
  6321. * Returns dataless object representation of canvas
  6322. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  6323. * @return {Object} object representation of an instance
  6324. */
  6325. toDatalessObject: function (propertiesToInclude) {
  6326. return this._toObjectMethod('toDatalessObject', propertiesToInclude);
  6327. },
  6328. /**
  6329. * @private
  6330. */
  6331. _toObjectMethod: function (methodName, propertiesToInclude) {
  6332. var data = {
  6333. objects: this._toObjects(methodName, propertiesToInclude)
  6334. };
  6335. extend(data, this.__serializeBgOverlay(methodName, propertiesToInclude));
  6336. fabric.util.populateWithProperties(this, data, propertiesToInclude);
  6337. return data;
  6338. },
  6339. /**
  6340. * @private
  6341. */
  6342. _toObjects: function(methodName, propertiesToInclude) {
  6343. return this.getObjects().filter(function(object) {
  6344. return !object.excludeFromExport;
  6345. }).map(function(instance) {
  6346. return this._toObject(instance, methodName, propertiesToInclude);
  6347. }, this);
  6348. },
  6349. /**
  6350. * @private
  6351. */
  6352. _toObject: function(instance, methodName, propertiesToInclude) {
  6353. var originalValue;
  6354. if (!this.includeDefaultValues) {
  6355. originalValue = instance.includeDefaultValues;
  6356. instance.includeDefaultValues = false;
  6357. }
  6358. var object = instance[methodName](propertiesToInclude);
  6359. if (!this.includeDefaultValues) {
  6360. instance.includeDefaultValues = originalValue;
  6361. }
  6362. return object;
  6363. },
  6364. /**
  6365. * @private
  6366. */
  6367. __serializeBgOverlay: function(methodName, propertiesToInclude) {
  6368. var data = { };
  6369. if (this.backgroundColor) {
  6370. data.background = this.backgroundColor.toObject
  6371. ? this.backgroundColor.toObject(propertiesToInclude)
  6372. : this.backgroundColor;
  6373. }
  6374. if (this.overlayColor) {
  6375. data.overlay = this.overlayColor.toObject
  6376. ? this.overlayColor.toObject(propertiesToInclude)
  6377. : this.overlayColor;
  6378. }
  6379. if (this.backgroundImage) {
  6380. data.backgroundImage = this._toObject(this.backgroundImage, methodName, propertiesToInclude);
  6381. }
  6382. if (this.overlayImage) {
  6383. data.overlayImage = this._toObject(this.overlayImage, methodName, propertiesToInclude);
  6384. }
  6385. return data;
  6386. },
  6387. /* _TO_SVG_START_ */
  6388. /**
  6389. * When true, getSvgTransform() will apply the StaticCanvas.viewportTransform to the SVG transformation. When true,
  6390. * a zoomed canvas will then produce zoomed SVG output.
  6391. * @type Boolean
  6392. * @default
  6393. */
  6394. svgViewportTransformation: true,
  6395. /**
  6396. * Returns SVG representation of canvas
  6397. * @function
  6398. * @param {Object} [options] Options object for SVG output
  6399. * @param {Boolean} [options.suppressPreamble=false] If true xml tag is not included
  6400. * @param {Object} [options.viewBox] SVG viewbox object
  6401. * @param {Number} [options.viewBox.x] x-cooridnate of viewbox
  6402. * @param {Number} [options.viewBox.y] y-coordinate of viewbox
  6403. * @param {Number} [options.viewBox.width] Width of viewbox
  6404. * @param {Number} [options.viewBox.height] Height of viewbox
  6405. * @param {String} [options.encoding=UTF-8] Encoding of SVG output
  6406. * @param {String} [options.width] desired width of svg with or without units
  6407. * @param {String} [options.height] desired height of svg with or without units
  6408. * @param {Function} [reviver] Method for further parsing of svg elements, called after each fabric object converted into svg representation.
  6409. * @return {String} SVG string
  6410. * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#serialization}
  6411. * @see {@link http://jsfiddle.net/fabricjs/jQ3ZZ/|jsFiddle demo}
  6412. * @example <caption>Normal SVG output</caption>
  6413. * var svg = canvas.toSVG();
  6414. * @example <caption>SVG output without preamble (without &lt;?xml ../>)</caption>
  6415. * var svg = canvas.toSVG({suppressPreamble: true});
  6416. * @example <caption>SVG output with viewBox attribute</caption>
  6417. * var svg = canvas.toSVG({
  6418. * viewBox: {
  6419. * x: 100,
  6420. * y: 100,
  6421. * width: 200,
  6422. * height: 300
  6423. * }
  6424. * });
  6425. * @example <caption>SVG output with different encoding (default: UTF-8)</caption>
  6426. * var svg = canvas.toSVG({encoding: 'ISO-8859-1'});
  6427. * @example <caption>Modify SVG output with reviver function</caption>
  6428. * var svg = canvas.toSVG(null, function(svg) {
  6429. * return svg.replace('stroke-dasharray: ; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; ', '');
  6430. * });
  6431. */
  6432. toSVG: function(options, reviver) {
  6433. options || (options = { });
  6434. var markup = [];
  6435. this._setSVGPreamble(markup, options);
  6436. this._setSVGHeader(markup, options);
  6437. this._setSVGBgOverlayColor(markup, 'backgroundColor');
  6438. this._setSVGBgOverlayImage(markup, 'backgroundImage', reviver);
  6439. this._setSVGObjects(markup, reviver);
  6440. this._setSVGBgOverlayColor(markup, 'overlayColor');
  6441. this._setSVGBgOverlayImage(markup, 'overlayImage', reviver);
  6442. markup.push('</svg>');
  6443. return markup.join('');
  6444. },
  6445. /**
  6446. * @private
  6447. */
  6448. _setSVGPreamble: function(markup, options) {
  6449. if (options.suppressPreamble) {
  6450. return;
  6451. }
  6452. markup.push(
  6453. '<?xml version="1.0" encoding="', (options.encoding || 'UTF-8'), '" standalone="no" ?>\n',
  6454. '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" ',
  6455. '"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n'
  6456. );
  6457. },
  6458. /**
  6459. * @private
  6460. */
  6461. _setSVGHeader: function(markup, options) {
  6462. var width = options.width || this.width,
  6463. height = options.height || this.height,
  6464. vpt, viewBox = 'viewBox="0 0 ' + this.width + ' ' + this.height + '" ',
  6465. NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS;
  6466. if (options.viewBox) {
  6467. viewBox = 'viewBox="' +
  6468. options.viewBox.x + ' ' +
  6469. options.viewBox.y + ' ' +
  6470. options.viewBox.width + ' ' +
  6471. options.viewBox.height + '" ';
  6472. }
  6473. else {
  6474. if (this.svgViewportTransformation) {
  6475. vpt = this.viewportTransform;
  6476. viewBox = 'viewBox="' +
  6477. toFixed(-vpt[4] / vpt[0], NUM_FRACTION_DIGITS) + ' ' +
  6478. toFixed(-vpt[5] / vpt[3], NUM_FRACTION_DIGITS) + ' ' +
  6479. toFixed(this.width / vpt[0], NUM_FRACTION_DIGITS) + ' ' +
  6480. toFixed(this.height / vpt[3], NUM_FRACTION_DIGITS) + '" ';
  6481. }
  6482. }
  6483. markup.push(
  6484. '<svg ',
  6485. 'xmlns="http://www.w3.org/2000/svg" ',
  6486. 'xmlns:xlink="http://www.w3.org/1999/xlink" ',
  6487. 'version="1.1" ',
  6488. 'width="', width, '" ',
  6489. 'height="', height, '" ',
  6490. viewBox,
  6491. 'xml:space="preserve">\n',
  6492. '<desc>Created with Fabric.js ', fabric.version, '</desc>\n',
  6493. '<defs>\n',
  6494. this.createSVGFontFacesMarkup(),
  6495. this.createSVGRefElementsMarkup(),
  6496. '</defs>\n'
  6497. );
  6498. },
  6499. /**
  6500. * Creates markup containing SVG referenced elements like patterns, gradients etc.
  6501. * @return {String}
  6502. */
  6503. createSVGRefElementsMarkup: function() {
  6504. var _this = this,
  6505. markup = ['backgroundColor', 'overlayColor'].map(function(prop) {
  6506. var fill = _this[prop];
  6507. if (fill && fill.toLive) {
  6508. return fill.toSVG(_this, false);
  6509. }
  6510. });
  6511. return markup.join('');
  6512. },
  6513. /**
  6514. * Creates markup containing SVG font faces,
  6515. * font URLs for font faces must be collected by developers
  6516. * and are not extracted from the DOM by fabricjs
  6517. * @param {Array} objects Array of fabric objects
  6518. * @return {String}
  6519. */
  6520. createSVGFontFacesMarkup: function() {
  6521. var markup = '', fontList = { }, obj, fontFamily,
  6522. style, row, rowIndex, _char, charIndex,
  6523. fontPaths = fabric.fontPaths, objects = this.getObjects();
  6524. for (var i = 0, len = objects.length; i < len; i++) {
  6525. obj = objects[i];
  6526. fontFamily = obj.fontFamily;
  6527. if (obj.type.indexOf('text') === -1 || fontList[fontFamily] || !fontPaths[fontFamily]) {
  6528. continue;
  6529. }
  6530. fontList[fontFamily] = true;
  6531. if (!obj.styles) {
  6532. continue;
  6533. }
  6534. style = obj.styles;
  6535. for (rowIndex in style) {
  6536. row = style[rowIndex];
  6537. for (charIndex in row) {
  6538. _char = row[charIndex];
  6539. fontFamily = _char.fontFamily;
  6540. if (!fontList[fontFamily] && fontPaths[fontFamily]) {
  6541. fontList[fontFamily] = true;
  6542. }
  6543. }
  6544. }
  6545. }
  6546. for (var j in fontList) {
  6547. markup += [
  6548. '\t\t@font-face {\n',
  6549. '\t\t\tfont-family: \'', j, '\';\n',
  6550. '\t\t\tsrc: url(\'', fontPaths[j], '\');\n',
  6551. '\t\t}\n'
  6552. ].join('');
  6553. }
  6554. if (markup) {
  6555. markup = [
  6556. '\t<style type="text/css">',
  6557. '<![CDATA[\n',
  6558. markup,
  6559. ']]>',
  6560. '</style>\n'
  6561. ].join('');
  6562. }
  6563. return markup;
  6564. },
  6565. /**
  6566. * @private
  6567. */
  6568. _setSVGObjects: function(markup, reviver) {
  6569. var instance;
  6570. for (var i = 0, objects = this.getObjects(), len = objects.length; i < len; i++) {
  6571. instance = objects[i];
  6572. if (instance.excludeFromExport) {
  6573. continue;
  6574. }
  6575. this._setSVGObject(markup, instance, reviver);
  6576. }
  6577. },
  6578. /**
  6579. * push single object svg representation in the markup
  6580. * @private
  6581. */
  6582. _setSVGObject: function(markup, instance, reviver) {
  6583. markup.push(instance.toSVG(reviver));
  6584. },
  6585. /**
  6586. * @private
  6587. */
  6588. _setSVGBgOverlayImage: function(markup, property, reviver) {
  6589. if (this[property] && this[property].toSVG) {
  6590. markup.push(this[property].toSVG(reviver));
  6591. }
  6592. },
  6593. /**
  6594. * @private
  6595. */
  6596. _setSVGBgOverlayColor: function(markup, property) {
  6597. var filler = this[property];
  6598. if (!filler) {
  6599. return;
  6600. }
  6601. if (filler.toLive) {
  6602. var repeat = filler.repeat;
  6603. markup.push(
  6604. '<rect transform="translate(', this.width / 2, ',', this.height / 2, ')"',
  6605. ' x="', filler.offsetX - this.width / 2, '" y="', filler.offsetY - this.height / 2, '" ',
  6606. 'width="',
  6607. (repeat === 'repeat-y' || repeat === 'no-repeat'
  6608. ? filler.source.width
  6609. : this.width),
  6610. '" height="',
  6611. (repeat === 'repeat-x' || repeat === 'no-repeat'
  6612. ? filler.source.height
  6613. : this.height),
  6614. '" fill="url(#SVGID_' + filler.id + ')"',
  6615. '></rect>\n'
  6616. );
  6617. }
  6618. else {
  6619. markup.push(
  6620. '<rect x="0" y="0" ',
  6621. 'width="', this.width,
  6622. '" height="', this.height,
  6623. '" fill="', this[property], '"',
  6624. '></rect>\n'
  6625. );
  6626. }
  6627. },
  6628. /* _TO_SVG_END_ */
  6629. /**
  6630. * Moves an object or the objects of a multiple selection
  6631. * to the bottom of the stack of drawn objects
  6632. * @param {fabric.Object} object Object to send to back
  6633. * @return {fabric.Canvas} thisArg
  6634. * @chainable
  6635. */
  6636. sendToBack: function (object) {
  6637. if (!object) {
  6638. return this;
  6639. }
  6640. var activeGroup = this._activeGroup,
  6641. i, obj, objs;
  6642. if (object === activeGroup) {
  6643. objs = activeGroup._objects;
  6644. for (i = objs.length; i--;) {
  6645. obj = objs[i];
  6646. removeFromArray(this._objects, obj);
  6647. this._objects.unshift(obj);
  6648. }
  6649. }
  6650. else {
  6651. removeFromArray(this._objects, object);
  6652. this._objects.unshift(object);
  6653. }
  6654. return this.renderAll && this.renderAll();
  6655. },
  6656. /**
  6657. * Moves an object or the objects of a multiple selection
  6658. * to the top of the stack of drawn objects
  6659. * @param {fabric.Object} object Object to send
  6660. * @return {fabric.Canvas} thisArg
  6661. * @chainable
  6662. */
  6663. bringToFront: function (object) {
  6664. if (!object) {
  6665. return this;
  6666. }
  6667. var activeGroup = this._activeGroup,
  6668. i, obj, objs;
  6669. if (object === activeGroup) {
  6670. objs = activeGroup._objects;
  6671. for (i = 0; i < objs.length; i++) {
  6672. obj = objs[i];
  6673. removeFromArray(this._objects, obj);
  6674. this._objects.push(obj);
  6675. }
  6676. }
  6677. else {
  6678. removeFromArray(this._objects, object);
  6679. this._objects.push(object);
  6680. }
  6681. return this.renderAll && this.renderAll();
  6682. },
  6683. /**
  6684. * Moves an object or a selection down in stack of drawn objects
  6685. * @param {fabric.Object} object Object to send
  6686. * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object
  6687. * @return {fabric.Canvas} thisArg
  6688. * @chainable
  6689. */
  6690. sendBackwards: function (object, intersecting) {
  6691. if (!object) {
  6692. return this;
  6693. }
  6694. var activeGroup = this._activeGroup,
  6695. i, obj, idx, newIdx, objs;
  6696. if (object === activeGroup) {
  6697. objs = activeGroup._objects;
  6698. for (i = 0; i < objs.length; i++) {
  6699. obj = objs[i];
  6700. idx = this._objects.indexOf(obj);
  6701. if (idx !== 0) {
  6702. newIdx = idx - 1;
  6703. removeFromArray(this._objects, obj);
  6704. this._objects.splice(newIdx, 0, obj);
  6705. }
  6706. }
  6707. }
  6708. else {
  6709. idx = this._objects.indexOf(object);
  6710. if (idx !== 0) {
  6711. // if object is not on the bottom of stack
  6712. newIdx = this._findNewLowerIndex(object, idx, intersecting);
  6713. removeFromArray(this._objects, object);
  6714. this._objects.splice(newIdx, 0, object);
  6715. }
  6716. }
  6717. this.renderAll && this.renderAll();
  6718. return this;
  6719. },
  6720. /**
  6721. * @private
  6722. */
  6723. _findNewLowerIndex: function(object, idx, intersecting) {
  6724. var newIdx;
  6725. if (intersecting) {
  6726. newIdx = idx;
  6727. // traverse down the stack looking for the nearest intersecting object
  6728. for (var i = idx - 1; i >= 0; --i) {
  6729. var isIntersecting = object.intersectsWithObject(this._objects[i]) ||
  6730. object.isContainedWithinObject(this._objects[i]) ||
  6731. this._objects[i].isContainedWithinObject(object);
  6732. if (isIntersecting) {
  6733. newIdx = i;
  6734. break;
  6735. }
  6736. }
  6737. }
  6738. else {
  6739. newIdx = idx - 1;
  6740. }
  6741. return newIdx;
  6742. },
  6743. /**
  6744. * Moves an object or a selection up in stack of drawn objects
  6745. * @param {fabric.Object} object Object to send
  6746. * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object
  6747. * @return {fabric.Canvas} thisArg
  6748. * @chainable
  6749. */
  6750. bringForward: function (object, intersecting) {
  6751. if (!object) {
  6752. return this;
  6753. }
  6754. var activeGroup = this._activeGroup,
  6755. i, obj, idx, newIdx, objs;
  6756. if (object === activeGroup) {
  6757. objs = activeGroup._objects;
  6758. for (i = objs.length; i--;) {
  6759. obj = objs[i];
  6760. idx = this._objects.indexOf(obj);
  6761. if (idx !== this._objects.length - 1) {
  6762. newIdx = idx + 1;
  6763. removeFromArray(this._objects, obj);
  6764. this._objects.splice(newIdx, 0, obj);
  6765. }
  6766. }
  6767. }
  6768. else {
  6769. idx = this._objects.indexOf(object);
  6770. if (idx !== this._objects.length - 1) {
  6771. // if object is not on top of stack (last item in an array)
  6772. newIdx = this._findNewUpperIndex(object, idx, intersecting);
  6773. removeFromArray(this._objects, object);
  6774. this._objects.splice(newIdx, 0, object);
  6775. }
  6776. }
  6777. this.renderAll && this.renderAll();
  6778. return this;
  6779. },
  6780. /**
  6781. * @private
  6782. */
  6783. _findNewUpperIndex: function(object, idx, intersecting) {
  6784. var newIdx;
  6785. if (intersecting) {
  6786. newIdx = idx;
  6787. // traverse up the stack looking for the nearest intersecting object
  6788. for (var i = idx + 1; i < this._objects.length; ++i) {
  6789. var isIntersecting = object.intersectsWithObject(this._objects[i]) ||
  6790. object.isContainedWithinObject(this._objects[i]) ||
  6791. this._objects[i].isContainedWithinObject(object);
  6792. if (isIntersecting) {
  6793. newIdx = i;
  6794. break;
  6795. }
  6796. }
  6797. }
  6798. else {
  6799. newIdx = idx + 1;
  6800. }
  6801. return newIdx;
  6802. },
  6803. /**
  6804. * Moves an object to specified level in stack of drawn objects
  6805. * @param {fabric.Object} object Object to send
  6806. * @param {Number} index Position to move to
  6807. * @return {fabric.Canvas} thisArg
  6808. * @chainable
  6809. */
  6810. moveTo: function (object, index) {
  6811. removeFromArray(this._objects, object);
  6812. this._objects.splice(index, 0, object);
  6813. return this.renderAll && this.renderAll();
  6814. },
  6815. /**
  6816. * Clears a canvas element and removes all event listeners
  6817. * @return {fabric.Canvas} thisArg
  6818. * @chainable
  6819. */
  6820. dispose: function () {
  6821. this.clear();
  6822. return this;
  6823. },
  6824. /**
  6825. * Returns a string representation of an instance
  6826. * @return {String} string representation of an instance
  6827. */
  6828. toString: function () {
  6829. return '#<fabric.Canvas (' + this.complexity() + '): ' +
  6830. '{ objects: ' + this.getObjects().length + ' }>';
  6831. }
  6832. });
  6833. extend(fabric.StaticCanvas.prototype, fabric.Observable);
  6834. extend(fabric.StaticCanvas.prototype, fabric.Collection);
  6835. extend(fabric.StaticCanvas.prototype, fabric.DataURLExporter);
  6836. extend(fabric.StaticCanvas, /** @lends fabric.StaticCanvas */ {
  6837. /**
  6838. * @static
  6839. * @type String
  6840. * @default
  6841. */
  6842. EMPTY_JSON: '{"objects": [], "background": "white"}',
  6843. /**
  6844. * Provides a way to check support of some of the canvas methods
  6845. * (either those of HTMLCanvasElement itself, or rendering context)
  6846. *
  6847. * @param {String} methodName Method to check support for;
  6848. * Could be one of "getImageData", "toDataURL", "toDataURLWithQuality" or "setLineDash"
  6849. * @return {Boolean | null} `true` if method is supported (or at least exists),
  6850. * `null` if canvas element or context can not be initialized
  6851. */
  6852. supports: function (methodName) {
  6853. var el = fabric.util.createCanvasElement();
  6854. if (!el || !el.getContext) {
  6855. return null;
  6856. }
  6857. var ctx = el.getContext('2d');
  6858. if (!ctx) {
  6859. return null;
  6860. }
  6861. switch (methodName) {
  6862. case 'getImageData':
  6863. return typeof ctx.getImageData !== 'undefined';
  6864. case 'setLineDash':
  6865. return typeof ctx.setLineDash !== 'undefined';
  6866. case 'toDataURL':
  6867. return typeof el.toDataURL !== 'undefined';
  6868. case 'toDataURLWithQuality':
  6869. try {
  6870. el.toDataURL('image/jpeg', 0);
  6871. return true;
  6872. }
  6873. catch (e) { }
  6874. return false;
  6875. default:
  6876. return null;
  6877. }
  6878. }
  6879. });
  6880. /**
  6881. * Returns JSON representation of canvas
  6882. * @function
  6883. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  6884. * @return {String} JSON string
  6885. * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#serialization}
  6886. * @see {@link http://jsfiddle.net/fabricjs/pec86/|jsFiddle demo}
  6887. * @example <caption>JSON without additional properties</caption>
  6888. * var json = canvas.toJSON();
  6889. * @example <caption>JSON with additional properties included</caption>
  6890. * var json = canvas.toJSON(['lockMovementX', 'lockMovementY', 'lockRotation', 'lockScalingX', 'lockScalingY', 'lockUniScaling']);
  6891. * @example <caption>JSON without default values</caption>
  6892. * canvas.includeDefaultValues = false;
  6893. * var json = canvas.toJSON();
  6894. */
  6895. fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject;
  6896. })();
  6897. /**
  6898. * BaseBrush class
  6899. * @class fabric.BaseBrush
  6900. * @see {@link http://fabricjs.com/freedrawing|Freedrawing demo}
  6901. */
  6902. fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype */ {
  6903. /**
  6904. * Color of a brush
  6905. * @type String
  6906. * @default
  6907. */
  6908. color: 'rgb(0, 0, 0)',
  6909. /**
  6910. * Width of a brush
  6911. * @type Number
  6912. * @default
  6913. */
  6914. width: 1,
  6915. /**
  6916. * Shadow object representing shadow of this shape.
  6917. * <b>Backwards incompatibility note:</b> This property replaces "shadowColor" (String), "shadowOffsetX" (Number),
  6918. * "shadowOffsetY" (Number) and "shadowBlur" (Number) since v1.2.12
  6919. * @type fabric.Shadow
  6920. * @default
  6921. */
  6922. shadow: null,
  6923. /**
  6924. * Line endings style of a brush (one of "butt", "round", "square")
  6925. * @type String
  6926. * @default
  6927. */
  6928. strokeLineCap: 'round',
  6929. /**
  6930. * Corner style of a brush (one of "bevil", "round", "miter")
  6931. * @type String
  6932. * @default
  6933. */
  6934. strokeLineJoin: 'round',
  6935. /**
  6936. * Stroke Dash Array.
  6937. * @type Array
  6938. * @default
  6939. */
  6940. strokeDashArray: null,
  6941. /**
  6942. * Sets shadow of an object
  6943. * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)")
  6944. * @return {fabric.Object} thisArg
  6945. * @chainable
  6946. */
  6947. setShadow: function(options) {
  6948. this.shadow = new fabric.Shadow(options);
  6949. return this;
  6950. },
  6951. /**
  6952. * Sets brush styles
  6953. * @private
  6954. */
  6955. _setBrushStyles: function() {
  6956. var ctx = this.canvas.contextTop;
  6957. ctx.strokeStyle = this.color;
  6958. ctx.lineWidth = this.width;
  6959. ctx.lineCap = this.strokeLineCap;
  6960. ctx.lineJoin = this.strokeLineJoin;
  6961. if (this.strokeDashArray && fabric.StaticCanvas.supports('setLineDash')) {
  6962. ctx.setLineDash(this.strokeDashArray);
  6963. }
  6964. },
  6965. /**
  6966. * Sets brush shadow styles
  6967. * @private
  6968. */
  6969. _setShadow: function() {
  6970. if (!this.shadow) {
  6971. return;
  6972. }
  6973. var ctx = this.canvas.contextTop,
  6974. zoom = this.canvas.getZoom();
  6975. ctx.shadowColor = this.shadow.color;
  6976. ctx.shadowBlur = this.shadow.blur * zoom;
  6977. ctx.shadowOffsetX = this.shadow.offsetX * zoom;
  6978. ctx.shadowOffsetY = this.shadow.offsetY * zoom;
  6979. },
  6980. /**
  6981. * Removes brush shadow styles
  6982. * @private
  6983. */
  6984. _resetShadow: function() {
  6985. var ctx = this.canvas.contextTop;
  6986. ctx.shadowColor = '';
  6987. ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0;
  6988. }
  6989. });
  6990. (function() {
  6991. /**
  6992. * PencilBrush class
  6993. * @class fabric.PencilBrush
  6994. * @extends fabric.BaseBrush
  6995. */
  6996. fabric.PencilBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.PencilBrush.prototype */ {
  6997. /**
  6998. * Constructor
  6999. * @param {fabric.Canvas} canvas
  7000. * @return {fabric.PencilBrush} Instance of a pencil brush
  7001. */
  7002. initialize: function(canvas) {
  7003. this.canvas = canvas;
  7004. this._points = [];
  7005. },
  7006. /**
  7007. * Inovoked on mouse down
  7008. * @param {Object} pointer
  7009. */
  7010. onMouseDown: function(pointer) {
  7011. this._prepareForDrawing(pointer);
  7012. // capture coordinates immediately
  7013. // this allows to draw dots (when movement never occurs)
  7014. this._captureDrawingPath(pointer);
  7015. this._render();
  7016. },
  7017. /**
  7018. * Inovoked on mouse move
  7019. * @param {Object} pointer
  7020. */
  7021. onMouseMove: function(pointer) {
  7022. this._captureDrawingPath(pointer);
  7023. // redraw curve
  7024. // clear top canvas
  7025. this.canvas.clearContext(this.canvas.contextTop);
  7026. this._render();
  7027. },
  7028. /**
  7029. * Invoked on mouse up
  7030. */
  7031. onMouseUp: function() {
  7032. this._finalizeAndAddPath();
  7033. },
  7034. /**
  7035. * @private
  7036. * @param {Object} pointer Actual mouse position related to the canvas.
  7037. */
  7038. _prepareForDrawing: function(pointer) {
  7039. var p = new fabric.Point(pointer.x, pointer.y);
  7040. this._reset();
  7041. this._addPoint(p);
  7042. this.canvas.contextTop.moveTo(p.x, p.y);
  7043. },
  7044. /**
  7045. * @private
  7046. * @param {fabric.Point} point Point to be added to points array
  7047. */
  7048. _addPoint: function(point) {
  7049. this._points.push(point);
  7050. },
  7051. /**
  7052. * Clear points array and set contextTop canvas style.
  7053. * @private
  7054. */
  7055. _reset: function() {
  7056. this._points.length = 0;
  7057. this._setBrushStyles();
  7058. this._setShadow();
  7059. },
  7060. /**
  7061. * @private
  7062. * @param {Object} pointer Actual mouse position related to the canvas.
  7063. */
  7064. _captureDrawingPath: function(pointer) {
  7065. var pointerPoint = new fabric.Point(pointer.x, pointer.y);
  7066. this._addPoint(pointerPoint);
  7067. },
  7068. /**
  7069. * Draw a smooth path on the topCanvas using quadraticCurveTo
  7070. * @private
  7071. */
  7072. _render: function() {
  7073. var ctx = this.canvas.contextTop,
  7074. v = this.canvas.viewportTransform,
  7075. p1 = this._points[0],
  7076. p2 = this._points[1];
  7077. ctx.save();
  7078. ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);
  7079. ctx.beginPath();
  7080. //if we only have 2 points in the path and they are the same
  7081. //it means that the user only clicked the canvas without moving the mouse
  7082. //then we should be drawing a dot. A path isn't drawn between two identical dots
  7083. //that's why we set them apart a bit
  7084. if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) {
  7085. p1.x -= 0.5;
  7086. p2.x += 0.5;
  7087. }
  7088. ctx.moveTo(p1.x, p1.y);
  7089. for (var i = 1, len = this._points.length; i < len; i++) {
  7090. // we pick the point between pi + 1 & pi + 2 as the
  7091. // end point and p1 as our control point.
  7092. var midPoint = p1.midPointFrom(p2);
  7093. ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y);
  7094. p1 = this._points[i];
  7095. p2 = this._points[i + 1];
  7096. }
  7097. // Draw last line as a straight line while
  7098. // we wait for the next point to be able to calculate
  7099. // the bezier control point
  7100. ctx.lineTo(p1.x, p1.y);
  7101. ctx.stroke();
  7102. ctx.restore();
  7103. },
  7104. /**
  7105. * Converts points to SVG path
  7106. * @param {Array} points Array of points
  7107. * @return {String} SVG path
  7108. */
  7109. convertPointsToSVGPath: function(points) {
  7110. var path = [],
  7111. p1 = new fabric.Point(points[0].x, points[0].y),
  7112. p2 = new fabric.Point(points[1].x, points[1].y);
  7113. path.push('M ', points[0].x, ' ', points[0].y, ' ');
  7114. for (var i = 1, len = points.length; i < len; i++) {
  7115. var midPoint = p1.midPointFrom(p2);
  7116. // p1 is our bezier control point
  7117. // midpoint is our endpoint
  7118. // start point is p(i-1) value.
  7119. path.push('Q ', p1.x, ' ', p1.y, ' ', midPoint.x, ' ', midPoint.y, ' ');
  7120. p1 = new fabric.Point(points[i].x, points[i].y);
  7121. if ((i + 1) < points.length) {
  7122. p2 = new fabric.Point(points[i + 1].x, points[i + 1].y);
  7123. }
  7124. }
  7125. path.push('L ', p1.x, ' ', p1.y, ' ');
  7126. return path;
  7127. },
  7128. /**
  7129. * Creates fabric.Path object to add on canvas
  7130. * @param {String} pathData Path data
  7131. * @return {fabric.Path} Path to add on canvas
  7132. */
  7133. createPath: function(pathData) {
  7134. var path = new fabric.Path(pathData, {
  7135. fill: null,
  7136. stroke: this.color,
  7137. strokeWidth: this.width,
  7138. strokeLineCap: this.strokeLineCap,
  7139. strokeLineJoin: this.strokeLineJoin,
  7140. strokeDashArray: this.strokeDashArray,
  7141. originX: 'center',
  7142. originY: 'center'
  7143. });
  7144. if (this.shadow) {
  7145. this.shadow.affectStroke = true;
  7146. path.setShadow(this.shadow);
  7147. }
  7148. return path;
  7149. },
  7150. /**
  7151. * On mouseup after drawing the path on contextTop canvas
  7152. * we use the points captured to create an new fabric path object
  7153. * and add it to the fabric canvas.
  7154. */
  7155. _finalizeAndAddPath: function() {
  7156. var ctx = this.canvas.contextTop;
  7157. ctx.closePath();
  7158. var pathData = this.convertPointsToSVGPath(this._points).join('');
  7159. if (pathData === 'M 0 0 Q 0 0 0 0 L 0 0') {
  7160. // do not create 0 width/height paths, as they are
  7161. // rendered inconsistently across browsers
  7162. // Firefox 4, for example, renders a dot,
  7163. // whereas Chrome 10 renders nothing
  7164. this.canvas.renderAll();
  7165. return;
  7166. }
  7167. var path = this.createPath(pathData);
  7168. this.canvas.add(path);
  7169. path.setCoords();
  7170. this.canvas.clearContext(this.canvas.contextTop);
  7171. this._resetShadow();
  7172. this.canvas.renderAll();
  7173. // fire event 'path' created
  7174. this.canvas.fire('path:created', { path: path });
  7175. }
  7176. });
  7177. })();
  7178. /**
  7179. * CircleBrush class
  7180. * @class fabric.CircleBrush
  7181. */
  7182. fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.CircleBrush.prototype */ {
  7183. /**
  7184. * Width of a brush
  7185. * @type Number
  7186. * @default
  7187. */
  7188. width: 10,
  7189. /**
  7190. * Constructor
  7191. * @param {fabric.Canvas} canvas
  7192. * @return {fabric.CircleBrush} Instance of a circle brush
  7193. */
  7194. initialize: function(canvas) {
  7195. this.canvas = canvas;
  7196. this.points = [];
  7197. },
  7198. /**
  7199. * Invoked inside on mouse down and mouse move
  7200. * @param {Object} pointer
  7201. */
  7202. drawDot: function(pointer) {
  7203. var point = this.addPoint(pointer),
  7204. ctx = this.canvas.contextTop,
  7205. v = this.canvas.viewportTransform;
  7206. ctx.save();
  7207. ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);
  7208. ctx.fillStyle = point.fill;
  7209. ctx.beginPath();
  7210. ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false);
  7211. ctx.closePath();
  7212. ctx.fill();
  7213. ctx.restore();
  7214. },
  7215. /**
  7216. * Invoked on mouse down
  7217. */
  7218. onMouseDown: function(pointer) {
  7219. this.points.length = 0;
  7220. this.canvas.clearContext(this.canvas.contextTop);
  7221. this._setShadow();
  7222. this.drawDot(pointer);
  7223. },
  7224. /**
  7225. * Invoked on mouse move
  7226. * @param {Object} pointer
  7227. */
  7228. onMouseMove: function(pointer) {
  7229. this.drawDot(pointer);
  7230. },
  7231. /**
  7232. * Invoked on mouse up
  7233. */
  7234. onMouseUp: function() {
  7235. var originalRenderOnAddRemove = this.canvas.renderOnAddRemove;
  7236. this.canvas.renderOnAddRemove = false;
  7237. var circles = [];
  7238. for (var i = 0, len = this.points.length; i < len; i++) {
  7239. var point = this.points[i],
  7240. circle = new fabric.Circle({
  7241. radius: point.radius,
  7242. left: point.x,
  7243. top: point.y,
  7244. originX: 'center',
  7245. originY: 'center',
  7246. fill: point.fill
  7247. });
  7248. this.shadow && circle.setShadow(this.shadow);
  7249. circles.push(circle);
  7250. }
  7251. var group = new fabric.Group(circles, { originX: 'center', originY: 'center' });
  7252. group.canvas = this.canvas;
  7253. this.canvas.add(group);
  7254. this.canvas.fire('path:created', { path: group });
  7255. this.canvas.clearContext(this.canvas.contextTop);
  7256. this._resetShadow();
  7257. this.canvas.renderOnAddRemove = originalRenderOnAddRemove;
  7258. this.canvas.renderAll();
  7259. },
  7260. /**
  7261. * @param {Object} pointer
  7262. * @return {fabric.Point} Just added pointer point
  7263. */
  7264. addPoint: function(pointer) {
  7265. var pointerPoint = new fabric.Point(pointer.x, pointer.y),
  7266. circleRadius = fabric.util.getRandomInt(
  7267. Math.max(0, this.width - 20), this.width + 20) / 2,
  7268. circleColor = new fabric.Color(this.color)
  7269. .setAlpha(fabric.util.getRandomInt(0, 100) / 100)
  7270. .toRgba();
  7271. pointerPoint.radius = circleRadius;
  7272. pointerPoint.fill = circleColor;
  7273. this.points.push(pointerPoint);
  7274. return pointerPoint;
  7275. }
  7276. });
  7277. /**
  7278. * SprayBrush class
  7279. * @class fabric.SprayBrush
  7280. */
  7281. fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric.SprayBrush.prototype */ {
  7282. /**
  7283. * Width of a spray
  7284. * @type Number
  7285. * @default
  7286. */
  7287. width: 10,
  7288. /**
  7289. * Density of a spray (number of dots per chunk)
  7290. * @type Number
  7291. * @default
  7292. */
  7293. density: 20,
  7294. /**
  7295. * Width of spray dots
  7296. * @type Number
  7297. * @default
  7298. */
  7299. dotWidth: 1,
  7300. /**
  7301. * Width variance of spray dots
  7302. * @type Number
  7303. * @default
  7304. */
  7305. dotWidthVariance: 1,
  7306. /**
  7307. * Whether opacity of a dot should be random
  7308. * @type Boolean
  7309. * @default
  7310. */
  7311. randomOpacity: false,
  7312. /**
  7313. * Whether overlapping dots (rectangles) should be removed (for performance reasons)
  7314. * @type Boolean
  7315. * @default
  7316. */
  7317. optimizeOverlapping: true,
  7318. /**
  7319. * Constructor
  7320. * @param {fabric.Canvas} canvas
  7321. * @return {fabric.SprayBrush} Instance of a spray brush
  7322. */
  7323. initialize: function(canvas) {
  7324. this.canvas = canvas;
  7325. this.sprayChunks = [];
  7326. },
  7327. /**
  7328. * Invoked on mouse down
  7329. * @param {Object} pointer
  7330. */
  7331. onMouseDown: function(pointer) {
  7332. this.sprayChunks.length = 0;
  7333. this.canvas.clearContext(this.canvas.contextTop);
  7334. this._setShadow();
  7335. this.addSprayChunk(pointer);
  7336. this.render();
  7337. },
  7338. /**
  7339. * Invoked on mouse move
  7340. * @param {Object} pointer
  7341. */
  7342. onMouseMove: function(pointer) {
  7343. this.addSprayChunk(pointer);
  7344. this.render();
  7345. },
  7346. /**
  7347. * Invoked on mouse up
  7348. */
  7349. onMouseUp: function() {
  7350. var originalRenderOnAddRemove = this.canvas.renderOnAddRemove;
  7351. this.canvas.renderOnAddRemove = false;
  7352. var rects = [];
  7353. for (var i = 0, ilen = this.sprayChunks.length; i < ilen; i++) {
  7354. var sprayChunk = this.sprayChunks[i];
  7355. for (var j = 0, jlen = sprayChunk.length; j < jlen; j++) {
  7356. var rect = new fabric.Rect({
  7357. width: sprayChunk[j].width,
  7358. height: sprayChunk[j].width,
  7359. left: sprayChunk[j].x + 1,
  7360. top: sprayChunk[j].y + 1,
  7361. originX: 'center',
  7362. originY: 'center',
  7363. fill: this.color
  7364. });
  7365. this.shadow && rect.setShadow(this.shadow);
  7366. rects.push(rect);
  7367. }
  7368. }
  7369. if (this.optimizeOverlapping) {
  7370. rects = this._getOptimizedRects(rects);
  7371. }
  7372. var group = new fabric.Group(rects, { originX: 'center', originY: 'center' });
  7373. group.canvas = this.canvas;
  7374. this.canvas.add(group);
  7375. this.canvas.fire('path:created', { path: group });
  7376. this.canvas.clearContext(this.canvas.contextTop);
  7377. this._resetShadow();
  7378. this.canvas.renderOnAddRemove = originalRenderOnAddRemove;
  7379. this.canvas.renderAll();
  7380. },
  7381. /**
  7382. * @private
  7383. * @param {Array} rects
  7384. */
  7385. _getOptimizedRects: function(rects) {
  7386. // avoid creating duplicate rects at the same coordinates
  7387. var uniqueRects = { }, key;
  7388. for (var i = 0, len = rects.length; i < len; i++) {
  7389. key = rects[i].left + '' + rects[i].top;
  7390. if (!uniqueRects[key]) {
  7391. uniqueRects[key] = rects[i];
  7392. }
  7393. }
  7394. var uniqueRectsArray = [];
  7395. for (key in uniqueRects) {
  7396. uniqueRectsArray.push(uniqueRects[key]);
  7397. }
  7398. return uniqueRectsArray;
  7399. },
  7400. /**
  7401. * Renders brush
  7402. */
  7403. render: function() {
  7404. var ctx = this.canvas.contextTop;
  7405. ctx.fillStyle = this.color;
  7406. var v = this.canvas.viewportTransform;
  7407. ctx.save();
  7408. ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);
  7409. for (var i = 0, len = this.sprayChunkPoints.length; i < len; i++) {
  7410. var point = this.sprayChunkPoints[i];
  7411. if (typeof point.opacity !== 'undefined') {
  7412. ctx.globalAlpha = point.opacity;
  7413. }
  7414. ctx.fillRect(point.x, point.y, point.width, point.width);
  7415. }
  7416. ctx.restore();
  7417. },
  7418. /**
  7419. * @param {Object} pointer
  7420. */
  7421. addSprayChunk: function(pointer) {
  7422. this.sprayChunkPoints = [];
  7423. var x, y, width, radius = this.width / 2;
  7424. for (var i = 0; i < this.density; i++) {
  7425. x = fabric.util.getRandomInt(pointer.x - radius, pointer.x + radius);
  7426. y = fabric.util.getRandomInt(pointer.y - radius, pointer.y + radius);
  7427. if (this.dotWidthVariance) {
  7428. width = fabric.util.getRandomInt(
  7429. // bottom clamp width to 1
  7430. Math.max(1, this.dotWidth - this.dotWidthVariance),
  7431. this.dotWidth + this.dotWidthVariance);
  7432. }
  7433. else {
  7434. width = this.dotWidth;
  7435. }
  7436. var point = new fabric.Point(x, y);
  7437. point.width = width;
  7438. if (this.randomOpacity) {
  7439. point.opacity = fabric.util.getRandomInt(0, 100) / 100;
  7440. }
  7441. this.sprayChunkPoints.push(point);
  7442. }
  7443. this.sprayChunks.push(this.sprayChunkPoints);
  7444. }
  7445. });
  7446. /**
  7447. * PatternBrush class
  7448. * @class fabric.PatternBrush
  7449. * @extends fabric.BaseBrush
  7450. */
  7451. fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fabric.PatternBrush.prototype */ {
  7452. getPatternSrc: function() {
  7453. var dotWidth = 20,
  7454. dotDistance = 5,
  7455. patternCanvas = fabric.document.createElement('canvas'),
  7456. patternCtx = patternCanvas.getContext('2d');
  7457. patternCanvas.width = patternCanvas.height = dotWidth + dotDistance;
  7458. patternCtx.fillStyle = this.color;
  7459. patternCtx.beginPath();
  7460. patternCtx.arc(dotWidth / 2, dotWidth / 2, dotWidth / 2, 0, Math.PI * 2, false);
  7461. patternCtx.closePath();
  7462. patternCtx.fill();
  7463. return patternCanvas;
  7464. },
  7465. getPatternSrcFunction: function() {
  7466. return String(this.getPatternSrc).replace('this.color', '"' + this.color + '"');
  7467. },
  7468. /**
  7469. * Creates "pattern" instance property
  7470. */
  7471. getPattern: function() {
  7472. return this.canvas.contextTop.createPattern(this.source || this.getPatternSrc(), 'repeat');
  7473. },
  7474. /**
  7475. * Sets brush styles
  7476. */
  7477. _setBrushStyles: function() {
  7478. this.callSuper('_setBrushStyles');
  7479. this.canvas.contextTop.strokeStyle = this.getPattern();
  7480. },
  7481. /**
  7482. * Creates path
  7483. */
  7484. createPath: function(pathData) {
  7485. var path = this.callSuper('createPath', pathData),
  7486. topLeft = path._getLeftTopCoords().scalarAdd(path.strokeWidth / 2);
  7487. path.stroke = new fabric.Pattern({
  7488. source: this.source || this.getPatternSrcFunction(),
  7489. offsetX: -topLeft.x,
  7490. offsetY: -topLeft.y
  7491. });
  7492. return path;
  7493. }
  7494. });
  7495. (function() {
  7496. var getPointer = fabric.util.getPointer,
  7497. degreesToRadians = fabric.util.degreesToRadians,
  7498. radiansToDegrees = fabric.util.radiansToDegrees,
  7499. atan2 = Math.atan2,
  7500. abs = Math.abs,
  7501. supportLineDash = fabric.StaticCanvas.supports('setLineDash'),
  7502. STROKE_OFFSET = 0.5;
  7503. /**
  7504. * Canvas class
  7505. * @class fabric.Canvas
  7506. * @extends fabric.StaticCanvas
  7507. * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#canvas}
  7508. * @see {@link fabric.Canvas#initialize} for constructor definition
  7509. *
  7510. * @fires object:added
  7511. * @fires object:modified
  7512. * @fires object:rotating
  7513. * @fires object:scaling
  7514. * @fires object:moving
  7515. * @fires object:selected
  7516. *
  7517. * @fires before:selection:cleared
  7518. * @fires selection:cleared
  7519. * @fires selection:created
  7520. *
  7521. * @fires path:created
  7522. * @fires mouse:down
  7523. * @fires mouse:move
  7524. * @fires mouse:up
  7525. * @fires mouse:over
  7526. * @fires mouse:out
  7527. *
  7528. */
  7529. fabric.Canvas = fabric.util.createClass(fabric.StaticCanvas, /** @lends fabric.Canvas.prototype */ {
  7530. /**
  7531. * Constructor
  7532. * @param {HTMLElement | String} el &lt;canvas> element to initialize instance on
  7533. * @param {Object} [options] Options object
  7534. * @return {Object} thisArg
  7535. */
  7536. initialize: function(el, options) {
  7537. options || (options = { });
  7538. this._initStatic(el, options);
  7539. this._initInteractive();
  7540. this._createCacheCanvas();
  7541. },
  7542. /**
  7543. * When true, objects can be transformed by one side (unproportionally)
  7544. * @type Boolean
  7545. * @default
  7546. */
  7547. uniScaleTransform: false,
  7548. /**
  7549. * Indicates which key enable unproportional scaling
  7550. * values: 'altKey', 'shiftKey', 'ctrlKey'.
  7551. * If `null` or 'none' or any other string that is not a modifier key
  7552. * feature is disabled feature disabled.
  7553. * @since 1.6.2
  7554. * @type String
  7555. * @default
  7556. */
  7557. uniScaleKey: 'shiftKey',
  7558. /**
  7559. * When true, objects use center point as the origin of scale transformation.
  7560. * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean).
  7561. * @since 1.3.4
  7562. * @type Boolean
  7563. * @default
  7564. */
  7565. centeredScaling: false,
  7566. /**
  7567. * When true, objects use center point as the origin of rotate transformation.
  7568. * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean).
  7569. * @since 1.3.4
  7570. * @type Boolean
  7571. * @default
  7572. */
  7573. centeredRotation: false,
  7574. /**
  7575. * Indicates which key enable centered Transfrom
  7576. * values: 'altKey', 'shiftKey', 'ctrlKey'.
  7577. * If `null` or 'none' or any other string that is not a modifier key
  7578. * feature is disabled feature disabled.
  7579. * @since 1.6.2
  7580. * @type String
  7581. * @default
  7582. */
  7583. centeredKey: 'altKey',
  7584. /**
  7585. * Indicates which key enable alternate action on corner
  7586. * values: 'altKey', 'shiftKey', 'ctrlKey'.
  7587. * If `null` or 'none' or any other string that is not a modifier key
  7588. * feature is disabled feature disabled.
  7589. * @since 1.6.2
  7590. * @type String
  7591. * @default
  7592. */
  7593. altActionKey: 'shiftKey',
  7594. /**
  7595. * Indicates that canvas is interactive. This property should not be changed.
  7596. * @type Boolean
  7597. * @default
  7598. */
  7599. interactive: true,
  7600. /**
  7601. * Indicates whether group selection should be enabled
  7602. * @type Boolean
  7603. * @default
  7604. */
  7605. selection: true,
  7606. /**
  7607. * Indicates which key enable multiple click selection
  7608. * values: 'altKey', 'shiftKey', 'ctrlKey'.
  7609. * If `null` or 'none' or any other string that is not a modifier key
  7610. * feature is disabled feature disabled.
  7611. * @since 1.6.2
  7612. * @type String
  7613. * @default
  7614. */
  7615. selectionKey: 'shiftKey',
  7616. /**
  7617. * Indicates which key enable alternative selection
  7618. * in case of target overlapping with active object
  7619. * values: 'altKey', 'shiftKey', 'ctrlKey'.
  7620. * If `null` or 'none' or any other string that is not a modifier key
  7621. * feature is disabled feature disabled.
  7622. * @since 1.6.5
  7623. * @type null|String
  7624. * @default
  7625. */
  7626. altSelectionKey: null,
  7627. /**
  7628. * Color of selection
  7629. * @type String
  7630. * @default
  7631. */
  7632. selectionColor: 'rgba(100, 100, 255, 0.3)', // blue
  7633. /**
  7634. * Default dash array pattern
  7635. * If not empty the selection border is dashed
  7636. * @type Array
  7637. */
  7638. selectionDashArray: [],
  7639. /**
  7640. * Color of the border of selection (usually slightly darker than color of selection itself)
  7641. * @type String
  7642. * @default
  7643. */
  7644. selectionBorderColor: 'rgba(255, 255, 255, 0.3)',
  7645. /**
  7646. * Width of a line used in object/group selection
  7647. * @type Number
  7648. * @default
  7649. */
  7650. selectionLineWidth: 1,
  7651. /**
  7652. * Default cursor value used when hovering over an object on canvas
  7653. * @type String
  7654. * @default
  7655. */
  7656. hoverCursor: 'move',
  7657. /**
  7658. * Default cursor value used when moving an object on canvas
  7659. * @type String
  7660. * @default
  7661. */
  7662. moveCursor: 'move',
  7663. /**
  7664. * Default cursor value used for the entire canvas
  7665. * @type String
  7666. * @default
  7667. */
  7668. defaultCursor: 'default',
  7669. /**
  7670. * Cursor value used during free drawing
  7671. * @type String
  7672. * @default
  7673. */
  7674. freeDrawingCursor: 'crosshair',
  7675. /**
  7676. * Cursor value used for rotation point
  7677. * @type String
  7678. * @default
  7679. */
  7680. rotationCursor: 'crosshair',
  7681. /**
  7682. * Default element class that's given to wrapper (div) element of canvas
  7683. * @type String
  7684. * @default
  7685. */
  7686. containerClass: 'canvas-container',
  7687. /**
  7688. * When true, object detection happens on per-pixel basis rather than on per-bounding-box
  7689. * @type Boolean
  7690. * @default
  7691. */
  7692. perPixelTargetFind: false,
  7693. /**
  7694. * Number of pixels around target pixel to tolerate (consider active) during object detection
  7695. * @type Number
  7696. * @default
  7697. */
  7698. targetFindTolerance: 0,
  7699. /**
  7700. * When true, target detection is skipped when hovering over canvas. This can be used to improve performance.
  7701. * @type Boolean
  7702. * @default
  7703. */
  7704. skipTargetFind: false,
  7705. /**
  7706. * When true, mouse events on canvas (mousedown/mousemove/mouseup) result in free drawing.
  7707. * After mousedown, mousemove creates a shape,
  7708. * and then mouseup finalizes it and adds an instance of `fabric.Path` onto canvas.
  7709. * @tutorial {@link http://fabricjs.com/fabric-intro-part-4#free_drawing}
  7710. * @type Boolean
  7711. * @default
  7712. */
  7713. isDrawingMode: false,
  7714. /**
  7715. * Indicates whether objects should remain in current stack position when selected.
  7716. * When false objects are brought to top and rendered as part of the selection group
  7717. * @type Boolean
  7718. * @default
  7719. */
  7720. preserveObjectStacking: false,
  7721. /**
  7722. * Indicates the angle that an object will lock to while rotating.
  7723. * @type Number
  7724. * @since 1.6.7
  7725. * @default
  7726. */
  7727. snapAngle: 0,
  7728. /**
  7729. * Indicates the distance from the snapAngle the rotation will lock to the snapAngle.
  7730. * When `null`, the snapThreshold will default to the snapAngle.
  7731. * @type null|Number
  7732. * @since 1.6.7
  7733. * @default
  7734. */
  7735. snapThreshold: null,
  7736. /**
  7737. * Indicates if the right click on canvas can output the context menu or not
  7738. * @type Boolean
  7739. * @since 1.6.5
  7740. * @default
  7741. */
  7742. stopContextMenu: false,
  7743. /**
  7744. * Indicates if the canvas can fire right click events
  7745. * @type Boolean
  7746. * @since 1.6.5
  7747. * @default
  7748. */
  7749. fireRightClick: false,
  7750. /**
  7751. * Indicates if the canvas can fire middle click events
  7752. * @type Boolean
  7753. * @since 1.7.8
  7754. * @default
  7755. */
  7756. fireMiddleClick: false,
  7757. /**
  7758. * @private
  7759. */
  7760. _initInteractive: function() {
  7761. this._currentTransform = null;
  7762. this._groupSelector = null;
  7763. this._initWrapperElement();
  7764. this._createUpperCanvas();
  7765. this._initEventListeners();
  7766. this._initRetinaScaling();
  7767. this.freeDrawingBrush = fabric.PencilBrush && new fabric.PencilBrush(this);
  7768. this.calcOffset();
  7769. },
  7770. /**
  7771. * Divides objects in two groups, one to render immediately
  7772. * and one to render as activeGroup.
  7773. * @return {Array} objects to render immediately and pushes the other in the activeGroup.
  7774. */
  7775. _chooseObjectsToRender: function() {
  7776. var activeGroup = this.getActiveGroup(),
  7777. activeObject = this.getActiveObject(),
  7778. object, objsToRender = [], activeGroupObjects = [];
  7779. if ((activeGroup || activeObject) && !this.preserveObjectStacking) {
  7780. for (var i = 0, length = this._objects.length; i < length; i++) {
  7781. object = this._objects[i];
  7782. if ((!activeGroup || !activeGroup.contains(object)) && object !== activeObject) {
  7783. objsToRender.push(object);
  7784. }
  7785. else {
  7786. activeGroupObjects.push(object);
  7787. }
  7788. }
  7789. if (activeGroup) {
  7790. activeGroup._set('_objects', activeGroupObjects);
  7791. objsToRender.push(activeGroup);
  7792. }
  7793. activeObject && objsToRender.push(activeObject);
  7794. }
  7795. else {
  7796. objsToRender = this._objects;
  7797. }
  7798. return objsToRender;
  7799. },
  7800. /**
  7801. * Renders both the top canvas and the secondary container canvas.
  7802. * @return {fabric.Canvas} instance
  7803. * @chainable
  7804. */
  7805. renderAll: function () {
  7806. if (this.contextTopDirty && !this._groupSelector && !this.isDrawingMode) {
  7807. this.clearContext(this.contextTop);
  7808. this.contextTopDirty = false;
  7809. }
  7810. var canvasToDrawOn = this.contextContainer;
  7811. this.renderCanvas(canvasToDrawOn, this._chooseObjectsToRender());
  7812. return this;
  7813. },
  7814. /**
  7815. * Method to render only the top canvas.
  7816. * Also used to render the group selection box.
  7817. * @return {fabric.Canvas} thisArg
  7818. * @chainable
  7819. */
  7820. renderTop: function () {
  7821. var ctx = this.contextTop;
  7822. this.clearContext(ctx);
  7823. // we render the top context - last object
  7824. if (this.selection && this._groupSelector) {
  7825. this._drawSelection(ctx);
  7826. }
  7827. this.fire('after:render');
  7828. this.contextTopDirty = true;
  7829. return this;
  7830. },
  7831. /**
  7832. * Resets the current transform to its original values and chooses the type of resizing based on the event
  7833. * @private
  7834. */
  7835. _resetCurrentTransform: function() {
  7836. var t = this._currentTransform;
  7837. t.target.set({
  7838. scaleX: t.original.scaleX,
  7839. scaleY: t.original.scaleY,
  7840. skewX: t.original.skewX,
  7841. skewY: t.original.skewY,
  7842. left: t.original.left,
  7843. top: t.original.top
  7844. });
  7845. if (this._shouldCenterTransform(t.target)) {
  7846. if (t.action === 'rotate') {
  7847. this._setOriginToCenter(t.target);
  7848. }
  7849. else {
  7850. if (t.originX !== 'center') {
  7851. if (t.originX === 'right') {
  7852. t.mouseXSign = -1;
  7853. }
  7854. else {
  7855. t.mouseXSign = 1;
  7856. }
  7857. }
  7858. if (t.originY !== 'center') {
  7859. if (t.originY === 'bottom') {
  7860. t.mouseYSign = -1;
  7861. }
  7862. else {
  7863. t.mouseYSign = 1;
  7864. }
  7865. }
  7866. t.originX = 'center';
  7867. t.originY = 'center';
  7868. }
  7869. }
  7870. else {
  7871. t.originX = t.original.originX;
  7872. t.originY = t.original.originY;
  7873. }
  7874. },
  7875. /**
  7876. * Checks if point is contained within an area of given object
  7877. * @param {Event} e Event object
  7878. * @param {fabric.Object} target Object to test against
  7879. * @param {Object} [point] x,y object of point coordinates we want to check.
  7880. * @return {Boolean} true if point is contained within an area of given object
  7881. */
  7882. containsPoint: function (e, target, point) {
  7883. var ignoreZoom = true,
  7884. pointer = point || this.getPointer(e, ignoreZoom),
  7885. xy;
  7886. if (target.group && target.group === this.getActiveGroup()) {
  7887. xy = this._normalizePointer(target.group, pointer);
  7888. }
  7889. else {
  7890. xy = { x: pointer.x, y: pointer.y };
  7891. }
  7892. // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html
  7893. // http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html
  7894. return (target.containsPoint(xy) || target._findTargetCorner(pointer));
  7895. },
  7896. /**
  7897. * @private
  7898. */
  7899. _normalizePointer: function (object, pointer) {
  7900. var m = object.calcTransformMatrix(),
  7901. invertedM = fabric.util.invertTransform(m),
  7902. vptPointer = this.restorePointerVpt(pointer);
  7903. return fabric.util.transformPoint(vptPointer, invertedM);
  7904. },
  7905. /**
  7906. * Returns true if object is transparent at a certain location
  7907. * @param {fabric.Object} target Object to check
  7908. * @param {Number} x Left coordinate
  7909. * @param {Number} y Top coordinate
  7910. * @return {Boolean}
  7911. */
  7912. isTargetTransparent: function (target, x, y) {
  7913. var hasBorders = target.hasBorders,
  7914. transparentCorners = target.transparentCorners,
  7915. ctx = this.contextCache,
  7916. originalColor = target.selectionBackgroundColor;
  7917. target.hasBorders = target.transparentCorners = false;
  7918. target.selectionBackgroundColor = '';
  7919. ctx.save();
  7920. ctx.transform.apply(ctx, this.viewportTransform);
  7921. target.render(ctx);
  7922. ctx.restore();
  7923. target.active && target._renderControls(ctx);
  7924. target.hasBorders = hasBorders;
  7925. target.transparentCorners = transparentCorners;
  7926. target.selectionBackgroundColor = originalColor;
  7927. var isTransparent = fabric.util.isTransparent(
  7928. ctx, x, y, this.targetFindTolerance);
  7929. this.clearContext(ctx);
  7930. return isTransparent;
  7931. },
  7932. /**
  7933. * @private
  7934. * @param {Event} e Event object
  7935. * @param {fabric.Object} target
  7936. */
  7937. _shouldClearSelection: function (e, target) {
  7938. var activeGroup = this.getActiveGroup(),
  7939. activeObject = this.getActiveObject();
  7940. return (
  7941. !target
  7942. ||
  7943. (target &&
  7944. activeGroup &&
  7945. !activeGroup.contains(target) &&
  7946. activeGroup !== target &&
  7947. !e[this.selectionKey])
  7948. ||
  7949. (target && !target.evented)
  7950. ||
  7951. (target &&
  7952. !target.selectable &&
  7953. activeObject &&
  7954. activeObject !== target)
  7955. );
  7956. },
  7957. /**
  7958. * @private
  7959. * @param {fabric.Object} target
  7960. */
  7961. _shouldCenterTransform: function (target) {
  7962. if (!target) {
  7963. return;
  7964. }
  7965. var t = this._currentTransform,
  7966. centerTransform;
  7967. if (t.action === 'scale' || t.action === 'scaleX' || t.action === 'scaleY') {
  7968. centerTransform = this.centeredScaling || target.centeredScaling;
  7969. }
  7970. else if (t.action === 'rotate') {
  7971. centerTransform = this.centeredRotation || target.centeredRotation;
  7972. }
  7973. return centerTransform ? !t.altKey : t.altKey;
  7974. },
  7975. /**
  7976. * @private
  7977. */
  7978. _getOriginFromCorner: function(target, corner) {
  7979. var origin = {
  7980. x: target.originX,
  7981. y: target.originY
  7982. };
  7983. if (corner === 'ml' || corner === 'tl' || corner === 'bl') {
  7984. origin.x = 'right';
  7985. }
  7986. else if (corner === 'mr' || corner === 'tr' || corner === 'br') {
  7987. origin.x = 'left';
  7988. }
  7989. if (corner === 'tl' || corner === 'mt' || corner === 'tr') {
  7990. origin.y = 'bottom';
  7991. }
  7992. else if (corner === 'bl' || corner === 'mb' || corner === 'br') {
  7993. origin.y = 'top';
  7994. }
  7995. return origin;
  7996. },
  7997. /**
  7998. * @private
  7999. */
  8000. _getActionFromCorner: function(target, corner, e) {
  8001. if (!corner) {
  8002. return 'drag';
  8003. }
  8004. switch (corner) {
  8005. case 'mtr':
  8006. return 'rotate';
  8007. case 'ml':
  8008. case 'mr':
  8009. return e[this.altActionKey] ? 'skewY' : 'scaleX';
  8010. case 'mt':
  8011. case 'mb':
  8012. return e[this.altActionKey] ? 'skewX' : 'scaleY';
  8013. default:
  8014. return 'scale';
  8015. }
  8016. },
  8017. /**
  8018. * @private
  8019. * @param {Event} e Event object
  8020. * @param {fabric.Object} target
  8021. */
  8022. _setupCurrentTransform: function (e, target) {
  8023. if (!target) {
  8024. return;
  8025. }
  8026. var pointer = this.getPointer(e),
  8027. corner = target._findTargetCorner(this.getPointer(e, true)),
  8028. action = this._getActionFromCorner(target, corner, e),
  8029. origin = this._getOriginFromCorner(target, corner);
  8030. this._currentTransform = {
  8031. target: target,
  8032. action: action,
  8033. corner: corner,
  8034. scaleX: target.scaleX,
  8035. scaleY: target.scaleY,
  8036. skewX: target.skewX,
  8037. skewY: target.skewY,
  8038. offsetX: pointer.x - target.left,
  8039. offsetY: pointer.y - target.top,
  8040. originX: origin.x,
  8041. originY: origin.y,
  8042. ex: pointer.x,
  8043. ey: pointer.y,
  8044. lastX: pointer.x,
  8045. lastY: pointer.y,
  8046. left: target.left,
  8047. top: target.top,
  8048. theta: degreesToRadians(target.angle),
  8049. width: target.width * target.scaleX,
  8050. mouseXSign: 1,
  8051. mouseYSign: 1,
  8052. shiftKey: e.shiftKey,
  8053. altKey: e[this.centeredKey]
  8054. };
  8055. this._currentTransform.original = {
  8056. left: target.left,
  8057. top: target.top,
  8058. scaleX: target.scaleX,
  8059. scaleY: target.scaleY,
  8060. skewX: target.skewX,
  8061. skewY: target.skewY,
  8062. originX: origin.x,
  8063. originY: origin.y
  8064. };
  8065. this._resetCurrentTransform();
  8066. },
  8067. /**
  8068. * Translates object by "setting" its left/top
  8069. * @private
  8070. * @param {Number} x pointer's x coordinate
  8071. * @param {Number} y pointer's y coordinate
  8072. * @return {Boolean} true if the translation occurred
  8073. */
  8074. _translateObject: function (x, y) {
  8075. var transform = this._currentTransform,
  8076. target = transform.target,
  8077. newLeft = x - transform.offsetX,
  8078. newTop = y - transform.offsetY,
  8079. moveX = !target.get('lockMovementX') && target.left !== newLeft,
  8080. moveY = !target.get('lockMovementY') && target.top !== newTop;
  8081. moveX && target.set('left', newLeft);
  8082. moveY && target.set('top', newTop);
  8083. return moveX || moveY;
  8084. },
  8085. /**
  8086. * Check if we are increasing a positive skew or lower it,
  8087. * checking mouse direction and pressed corner.
  8088. * @private
  8089. */
  8090. _changeSkewTransformOrigin: function(mouseMove, t, by) {
  8091. var property = 'originX', origins = { 0: 'center' },
  8092. skew = t.target.skewX, originA = 'left', originB = 'right',
  8093. corner = t.corner === 'mt' || t.corner === 'ml' ? 1 : -1,
  8094. flipSign = 1;
  8095. mouseMove = mouseMove > 0 ? 1 : -1;
  8096. if (by === 'y') {
  8097. skew = t.target.skewY;
  8098. originA = 'top';
  8099. originB = 'bottom';
  8100. property = 'originY';
  8101. }
  8102. origins[-1] = originA;
  8103. origins[1] = originB;
  8104. t.target.flipX && (flipSign *= -1);
  8105. t.target.flipY && (flipSign *= -1);
  8106. if (skew === 0) {
  8107. t.skewSign = -corner * mouseMove * flipSign;
  8108. t[property] = origins[-mouseMove];
  8109. }
  8110. else {
  8111. skew = skew > 0 ? 1 : -1;
  8112. t.skewSign = skew;
  8113. t[property] = origins[skew * corner * flipSign];
  8114. }
  8115. },
  8116. /**
  8117. * Skew object by mouse events
  8118. * @private
  8119. * @param {Number} x pointer's x coordinate
  8120. * @param {Number} y pointer's y coordinate
  8121. * @param {String} by Either 'x' or 'y'
  8122. * @return {Boolean} true if the skewing occurred
  8123. */
  8124. _skewObject: function (x, y, by) {
  8125. var t = this._currentTransform,
  8126. target = t.target, skewed = false,
  8127. lockSkewingX = target.get('lockSkewingX'),
  8128. lockSkewingY = target.get('lockSkewingY');
  8129. if ((lockSkewingX && by === 'x') || (lockSkewingY && by === 'y')) {
  8130. return false;
  8131. }
  8132. // Get the constraint point
  8133. var center = target.getCenterPoint(),
  8134. actualMouseByCenter = target.toLocalPoint(new fabric.Point(x, y), 'center', 'center')[by],
  8135. lastMouseByCenter = target.toLocalPoint(new fabric.Point(t.lastX, t.lastY), 'center', 'center')[by],
  8136. actualMouseByOrigin, constraintPosition, dim = target._getTransformedDimensions();
  8137. this._changeSkewTransformOrigin(actualMouseByCenter - lastMouseByCenter, t, by);
  8138. actualMouseByOrigin = target.toLocalPoint(new fabric.Point(x, y), t.originX, t.originY)[by];
  8139. constraintPosition = target.translateToOriginPoint(center, t.originX, t.originY);
  8140. // Actually skew the object
  8141. skewed = this._setObjectSkew(actualMouseByOrigin, t, by, dim);
  8142. t.lastX = x;
  8143. t.lastY = y;
  8144. // Make sure the constraints apply
  8145. target.setPositionByOrigin(constraintPosition, t.originX, t.originY);
  8146. return skewed;
  8147. },
  8148. /**
  8149. * Set object skew
  8150. * @private
  8151. * @return {Boolean} true if the skewing occurred
  8152. */
  8153. _setObjectSkew: function(localMouse, transform, by, _dim) {
  8154. var target = transform.target, newValue, skewed = false,
  8155. skewSign = transform.skewSign, newDim, dimNoSkew,
  8156. otherBy, _otherBy, _by, newDimMouse, skewX, skewY;
  8157. if (by === 'x') {
  8158. otherBy = 'y';
  8159. _otherBy = 'Y';
  8160. _by = 'X';
  8161. skewX = 0;
  8162. skewY = target.skewY;
  8163. }
  8164. else {
  8165. otherBy = 'x';
  8166. _otherBy = 'X';
  8167. _by = 'Y';
  8168. skewX = target.skewX;
  8169. skewY = 0;
  8170. }
  8171. dimNoSkew = target._getTransformedDimensions(skewX, skewY);
  8172. newDimMouse = 2 * Math.abs(localMouse) - dimNoSkew[by];
  8173. if (newDimMouse <= 2) {
  8174. newValue = 0;
  8175. }
  8176. else {
  8177. newValue = skewSign * Math.atan((newDimMouse / target['scale' + _by]) /
  8178. (dimNoSkew[otherBy] / target['scale' + _otherBy]));
  8179. newValue = fabric.util.radiansToDegrees(newValue);
  8180. }
  8181. skewed = target['skew' + _by] !== newValue;
  8182. target.set('skew' + _by, newValue);
  8183. if (target['skew' + _otherBy] !== 0) {
  8184. newDim = target._getTransformedDimensions();
  8185. newValue = (_dim[otherBy] / newDim[otherBy]) * target['scale' + _otherBy];
  8186. target.set('scale' + _otherBy, newValue);
  8187. }
  8188. return skewed;
  8189. },
  8190. /**
  8191. * Scales object by invoking its scaleX/scaleY methods
  8192. * @private
  8193. * @param {Number} x pointer's x coordinate
  8194. * @param {Number} y pointer's y coordinate
  8195. * @param {String} by Either 'x' or 'y' - specifies dimension constraint by which to scale an object.
  8196. * When not provided, an object is scaled by both dimensions equally
  8197. * @return {Boolean} true if the scaling occurred
  8198. */
  8199. _scaleObject: function (x, y, by) {
  8200. var t = this._currentTransform,
  8201. target = t.target,
  8202. lockScalingX = target.get('lockScalingX'),
  8203. lockScalingY = target.get('lockScalingY'),
  8204. lockScalingFlip = target.get('lockScalingFlip');
  8205. if (lockScalingX && lockScalingY) {
  8206. return false;
  8207. }
  8208. // Get the constraint point
  8209. var constraintPosition = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY),
  8210. localMouse = target.toLocalPoint(new fabric.Point(x, y), t.originX, t.originY),
  8211. dim = target._getTransformedDimensions(), scaled = false;
  8212. this._setLocalMouse(localMouse, t);
  8213. // Actually scale the object
  8214. scaled = this._setObjectScale(localMouse, t, lockScalingX, lockScalingY, by, lockScalingFlip, dim);
  8215. // Make sure the constraints apply
  8216. target.setPositionByOrigin(constraintPosition, t.originX, t.originY);
  8217. return scaled;
  8218. },
  8219. /**
  8220. * @private
  8221. * @return {Boolean} true if the scaling occurred
  8222. */
  8223. _setObjectScale: function(localMouse, transform, lockScalingX, lockScalingY, by, lockScalingFlip, _dim) {
  8224. var target = transform.target, forbidScalingX = false, forbidScalingY = false, scaled = false,
  8225. changeX, changeY, scaleX, scaleY;
  8226. scaleX = localMouse.x * target.scaleX / _dim.x;
  8227. scaleY = localMouse.y * target.scaleY / _dim.y;
  8228. changeX = target.scaleX !== scaleX;
  8229. changeY = target.scaleY !== scaleY;
  8230. if (lockScalingFlip && scaleX <= 0 && scaleX < target.scaleX) {
  8231. forbidScalingX = true;
  8232. }
  8233. if (lockScalingFlip && scaleY <= 0 && scaleY < target.scaleY) {
  8234. forbidScalingY = true;
  8235. }
  8236. if (by === 'equally' && !lockScalingX && !lockScalingY) {
  8237. forbidScalingX || forbidScalingY || (scaled = this._scaleObjectEqually(localMouse, target, transform, _dim));
  8238. }
  8239. else if (!by) {
  8240. forbidScalingX || lockScalingX || (target.set('scaleX', scaleX) && (scaled = scaled || changeX));
  8241. forbidScalingY || lockScalingY || (target.set('scaleY', scaleY) && (scaled = scaled || changeY));
  8242. }
  8243. else if (by === 'x' && !target.get('lockUniScaling')) {
  8244. forbidScalingX || lockScalingX || (target.set('scaleX', scaleX) && (scaled = scaled || changeX));
  8245. }
  8246. else if (by === 'y' && !target.get('lockUniScaling')) {
  8247. forbidScalingY || lockScalingY || (target.set('scaleY', scaleY) && (scaled = scaled || changeY));
  8248. }
  8249. transform.newScaleX = scaleX;
  8250. transform.newScaleY = scaleY;
  8251. forbidScalingX || forbidScalingY || this._flipObject(transform, by);
  8252. return scaled;
  8253. },
  8254. /**
  8255. * @private
  8256. * @return {Boolean} true if the scaling occurred
  8257. */
  8258. _scaleObjectEqually: function(localMouse, target, transform, _dim) {
  8259. var dist = localMouse.y + localMouse.x,
  8260. lastDist = _dim.y * transform.original.scaleY / target.scaleY +
  8261. _dim.x * transform.original.scaleX / target.scaleX,
  8262. scaled;
  8263. // We use transform.scaleX/Y instead of target.scaleX/Y
  8264. // because the object may have a min scale and we'll loose the proportions
  8265. transform.newScaleX = transform.original.scaleX * dist / lastDist;
  8266. transform.newScaleY = transform.original.scaleY * dist / lastDist;
  8267. scaled = transform.newScaleX !== target.scaleX || transform.newScaleY !== target.scaleY;
  8268. target.set('scaleX', transform.newScaleX);
  8269. target.set('scaleY', transform.newScaleY);
  8270. return scaled;
  8271. },
  8272. /**
  8273. * @private
  8274. */
  8275. _flipObject: function(transform, by) {
  8276. if (transform.newScaleX < 0 && by !== 'y') {
  8277. if (transform.originX === 'left') {
  8278. transform.originX = 'right';
  8279. }
  8280. else if (transform.originX === 'right') {
  8281. transform.originX = 'left';
  8282. }
  8283. }
  8284. if (transform.newScaleY < 0 && by !== 'x') {
  8285. if (transform.originY === 'top') {
  8286. transform.originY = 'bottom';
  8287. }
  8288. else if (transform.originY === 'bottom') {
  8289. transform.originY = 'top';
  8290. }
  8291. }
  8292. },
  8293. /**
  8294. * @private
  8295. */
  8296. _setLocalMouse: function(localMouse, t) {
  8297. var target = t.target, zoom = this.getZoom(),
  8298. padding = target.padding / zoom;
  8299. if (t.originX === 'right') {
  8300. localMouse.x *= -1;
  8301. }
  8302. else if (t.originX === 'center') {
  8303. localMouse.x *= t.mouseXSign * 2;
  8304. if (localMouse.x < 0) {
  8305. t.mouseXSign = -t.mouseXSign;
  8306. }
  8307. }
  8308. if (t.originY === 'bottom') {
  8309. localMouse.y *= -1;
  8310. }
  8311. else if (t.originY === 'center') {
  8312. localMouse.y *= t.mouseYSign * 2;
  8313. if (localMouse.y < 0) {
  8314. t.mouseYSign = -t.mouseYSign;
  8315. }
  8316. }
  8317. // adjust the mouse coordinates when dealing with padding
  8318. if (abs(localMouse.x) > padding) {
  8319. if (localMouse.x < 0) {
  8320. localMouse.x += padding;
  8321. }
  8322. else {
  8323. localMouse.x -= padding;
  8324. }
  8325. }
  8326. else { // mouse is within the padding, set to 0
  8327. localMouse.x = 0;
  8328. }
  8329. if (abs(localMouse.y) > padding) {
  8330. if (localMouse.y < 0) {
  8331. localMouse.y += padding;
  8332. }
  8333. else {
  8334. localMouse.y -= padding;
  8335. }
  8336. }
  8337. else {
  8338. localMouse.y = 0;
  8339. }
  8340. },
  8341. /**
  8342. * Rotates object by invoking its rotate method
  8343. * @private
  8344. * @param {Number} x pointer's x coordinate
  8345. * @param {Number} y pointer's y coordinate
  8346. * @return {Boolean} true if the rotation occurred
  8347. */
  8348. _rotateObject: function (x, y) {
  8349. var t = this._currentTransform;
  8350. if (t.target.get('lockRotation')) {
  8351. return false;
  8352. }
  8353. var lastAngle = atan2(t.ey - t.top, t.ex - t.left),
  8354. curAngle = atan2(y - t.top, x - t.left),
  8355. angle = radiansToDegrees(curAngle - lastAngle + t.theta),
  8356. hasRoated = true;
  8357. // normalize angle to positive value
  8358. if (angle < 0) {
  8359. angle = 360 + angle;
  8360. }
  8361. angle %= 360;
  8362. if (t.target.snapAngle > 0) {
  8363. var snapAngle = t.target.snapAngle,
  8364. snapThreshold = t.target.snapThreshold || snapAngle,
  8365. rightAngleLocked = Math.ceil(angle / snapAngle) * snapAngle,
  8366. leftAngleLocked = Math.floor(angle / snapAngle) * snapAngle;
  8367. if (Math.abs(angle - leftAngleLocked) < snapThreshold) {
  8368. angle = leftAngleLocked;
  8369. }
  8370. else if (Math.abs(angle - rightAngleLocked) < snapThreshold) {
  8371. angle = rightAngleLocked;
  8372. }
  8373. if (t.target.angle === angle) {
  8374. hasRoated = false;
  8375. }
  8376. }
  8377. t.target.angle = angle;
  8378. return hasRoated;
  8379. },
  8380. /**
  8381. * Set the cursor type of the canvas element
  8382. * @param {String} value Cursor type of the canvas element.
  8383. * @see http://www.w3.org/TR/css3-ui/#cursor
  8384. */
  8385. setCursor: function (value) {
  8386. this.upperCanvasEl.style.cursor = value;
  8387. },
  8388. /**
  8389. * @param {fabric.Object} target to reset transform
  8390. * @private
  8391. */
  8392. _resetObjectTransform: function (target) {
  8393. target.scaleX = 1;
  8394. target.scaleY = 1;
  8395. target.skewX = 0;
  8396. target.skewY = 0;
  8397. target.setAngle(0);
  8398. },
  8399. /**
  8400. * @private
  8401. * @param {CanvasRenderingContext2D} ctx to draw the selection on
  8402. */
  8403. _drawSelection: function (ctx) {
  8404. var groupSelector = this._groupSelector,
  8405. left = groupSelector.left,
  8406. top = groupSelector.top,
  8407. aleft = abs(left),
  8408. atop = abs(top);
  8409. if (this.selectionColor) {
  8410. ctx.fillStyle = this.selectionColor;
  8411. ctx.fillRect(
  8412. groupSelector.ex - ((left > 0) ? 0 : -left),
  8413. groupSelector.ey - ((top > 0) ? 0 : -top),
  8414. aleft,
  8415. atop
  8416. );
  8417. }
  8418. if (!this.selectionLineWidth || !this.selectionBorderColor) {
  8419. return;
  8420. }
  8421. ctx.lineWidth = this.selectionLineWidth;
  8422. ctx.strokeStyle = this.selectionBorderColor;
  8423. // selection border
  8424. if (this.selectionDashArray.length > 1 && !supportLineDash) {
  8425. var px = groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0 : aleft),
  8426. py = groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0 : atop);
  8427. ctx.beginPath();
  8428. fabric.util.drawDashedLine(ctx, px, py, px + aleft, py, this.selectionDashArray);
  8429. fabric.util.drawDashedLine(ctx, px, py + atop - 1, px + aleft, py + atop - 1, this.selectionDashArray);
  8430. fabric.util.drawDashedLine(ctx, px, py, px, py + atop, this.selectionDashArray);
  8431. fabric.util.drawDashedLine(ctx, px + aleft - 1, py, px + aleft - 1, py + atop, this.selectionDashArray);
  8432. ctx.closePath();
  8433. ctx.stroke();
  8434. }
  8435. else {
  8436. fabric.Object.prototype._setLineDash.call(this, ctx, this.selectionDashArray);
  8437. ctx.strokeRect(
  8438. groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0 : aleft),
  8439. groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0 : atop),
  8440. aleft,
  8441. atop
  8442. );
  8443. }
  8444. },
  8445. /**
  8446. * Method that determines what object we are clicking on
  8447. * the skipGroup parameter is for internal use, is needed for shift+click action
  8448. * @param {Event} e mouse event
  8449. * @param {Boolean} skipGroup when true, activeGroup is skipped and only objects are traversed through
  8450. */
  8451. findTarget: function (e, skipGroup) {
  8452. if (this.skipTargetFind) {
  8453. return;
  8454. }
  8455. var ignoreZoom = true,
  8456. pointer = this.getPointer(e, ignoreZoom),
  8457. activeGroup = this.getActiveGroup(),
  8458. activeObject = this.getActiveObject(),
  8459. activeTarget;
  8460. // first check current group (if one exists)
  8461. // active group does not check sub targets like normal groups.
  8462. // if active group just exits.
  8463. if (activeGroup && !skipGroup && activeGroup === this._searchPossibleTargets([activeGroup], pointer)) {
  8464. this._fireOverOutEvents(activeGroup, e);
  8465. return activeGroup;
  8466. }
  8467. // if we hit the corner of an activeObject, let's return that.
  8468. if (activeObject && activeObject._findTargetCorner(pointer)) {
  8469. this._fireOverOutEvents(activeObject, e);
  8470. return activeObject;
  8471. }
  8472. if (activeObject && activeObject === this._searchPossibleTargets([activeObject], pointer)) {
  8473. if (!this.preserveObjectStacking) {
  8474. this._fireOverOutEvents(activeObject, e);
  8475. return activeObject;
  8476. }
  8477. else {
  8478. activeTarget = activeObject;
  8479. }
  8480. }
  8481. this.targets = [];
  8482. var target = this._searchPossibleTargets(this._objects, pointer);
  8483. if (e[this.altSelectionKey] && target && activeTarget && target !== activeTarget) {
  8484. target = activeTarget;
  8485. }
  8486. this._fireOverOutEvents(target, e);
  8487. return target;
  8488. },
  8489. /**
  8490. * @private
  8491. */
  8492. _fireOverOutEvents: function(target, e) {
  8493. if (target) {
  8494. if (this._hoveredTarget !== target) {
  8495. if (this._hoveredTarget) {
  8496. this.fire('mouse:out', { target: this._hoveredTarget, e: e });
  8497. this._hoveredTarget.fire('mouseout');
  8498. }
  8499. this.fire('mouse:over', { target: target, e: e });
  8500. target.fire('mouseover');
  8501. this._hoveredTarget = target;
  8502. }
  8503. }
  8504. else if (this._hoveredTarget) {
  8505. this.fire('mouse:out', { target: this._hoveredTarget, e: e });
  8506. this._hoveredTarget.fire('mouseout');
  8507. this._hoveredTarget = null;
  8508. }
  8509. },
  8510. /**
  8511. * @private
  8512. */
  8513. _checkTarget: function(pointer, obj) {
  8514. if (obj &&
  8515. obj.visible &&
  8516. obj.evented &&
  8517. this.containsPoint(null, obj, pointer)){
  8518. if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) {
  8519. var isTransparent = this.isTargetTransparent(obj, pointer.x, pointer.y);
  8520. if (!isTransparent) {
  8521. return true;
  8522. }
  8523. }
  8524. else {
  8525. return true;
  8526. }
  8527. }
  8528. },
  8529. /**
  8530. * @private
  8531. */
  8532. _searchPossibleTargets: function(objects, pointer) {
  8533. // Cache all targets where their bounding box contains point.
  8534. var target, i = objects.length, normalizedPointer, subTarget;
  8535. // Do not check for currently grouped objects, since we check the parent group itself.
  8536. // untill we call this function specifically to search inside the activeGroup
  8537. while (i--) {
  8538. if (this._checkTarget(pointer, objects[i])) {
  8539. target = objects[i];
  8540. if (target.type === 'group' && target.subTargetCheck) {
  8541. normalizedPointer = this._normalizePointer(target, pointer);
  8542. subTarget = this._searchPossibleTargets(target._objects, normalizedPointer);
  8543. subTarget && this.targets.push(subTarget);
  8544. }
  8545. break;
  8546. }
  8547. }
  8548. return target;
  8549. },
  8550. /**
  8551. * Returns pointer coordinates without the effect of the viewport
  8552. * @param {Object} pointer with "x" and "y" number values
  8553. * @return {Object} object with "x" and "y" number values
  8554. */
  8555. restorePointerVpt: function(pointer) {
  8556. return fabric.util.transformPoint(
  8557. pointer,
  8558. fabric.util.invertTransform(this.viewportTransform)
  8559. );
  8560. },
  8561. /**
  8562. * Returns pointer coordinates relative to canvas.
  8563. * Can return coordinates with or without viewportTransform.
  8564. * ignoreZoom false gives back coordinates that represent
  8565. * the point clicked on canvas element.
  8566. * ignoreZoom true gives back coordinates after being processed
  8567. * by the viewportTransform ( sort of coordinates of what is displayed
  8568. * on the canvas where you are clicking.
  8569. * To interact with your shapes top and left you want to use ignoreZoom true
  8570. * most of the time, while ignoreZoom false will give you coordinates
  8571. * compatible with the object.oCoords system.
  8572. * of the time.
  8573. * @param {Event} e
  8574. * @param {Boolean} ignoreZoom
  8575. * @return {Object} object with "x" and "y" number values
  8576. */
  8577. getPointer: function (e, ignoreZoom, upperCanvasEl) {
  8578. if (!upperCanvasEl) {
  8579. upperCanvasEl = this.upperCanvasEl;
  8580. }
  8581. var pointer = getPointer(e),
  8582. bounds = upperCanvasEl.getBoundingClientRect(),
  8583. boundsWidth = bounds.width || 0,
  8584. boundsHeight = bounds.height || 0,
  8585. cssScale;
  8586. if (!boundsWidth || !boundsHeight ) {
  8587. if ('top' in bounds && 'bottom' in bounds) {
  8588. boundsHeight = Math.abs( bounds.top - bounds.bottom );
  8589. }
  8590. if ('right' in bounds && 'left' in bounds) {
  8591. boundsWidth = Math.abs( bounds.right - bounds.left );
  8592. }
  8593. }
  8594. this.calcOffset();
  8595. pointer.x = pointer.x - this._offset.left;
  8596. pointer.y = pointer.y - this._offset.top;
  8597. if (!ignoreZoom) {
  8598. pointer = this.restorePointerVpt(pointer);
  8599. }
  8600. if (boundsWidth === 0 || boundsHeight === 0) {
  8601. // If bounds are not available (i.e. not visible), do not apply scale.
  8602. cssScale = { width: 1, height: 1 };
  8603. }
  8604. else {
  8605. cssScale = {
  8606. width: upperCanvasEl.width / boundsWidth,
  8607. height: upperCanvasEl.height / boundsHeight
  8608. };
  8609. }
  8610. return {
  8611. x: pointer.x * cssScale.width,
  8612. y: pointer.y * cssScale.height
  8613. };
  8614. },
  8615. /**
  8616. * @private
  8617. * @throws {CANVAS_INIT_ERROR} If canvas can not be initialized
  8618. */
  8619. _createUpperCanvas: function () {
  8620. var lowerCanvasClass = this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/, '');
  8621. this.upperCanvasEl = this._createCanvasElement();
  8622. fabric.util.addClass(this.upperCanvasEl, 'upper-canvas ' + lowerCanvasClass);
  8623. this.wrapperEl.appendChild(this.upperCanvasEl);
  8624. this._copyCanvasStyle(this.lowerCanvasEl, this.upperCanvasEl);
  8625. this._applyCanvasStyle(this.upperCanvasEl);
  8626. this.contextTop = this.upperCanvasEl.getContext('2d');
  8627. },
  8628. /**
  8629. * @private
  8630. */
  8631. _createCacheCanvas: function () {
  8632. this.cacheCanvasEl = this._createCanvasElement();
  8633. this.cacheCanvasEl.setAttribute('width', this.width);
  8634. this.cacheCanvasEl.setAttribute('height', this.height);
  8635. this.contextCache = this.cacheCanvasEl.getContext('2d');
  8636. },
  8637. /**
  8638. * @private
  8639. */
  8640. _initWrapperElement: function () {
  8641. this.wrapperEl = fabric.util.wrapElement(this.lowerCanvasEl, 'div', {
  8642. 'class': this.containerClass
  8643. });
  8644. fabric.util.setStyle(this.wrapperEl, {
  8645. width: this.getWidth() + 'px',
  8646. height: this.getHeight() + 'px',
  8647. position: 'relative'
  8648. });
  8649. fabric.util.makeElementUnselectable(this.wrapperEl);
  8650. },
  8651. /**
  8652. * @private
  8653. * @param {HTMLElement} element canvas element to apply styles on
  8654. */
  8655. _applyCanvasStyle: function (element) {
  8656. var width = this.getWidth() || element.width,
  8657. height = this.getHeight() || element.height;
  8658. fabric.util.setStyle(element, {
  8659. position: 'absolute',
  8660. width: width + 'px',
  8661. height: height + 'px',
  8662. left: 0,
  8663. top: 0,
  8664. 'touch-action': 'none'
  8665. });
  8666. element.width = width;
  8667. element.height = height;
  8668. fabric.util.makeElementUnselectable(element);
  8669. },
  8670. /**
  8671. * Copys the the entire inline style from one element (fromEl) to another (toEl)
  8672. * @private
  8673. * @param {Element} fromEl Element style is copied from
  8674. * @param {Element} toEl Element copied style is applied to
  8675. */
  8676. _copyCanvasStyle: function (fromEl, toEl) {
  8677. toEl.style.cssText = fromEl.style.cssText;
  8678. },
  8679. /**
  8680. * Returns context of canvas where object selection is drawn
  8681. * @return {CanvasRenderingContext2D}
  8682. */
  8683. getSelectionContext: function() {
  8684. return this.contextTop;
  8685. },
  8686. /**
  8687. * Returns &lt;canvas> element on which object selection is drawn
  8688. * @return {HTMLCanvasElement}
  8689. */
  8690. getSelectionElement: function () {
  8691. return this.upperCanvasEl;
  8692. },
  8693. /**
  8694. * @private
  8695. * @param {Object} object
  8696. */
  8697. _setActiveObject: function(object) {
  8698. var obj = this._activeObject;
  8699. if (obj) {
  8700. obj.set('active', false);
  8701. if (object !== obj && obj.onDeselect && typeof obj.onDeselect === 'function') {
  8702. obj.onDeselect();
  8703. }
  8704. }
  8705. this._activeObject = object;
  8706. object.set('active', true);
  8707. },
  8708. /**
  8709. * Sets given object as the only active object on canvas
  8710. * @param {fabric.Object} object Object to set as an active one
  8711. * @param {Event} [e] Event (passed along when firing "object:selected")
  8712. * @return {fabric.Canvas} thisArg
  8713. * @chainable
  8714. */
  8715. setActiveObject: function (object, e) {
  8716. var currentActiveObject = this.getActiveObject();
  8717. if (currentActiveObject && currentActiveObject !== object) {
  8718. currentActiveObject.fire('deselected', { e: e });
  8719. }
  8720. this._setActiveObject(object);
  8721. this.renderAll();
  8722. this.fire('object:selected', { target: object, e: e });
  8723. object.fire('selected', { e: e });
  8724. return this;
  8725. },
  8726. /**
  8727. * Returns currently active object
  8728. * @return {fabric.Object} active object
  8729. */
  8730. getActiveObject: function () {
  8731. return this._activeObject;
  8732. },
  8733. /**
  8734. * @private
  8735. * @param {fabric.Object} obj Object that was removed
  8736. */
  8737. _onObjectRemoved: function(obj) {
  8738. // removing active object should fire "selection:cleared" events
  8739. if (this.getActiveObject() === obj) {
  8740. this.fire('before:selection:cleared', { target: obj });
  8741. this._discardActiveObject();
  8742. this.fire('selection:cleared', { target: obj });
  8743. obj.fire('deselected');
  8744. }
  8745. if (this._hoveredTarget === obj) {
  8746. this._hoveredTarget = null;
  8747. }
  8748. this.callSuper('_onObjectRemoved', obj);
  8749. },
  8750. /**
  8751. * @private
  8752. */
  8753. _discardActiveObject: function() {
  8754. var obj = this._activeObject;
  8755. if (obj) {
  8756. obj.set('active', false);
  8757. if (obj.onDeselect && typeof obj.onDeselect === 'function') {
  8758. obj.onDeselect();
  8759. }
  8760. }
  8761. this._activeObject = null;
  8762. },
  8763. /**
  8764. * Discards currently active object and fire events. If the function is called by fabric
  8765. * as a consequence of a mouse event, the event is passed as a parmater and
  8766. * sent to the fire function for the custom events. When used as a method the
  8767. * e param does not have any application.
  8768. * @param {event} e
  8769. * @return {fabric.Canvas} thisArg
  8770. * @chainable
  8771. */
  8772. discardActiveObject: function (e) {
  8773. var activeObject = this._activeObject;
  8774. if (activeObject) {
  8775. this.fire('before:selection:cleared', { target: activeObject, e: e });
  8776. this._discardActiveObject();
  8777. this.fire('selection:cleared', { e: e });
  8778. activeObject.fire('deselected', { e: e });
  8779. }
  8780. return this;
  8781. },
  8782. /**
  8783. * @private
  8784. * @param {fabric.Group} group
  8785. */
  8786. _setActiveGroup: function(group) {
  8787. this._activeGroup = group;
  8788. if (group) {
  8789. group.set('active', true);
  8790. }
  8791. },
  8792. /**
  8793. * Sets active group to a specified one. If the function is called by fabric
  8794. * as a consequence of a mouse event, the event is passed as a parmater and
  8795. * sent to the fire function for the custom events. When used as a method the
  8796. * e param does not have any application.
  8797. * @param {fabric.Group} group Group to set as a current one
  8798. * @param {Event} e Event object
  8799. * @return {fabric.Canvas} thisArg
  8800. * @chainable
  8801. */
  8802. setActiveGroup: function (group, e) {
  8803. this._setActiveGroup(group);
  8804. if (group) {
  8805. this.fire('object:selected', { target: group, e: e });
  8806. group.fire('selected', { e: e });
  8807. }
  8808. return this;
  8809. },
  8810. /**
  8811. * Returns currently active group
  8812. * @return {fabric.Group} Current group
  8813. */
  8814. getActiveGroup: function () {
  8815. return this._activeGroup;
  8816. },
  8817. /**
  8818. * @private
  8819. */
  8820. _discardActiveGroup: function() {
  8821. var g = this.getActiveGroup();
  8822. if (g) {
  8823. g.destroy();
  8824. }
  8825. this.setActiveGroup(null);
  8826. },
  8827. /**
  8828. * Discards currently active group and fire events If the function is called by fabric
  8829. * as a consequence of a mouse event, the event is passed as a parmater and
  8830. * sent to the fire function for the custom events. When used as a method the
  8831. * e param does not have any application.
  8832. * @return {fabric.Canvas} thisArg
  8833. * @chainable
  8834. */
  8835. discardActiveGroup: function (e) {
  8836. var g = this.getActiveGroup();
  8837. if (g) {
  8838. this.fire('before:selection:cleared', { e: e, target: g });
  8839. this._discardActiveGroup();
  8840. this.fire('selection:cleared', { e: e });
  8841. }
  8842. return this;
  8843. },
  8844. /**
  8845. * Deactivates all objects on canvas, removing any active group or object
  8846. * @return {fabric.Canvas} thisArg
  8847. * @chainable
  8848. */
  8849. deactivateAll: function () {
  8850. var allObjects = this.getObjects(),
  8851. i = 0,
  8852. len = allObjects.length,
  8853. obj;
  8854. for ( ; i < len; i++) {
  8855. obj = allObjects[i];
  8856. obj && obj.set('active', false);
  8857. }
  8858. this._discardActiveGroup();
  8859. this._discardActiveObject();
  8860. return this;
  8861. },
  8862. /**
  8863. * Deactivates all objects and dispatches appropriate events If the function is called by fabric
  8864. * as a consequence of a mouse event, the event is passed as a parmater and
  8865. * sent to the fire function for the custom events. When used as a method the
  8866. * e param does not have any application.
  8867. * @return {fabric.Canvas} thisArg
  8868. * @chainable
  8869. */
  8870. deactivateAllWithDispatch: function (e) {
  8871. this.discardActiveGroup(e);
  8872. this.discardActiveObject(e);
  8873. this.deactivateAll();
  8874. return this;
  8875. },
  8876. /**
  8877. * Clears a canvas element and removes all event listeners
  8878. * @return {fabric.Canvas} thisArg
  8879. * @chainable
  8880. */
  8881. dispose: function () {
  8882. this.callSuper('dispose');
  8883. var wrapper = this.wrapperEl;
  8884. this.removeListeners();
  8885. wrapper.removeChild(this.upperCanvasEl);
  8886. wrapper.removeChild(this.lowerCanvasEl);
  8887. delete this.upperCanvasEl;
  8888. if (wrapper.parentNode) {
  8889. wrapper.parentNode.replaceChild(this.lowerCanvasEl, this.wrapperEl);
  8890. }
  8891. delete this.wrapperEl;
  8892. return this;
  8893. },
  8894. /**
  8895. * Clears all contexts (background, main, top) of an instance
  8896. * @return {fabric.Canvas} thisArg
  8897. * @chainable
  8898. */
  8899. clear: function () {
  8900. this.discardActiveGroup();
  8901. this.discardActiveObject();
  8902. this.clearContext(this.contextTop);
  8903. return this.callSuper('clear');
  8904. },
  8905. /**
  8906. * Draws objects' controls (borders/controls)
  8907. * @param {CanvasRenderingContext2D} ctx Context to render controls on
  8908. */
  8909. drawControls: function(ctx) {
  8910. var activeGroup = this.getActiveGroup();
  8911. if (activeGroup) {
  8912. activeGroup._renderControls(ctx);
  8913. }
  8914. else {
  8915. this._drawObjectsControls(ctx);
  8916. }
  8917. },
  8918. /**
  8919. * @private
  8920. */
  8921. _drawObjectsControls: function(ctx) {
  8922. for (var i = 0, len = this._objects.length; i < len; ++i) {
  8923. if (!this._objects[i] || !this._objects[i].active) {
  8924. continue;
  8925. }
  8926. this._objects[i]._renderControls(ctx);
  8927. }
  8928. },
  8929. /**
  8930. * @private
  8931. */
  8932. _toObject: function(instance, methodName, propertiesToInclude) {
  8933. //If the object is part of the current selection group, it should
  8934. //be transformed appropriately
  8935. //i.e. it should be serialised as it would appear if the selection group
  8936. //were to be destroyed.
  8937. var originalProperties = this._realizeGroupTransformOnObject(instance),
  8938. object = this.callSuper('_toObject', instance, methodName, propertiesToInclude);
  8939. //Undo the damage we did by changing all of its properties
  8940. this._unwindGroupTransformOnObject(instance, originalProperties);
  8941. return object;
  8942. },
  8943. /**
  8944. * Realises an object's group transformation on it
  8945. * @private
  8946. * @param {fabric.Object} [instance] the object to transform (gets mutated)
  8947. * @returns the original values of instance which were changed
  8948. */
  8949. _realizeGroupTransformOnObject: function(instance) {
  8950. var layoutProps = ['angle', 'flipX', 'flipY', 'height', 'left', 'scaleX', 'scaleY', 'top', 'width'];
  8951. if (instance.group && instance.group === this.getActiveGroup()) {
  8952. //Copy all the positionally relevant properties across now
  8953. var originalValues = {};
  8954. layoutProps.forEach(function(prop) {
  8955. originalValues[prop] = instance[prop];
  8956. });
  8957. this.getActiveGroup().realizeTransform(instance);
  8958. return originalValues;
  8959. }
  8960. else {
  8961. return null;
  8962. }
  8963. },
  8964. /**
  8965. * Restores the changed properties of instance
  8966. * @private
  8967. * @param {fabric.Object} [instance] the object to un-transform (gets mutated)
  8968. * @param {Object} [originalValues] the original values of instance, as returned by _realizeGroupTransformOnObject
  8969. */
  8970. _unwindGroupTransformOnObject: function(instance, originalValues) {
  8971. if (originalValues) {
  8972. instance.set(originalValues);
  8973. }
  8974. },
  8975. /**
  8976. * @private
  8977. */
  8978. _setSVGObject: function(markup, instance, reviver) {
  8979. var originalProperties;
  8980. //If the object is in a selection group, simulate what would happen to that
  8981. //object when the group is deselected
  8982. originalProperties = this._realizeGroupTransformOnObject(instance);
  8983. this.callSuper('_setSVGObject', markup, instance, reviver);
  8984. this._unwindGroupTransformOnObject(instance, originalProperties);
  8985. },
  8986. });
  8987. // copying static properties manually to work around Opera's bug,
  8988. // where "prototype" property is enumerable and overrides existing prototype
  8989. for (var prop in fabric.StaticCanvas) {
  8990. if (prop !== 'prototype') {
  8991. fabric.Canvas[prop] = fabric.StaticCanvas[prop];
  8992. }
  8993. }
  8994. if (fabric.isTouchSupported) {
  8995. /** @ignore */
  8996. fabric.Canvas.prototype._setCursorFromEvent = function() { };
  8997. }
  8998. /**
  8999. * @ignore
  9000. * @class fabric.Element
  9001. * @alias fabric.Canvas
  9002. * @deprecated Use {@link fabric.Canvas} instead.
  9003. * @constructor
  9004. */
  9005. fabric.Element = fabric.Canvas;
  9006. })();
  9007. (function() {
  9008. var cursorOffset = {
  9009. mt: 0, // n
  9010. tr: 1, // ne
  9011. mr: 2, // e
  9012. br: 3, // se
  9013. mb: 4, // s
  9014. bl: 5, // sw
  9015. ml: 6, // w
  9016. tl: 7 // nw
  9017. },
  9018. addListener = fabric.util.addListener,
  9019. removeListener = fabric.util.removeListener;
  9020. fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ {
  9021. /**
  9022. * Map of cursor style values for each of the object controls
  9023. * @private
  9024. */
  9025. cursorMap: [
  9026. 'n-resize',
  9027. 'ne-resize',
  9028. 'e-resize',
  9029. 'se-resize',
  9030. 's-resize',
  9031. 'sw-resize',
  9032. 'w-resize',
  9033. 'nw-resize'
  9034. ],
  9035. /**
  9036. * Adds mouse listeners to canvas
  9037. * @private
  9038. */
  9039. _initEventListeners: function () {
  9040. this._bindEvents();
  9041. addListener(fabric.window, 'resize', this._onResize);
  9042. // mouse events
  9043. addListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);
  9044. addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
  9045. addListener(this.upperCanvasEl, 'mouseout', this._onMouseOut);
  9046. addListener(this.upperCanvasEl, 'mouseenter', this._onMouseEnter);
  9047. addListener(this.upperCanvasEl, 'wheel', this._onMouseWheel);
  9048. addListener(this.upperCanvasEl, 'contextmenu', this._onContextMenu);
  9049. // touch events
  9050. addListener(this.upperCanvasEl, 'touchstart', this._onMouseDown, { passive: false });
  9051. addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove, { passive: false });
  9052. if (typeof eventjs !== 'undefined' && 'add' in eventjs) {
  9053. eventjs.add(this.upperCanvasEl, 'gesture', this._onGesture);
  9054. eventjs.add(this.upperCanvasEl, 'drag', this._onDrag);
  9055. eventjs.add(this.upperCanvasEl, 'orientation', this._onOrientationChange);
  9056. eventjs.add(this.upperCanvasEl, 'shake', this._onShake);
  9057. eventjs.add(this.upperCanvasEl, 'longpress', this._onLongPress);
  9058. }
  9059. },
  9060. /**
  9061. * @private
  9062. */
  9063. _bindEvents: function() {
  9064. this._onMouseDown = this._onMouseDown.bind(this);
  9065. this._onMouseMove = this._onMouseMove.bind(this);
  9066. this._onMouseUp = this._onMouseUp.bind(this);
  9067. this._onResize = this._onResize.bind(this);
  9068. this._onGesture = this._onGesture.bind(this);
  9069. this._onDrag = this._onDrag.bind(this);
  9070. this._onShake = this._onShake.bind(this);
  9071. this._onLongPress = this._onLongPress.bind(this);
  9072. this._onOrientationChange = this._onOrientationChange.bind(this);
  9073. this._onMouseWheel = this._onMouseWheel.bind(this);
  9074. this._onMouseOut = this._onMouseOut.bind(this);
  9075. this._onMouseEnter = this._onMouseEnter.bind(this);
  9076. this._onContextMenu = this._onContextMenu.bind(this);
  9077. },
  9078. /**
  9079. * Removes all event listeners
  9080. */
  9081. removeListeners: function() {
  9082. removeListener(fabric.window, 'resize', this._onResize);
  9083. removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);
  9084. removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
  9085. removeListener(this.upperCanvasEl, 'mouseout', this._onMouseOut);
  9086. removeListener(this.upperCanvasEl, 'mouseenter', this._onMouseEnter);
  9087. removeListener(this.upperCanvasEl, 'wheel', this._onMouseWheel);
  9088. removeListener(this.upperCanvasEl, 'contextmenu', this._onContextMenu);
  9089. removeListener(this.upperCanvasEl, 'touchstart', this._onMouseDown);
  9090. removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);
  9091. if (typeof eventjs !== 'undefined' && 'remove' in eventjs) {
  9092. eventjs.remove(this.upperCanvasEl, 'gesture', this._onGesture);
  9093. eventjs.remove(this.upperCanvasEl, 'drag', this._onDrag);
  9094. eventjs.remove(this.upperCanvasEl, 'orientation', this._onOrientationChange);
  9095. eventjs.remove(this.upperCanvasEl, 'shake', this._onShake);
  9096. eventjs.remove(this.upperCanvasEl, 'longpress', this._onLongPress);
  9097. }
  9098. },
  9099. /**
  9100. * @private
  9101. * @param {Event} [e] Event object fired on Event.js gesture
  9102. * @param {Event} [self] Inner Event object
  9103. */
  9104. _onGesture: function(e, self) {
  9105. this.__onTransformGesture && this.__onTransformGesture(e, self);
  9106. },
  9107. /**
  9108. * @private
  9109. * @param {Event} [e] Event object fired on Event.js drag
  9110. * @param {Event} [self] Inner Event object
  9111. */
  9112. _onDrag: function(e, self) {
  9113. this.__onDrag && this.__onDrag(e, self);
  9114. },
  9115. /**
  9116. * @private
  9117. * @param {Event} [e] Event object fired on wheel event
  9118. */
  9119. _onMouseWheel: function(e) {
  9120. this.__onMouseWheel(e);
  9121. },
  9122. /**
  9123. * @private
  9124. * @param {Event} e Event object fired on mousedown
  9125. */
  9126. _onMouseOut: function(e) {
  9127. var target = this._hoveredTarget;
  9128. this.fire('mouse:out', { target: target, e: e });
  9129. this._hoveredTarget = null;
  9130. target && target.fire('mouseout', { e: e });
  9131. if (this._iTextInstances) {
  9132. this._iTextInstances.forEach(function(obj) {
  9133. if (obj.isEditing) {
  9134. obj.hiddenTextarea.focus();
  9135. }
  9136. });
  9137. }
  9138. },
  9139. /**
  9140. * @private
  9141. * @param {Event} e Event object fired on mouseenter
  9142. */
  9143. _onMouseEnter: function(e) {
  9144. if (!this.findTarget(e)) {
  9145. this.fire('mouse:over', { target: null, e: e });
  9146. this._hoveredTarget = null;
  9147. }
  9148. },
  9149. /**
  9150. * @private
  9151. * @param {Event} [e] Event object fired on Event.js orientation change
  9152. * @param {Event} [self] Inner Event object
  9153. */
  9154. _onOrientationChange: function(e, self) {
  9155. this.__onOrientationChange && this.__onOrientationChange(e, self);
  9156. },
  9157. /**
  9158. * @private
  9159. * @param {Event} [e] Event object fired on Event.js shake
  9160. * @param {Event} [self] Inner Event object
  9161. */
  9162. _onShake: function(e, self) {
  9163. this.__onShake && this.__onShake(e, self);
  9164. },
  9165. /**
  9166. * @private
  9167. * @param {Event} [e] Event object fired on Event.js shake
  9168. * @param {Event} [self] Inner Event object
  9169. */
  9170. _onLongPress: function(e, self) {
  9171. this.__onLongPress && this.__onLongPress(e, self);
  9172. },
  9173. /**
  9174. * @private
  9175. * @param {Event} e Event object fired on mousedown
  9176. */
  9177. _onContextMenu: function (e) {
  9178. if (this.stopContextMenu) {
  9179. e.stopPropagation();
  9180. e.preventDefault();
  9181. }
  9182. return false;
  9183. },
  9184. /**
  9185. * @private
  9186. * @param {Event} e Event object fired on mousedown
  9187. */
  9188. _onMouseDown: function (e) {
  9189. this.__onMouseDown(e);
  9190. addListener(fabric.document, 'touchend', this._onMouseUp, { passive: false });
  9191. addListener(fabric.document, 'touchmove', this._onMouseMove, { passive: false });
  9192. removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
  9193. removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);
  9194. if (e.type === 'touchstart') {
  9195. // Unbind mousedown to prevent double triggers from touch devices
  9196. removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);
  9197. }
  9198. else {
  9199. addListener(fabric.document, 'mouseup', this._onMouseUp);
  9200. addListener(fabric.document, 'mousemove', this._onMouseMove);
  9201. }
  9202. },
  9203. /**
  9204. * @private
  9205. * @param {Event} e Event object fired on mouseup
  9206. */
  9207. _onMouseUp: function (e) {
  9208. this.__onMouseUp(e);
  9209. removeListener(fabric.document, 'mouseup', this._onMouseUp);
  9210. removeListener(fabric.document, 'touchend', this._onMouseUp);
  9211. removeListener(fabric.document, 'mousemove', this._onMouseMove);
  9212. removeListener(fabric.document, 'touchmove', this._onMouseMove);
  9213. addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
  9214. addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove, { passive: false });
  9215. if (e.type === 'touchend') {
  9216. // Wait 400ms before rebinding mousedown to prevent double triggers
  9217. // from touch devices
  9218. var _this = this;
  9219. setTimeout(function() {
  9220. addListener(_this.upperCanvasEl, 'mousedown', _this._onMouseDown);
  9221. }, 400);
  9222. }
  9223. },
  9224. /**
  9225. * @private
  9226. * @param {Event} e Event object fired on mousemove
  9227. */
  9228. _onMouseMove: function (e) {
  9229. !this.allowTouchScrolling && e.preventDefault && e.preventDefault();
  9230. this.__onMouseMove(e);
  9231. },
  9232. /**
  9233. * @private
  9234. */
  9235. _onResize: function () {
  9236. this.calcOffset();
  9237. },
  9238. /**
  9239. * Decides whether the canvas should be redrawn in mouseup and mousedown events.
  9240. * @private
  9241. * @param {Object} target
  9242. * @param {Object} pointer
  9243. */
  9244. _shouldRender: function(target, pointer) {
  9245. var activeObject = this.getActiveGroup() || this.getActiveObject();
  9246. if (activeObject && activeObject.isEditing && target === activeObject) {
  9247. // if we mouse up/down over a editing textbox a cursor change,
  9248. // there is no need to re render
  9249. return false;
  9250. }
  9251. return !!(
  9252. (target && (
  9253. target.isMoving ||
  9254. target !== activeObject))
  9255. ||
  9256. (!target && !!activeObject)
  9257. ||
  9258. (!target && !activeObject && !this._groupSelector)
  9259. ||
  9260. (pointer &&
  9261. this._previousPointer &&
  9262. this.selection && (
  9263. pointer.x !== this._previousPointer.x ||
  9264. pointer.y !== this._previousPointer.y))
  9265. );
  9266. },
  9267. /**
  9268. * Method that defines the actions when mouse is released on canvas.
  9269. * The method resets the currentTransform parameters, store the image corner
  9270. * position in the image object and render the canvas on top.
  9271. * @private
  9272. * @param {Event} e Event object fired on mouseup
  9273. */
  9274. __onMouseUp: function (e) {
  9275. var target, searchTarget = true, transform = this._currentTransform,
  9276. groupSelector = this._groupSelector,
  9277. isClick = (!groupSelector || (groupSelector.left === 0 && groupSelector.top === 0));
  9278. if (this.isDrawingMode && this._isCurrentlyDrawing) {
  9279. this._onMouseUpInDrawingMode(e);
  9280. return;
  9281. }
  9282. if (transform) {
  9283. this._finalizeCurrentTransform();
  9284. searchTarget = !transform.actionPerformed;
  9285. }
  9286. target = searchTarget ? this.findTarget(e, true) : transform.target;
  9287. var shouldRender = this._shouldRender(target, this.getPointer(e));
  9288. if (target || !isClick) {
  9289. this._maybeGroupObjects(e);
  9290. }
  9291. else {
  9292. // those are done by default on mouse up
  9293. // by _maybeGroupObjects, we are skipping it in case of no target find
  9294. this._groupSelector = null;
  9295. this._currentTransform = null;
  9296. }
  9297. if (target) {
  9298. target.isMoving = false;
  9299. }
  9300. this._handleCursorAndEvent(e, target, 'up');
  9301. target && (target.__corner = 0);
  9302. shouldRender && this.renderAll();
  9303. },
  9304. /**
  9305. * set cursor for mouse up and handle mouseUp event
  9306. * @param {Event} e event from mouse
  9307. * @param {fabric.Object} target receiving event
  9308. * @param {String} eventType event to fire (up, down or move)
  9309. */
  9310. _handleCursorAndEvent: function(e, target, eventType) {
  9311. this._setCursorFromEvent(e, target);
  9312. this._handleEvent(e, eventType, target ? target : null);
  9313. },
  9314. /**
  9315. * Handle event firing for target and subtargets
  9316. * @param {Event} e event from mouse
  9317. * @param {String} eventType event to fire (up, down or move)
  9318. * @param {fabric.Object} targetObj receiving event
  9319. */
  9320. _handleEvent: function(e, eventType, targetObj) {
  9321. var target = typeof targetObj === 'undefined' ? this.findTarget(e) : targetObj,
  9322. targets = this.targets || [],
  9323. options = { e: e, target: target, subTargets: targets };
  9324. this.fire('mouse:' + eventType, options);
  9325. target && target.fire('mouse' + eventType, options);
  9326. for (var i = 0; i < targets.length; i++) {
  9327. targets[i].fire('mouse' + eventType, options);
  9328. }
  9329. },
  9330. /**
  9331. * @private
  9332. */
  9333. _finalizeCurrentTransform: function() {
  9334. var transform = this._currentTransform,
  9335. target = transform.target;
  9336. if (target._scaling) {
  9337. target._scaling = false;
  9338. }
  9339. target.setCoords();
  9340. this._restoreOriginXY(target);
  9341. if (transform.actionPerformed || (this.stateful && target.hasStateChanged())) {
  9342. this.fire('object:modified', { target: target });
  9343. target.fire('modified');
  9344. }
  9345. },
  9346. /**
  9347. * @private
  9348. * @param {Object} target Object to restore
  9349. */
  9350. _restoreOriginXY: function(target) {
  9351. if (this._previousOriginX && this._previousOriginY) {
  9352. var originPoint = target.translateToOriginPoint(
  9353. target.getCenterPoint(),
  9354. this._previousOriginX,
  9355. this._previousOriginY);
  9356. target.originX = this._previousOriginX;
  9357. target.originY = this._previousOriginY;
  9358. target.left = originPoint.x;
  9359. target.top = originPoint.y;
  9360. this._previousOriginX = null;
  9361. this._previousOriginY = null;
  9362. }
  9363. },
  9364. /**
  9365. * @private
  9366. * @param {Event} e Event object fired on mousedown
  9367. */
  9368. _onMouseDownInDrawingMode: function(e) {
  9369. this._isCurrentlyDrawing = true;
  9370. this.discardActiveObject(e).renderAll();
  9371. if (this.clipTo) {
  9372. fabric.util.clipContext(this, this.contextTop);
  9373. }
  9374. var pointer = this.getPointer(e);
  9375. this.freeDrawingBrush.onMouseDown(pointer);
  9376. this._handleEvent(e, 'down');
  9377. },
  9378. /**
  9379. * @private
  9380. * @param {Event} e Event object fired on mousemove
  9381. */
  9382. _onMouseMoveInDrawingMode: function(e) {
  9383. if (this._isCurrentlyDrawing) {
  9384. var pointer = this.getPointer(e);
  9385. this.freeDrawingBrush.onMouseMove(pointer);
  9386. }
  9387. this.setCursor(this.freeDrawingCursor);
  9388. this._handleEvent(e, 'move');
  9389. },
  9390. /**
  9391. * @private
  9392. * @param {Event} e Event object fired on mouseup
  9393. */
  9394. _onMouseUpInDrawingMode: function(e) {
  9395. this._isCurrentlyDrawing = false;
  9396. if (this.clipTo) {
  9397. this.contextTop.restore();
  9398. }
  9399. this.freeDrawingBrush.onMouseUp();
  9400. this._handleEvent(e, 'up');
  9401. },
  9402. /**
  9403. * Method that defines the actions when mouse is clicked on canvas.
  9404. * The method inits the currentTransform parameters and renders all the
  9405. * canvas so the current image can be placed on the top canvas and the rest
  9406. * in on the container one.
  9407. * @private
  9408. * @param {Event} e Event object fired on mousedown
  9409. */
  9410. __onMouseDown: function (e) {
  9411. var target = this.findTarget(e);
  9412. // if right click just fire events
  9413. var isRightClick = 'which' in e ? e.which === 3 : e.button === 2;
  9414. if (isRightClick) {
  9415. if (this.fireRightClick) {
  9416. this._handleEvent(e, 'down', target ? target : null);
  9417. }
  9418. return;
  9419. }
  9420. var isMiddleClick = 'which' in e ? e.which === 2 : e.button === 1;
  9421. if (isMiddleClick) {
  9422. if (this.fireMiddleClick) {
  9423. this._handleEvent(e, 'down', target ? target : null);
  9424. }
  9425. return;
  9426. }
  9427. if (this.isDrawingMode) {
  9428. this._onMouseDownInDrawingMode(e);
  9429. return;
  9430. }
  9431. // ignore if some object is being transformed at this moment
  9432. if (this._currentTransform) {
  9433. return;
  9434. }
  9435. // save pointer for check in __onMouseUp event
  9436. var pointer = this.getPointer(e, true);
  9437. this._previousPointer = pointer;
  9438. var shouldRender = this._shouldRender(target, pointer),
  9439. shouldGroup = this._shouldGroup(e, target);
  9440. if (this._shouldClearSelection(e, target)) {
  9441. this._clearSelection(e, target, pointer);
  9442. }
  9443. else if (shouldGroup) {
  9444. this._handleGrouping(e, target);
  9445. target = this.getActiveGroup();
  9446. }
  9447. if (target) {
  9448. if (target.selectable && (target.__corner || !shouldGroup)) {
  9449. this._beforeTransform(e, target);
  9450. this._setupCurrentTransform(e, target);
  9451. }
  9452. var activeObject = this.getActiveObject();
  9453. if (target !== this.getActiveGroup() && target !== activeObject) {
  9454. this.deactivateAll();
  9455. if (target.selectable) {
  9456. activeObject && activeObject.fire('deselected', { e: e });
  9457. this.setActiveObject(target, e);
  9458. }
  9459. }
  9460. }
  9461. this._handleEvent(e, 'down', target ? target : null);
  9462. // we must renderAll so that we update the visuals
  9463. shouldRender && this.renderAll();
  9464. },
  9465. /**
  9466. * @private
  9467. */
  9468. _beforeTransform: function(e, target) {
  9469. this.stateful && target.saveState();
  9470. // determine if it's a drag or rotate case
  9471. if (target._findTargetCorner(this.getPointer(e))) {
  9472. this.onBeforeScaleRotate(target);
  9473. }
  9474. },
  9475. /**
  9476. * @private
  9477. */
  9478. _clearSelection: function(e, target, pointer) {
  9479. this.deactivateAllWithDispatch(e);
  9480. if (target && target.selectable) {
  9481. this.setActiveObject(target, e);
  9482. }
  9483. else if (this.selection) {
  9484. this._groupSelector = {
  9485. ex: pointer.x,
  9486. ey: pointer.y,
  9487. top: 0,
  9488. left: 0
  9489. };
  9490. }
  9491. },
  9492. /**
  9493. * @private
  9494. * @param {Object} target Object for that origin is set to center
  9495. */
  9496. _setOriginToCenter: function(target) {
  9497. this._previousOriginX = this._currentTransform.target.originX;
  9498. this._previousOriginY = this._currentTransform.target.originY;
  9499. var center = target.getCenterPoint();
  9500. target.originX = 'center';
  9501. target.originY = 'center';
  9502. target.left = center.x;
  9503. target.top = center.y;
  9504. this._currentTransform.left = target.left;
  9505. this._currentTransform.top = target.top;
  9506. },
  9507. /**
  9508. * @private
  9509. * @param {Object} target Object for that center is set to origin
  9510. */
  9511. _setCenterToOrigin: function(target) {
  9512. var originPoint = target.translateToOriginPoint(
  9513. target.getCenterPoint(),
  9514. this._previousOriginX,
  9515. this._previousOriginY);
  9516. target.originX = this._previousOriginX;
  9517. target.originY = this._previousOriginY;
  9518. target.left = originPoint.x;
  9519. target.top = originPoint.y;
  9520. this._previousOriginX = null;
  9521. this._previousOriginY = null;
  9522. },
  9523. /**
  9524. * Method that defines the actions when mouse is hovering the canvas.
  9525. * The currentTransform parameter will definde whether the user is rotating/scaling/translating
  9526. * an image or neither of them (only hovering). A group selection is also possible and would cancel
  9527. * all any other type of action.
  9528. * In case of an image transformation only the top canvas will be rendered.
  9529. * @private
  9530. * @param {Event} e Event object fired on mousemove
  9531. */
  9532. __onMouseMove: function (e) {
  9533. var target, pointer;
  9534. if (this.isDrawingMode) {
  9535. this._onMouseMoveInDrawingMode(e);
  9536. return;
  9537. }
  9538. if (typeof e.touches !== 'undefined' && e.touches.length > 1) {
  9539. return;
  9540. }
  9541. var groupSelector = this._groupSelector;
  9542. // We initially clicked in an empty area, so we draw a box for multiple selection
  9543. if (groupSelector) {
  9544. pointer = this.getPointer(e, true);
  9545. groupSelector.left = pointer.x - groupSelector.ex;
  9546. groupSelector.top = pointer.y - groupSelector.ey;
  9547. this.renderTop();
  9548. }
  9549. else if (!this._currentTransform) {
  9550. target = this.findTarget(e);
  9551. this._setCursorFromEvent(e, target);
  9552. }
  9553. else {
  9554. this._transformObject(e);
  9555. }
  9556. this._handleEvent(e, 'move', target ? target : null);
  9557. },
  9558. /**
  9559. * Method that defines actions when an Event Mouse Wheel
  9560. * @param {Event} e Event object fired on mouseup
  9561. */
  9562. __onMouseWheel: function(e) {
  9563. this._handleEvent(e, 'wheel');
  9564. },
  9565. /**
  9566. * @private
  9567. * @param {Event} e Event fired on mousemove
  9568. */
  9569. _transformObject: function(e) {
  9570. var pointer = this.getPointer(e),
  9571. transform = this._currentTransform;
  9572. transform.reset = false;
  9573. transform.target.isMoving = true;
  9574. transform.shiftKey = e.shiftKey;
  9575. transform.altKey = e[this.centeredKey];
  9576. this._beforeScaleTransform(e, transform);
  9577. this._performTransformAction(e, transform, pointer);
  9578. transform.actionPerformed && this.renderAll();
  9579. },
  9580. /**
  9581. * @private
  9582. */
  9583. _performTransformAction: function(e, transform, pointer) {
  9584. var x = pointer.x,
  9585. y = pointer.y,
  9586. target = transform.target,
  9587. action = transform.action,
  9588. actionPerformed = false;
  9589. if (action === 'rotate') {
  9590. (actionPerformed = this._rotateObject(x, y)) && this._fire('rotating', target, e);
  9591. }
  9592. else if (action === 'scale') {
  9593. (actionPerformed = this._onScale(e, transform, x, y)) && this._fire('scaling', target, e);
  9594. }
  9595. else if (action === 'scaleX') {
  9596. (actionPerformed = this._scaleObject(x, y, 'x')) && this._fire('scaling', target, e);
  9597. }
  9598. else if (action === 'scaleY') {
  9599. (actionPerformed = this._scaleObject(x, y, 'y')) && this._fire('scaling', target, e);
  9600. }
  9601. else if (action === 'skewX') {
  9602. (actionPerformed = this._skewObject(x, y, 'x')) && this._fire('skewing', target, e);
  9603. }
  9604. else if (action === 'skewY') {
  9605. (actionPerformed = this._skewObject(x, y, 'y')) && this._fire('skewing', target, e);
  9606. }
  9607. else {
  9608. actionPerformed = this._translateObject(x, y);
  9609. if (actionPerformed) {
  9610. this._fire('moving', target, e);
  9611. this.setCursor(target.moveCursor || this.moveCursor);
  9612. }
  9613. }
  9614. transform.actionPerformed = transform.actionPerformed || actionPerformed;
  9615. },
  9616. /**
  9617. * @private
  9618. */
  9619. _fire: function(eventName, target, e) {
  9620. this.fire('object:' + eventName, { target: target, e: e });
  9621. target.fire(eventName, { e: e });
  9622. },
  9623. /**
  9624. * @private
  9625. */
  9626. _beforeScaleTransform: function(e, transform) {
  9627. if (transform.action === 'scale' || transform.action === 'scaleX' || transform.action === 'scaleY') {
  9628. var centerTransform = this._shouldCenterTransform(transform.target);
  9629. // Switch from a normal resize to center-based
  9630. if ((centerTransform && (transform.originX !== 'center' || transform.originY !== 'center')) ||
  9631. // Switch from center-based resize to normal one
  9632. (!centerTransform && transform.originX === 'center' && transform.originY === 'center')
  9633. ) {
  9634. this._resetCurrentTransform();
  9635. transform.reset = true;
  9636. }
  9637. }
  9638. },
  9639. /**
  9640. * @private
  9641. * @param {Event} e Event object
  9642. * @param {Object} transform current tranform
  9643. * @param {Number} x mouse position x from origin
  9644. * @param {Number} y mouse poistion y from origin
  9645. * @return {Boolean} true if the scaling occurred
  9646. */
  9647. _onScale: function(e, transform, x, y) {
  9648. if ((e[this.uniScaleKey] || this.uniScaleTransform) && !transform.target.get('lockUniScaling')) {
  9649. transform.currentAction = 'scale';
  9650. return this._scaleObject(x, y);
  9651. }
  9652. else {
  9653. // Switch from a normal resize to proportional
  9654. if (!transform.reset && transform.currentAction === 'scale') {
  9655. this._resetCurrentTransform();
  9656. }
  9657. transform.currentAction = 'scaleEqually';
  9658. return this._scaleObject(x, y, 'equally');
  9659. }
  9660. },
  9661. /**
  9662. * Sets the cursor depending on where the canvas is being hovered.
  9663. * Note: very buggy in Opera
  9664. * @param {Event} e Event object
  9665. * @param {Object} target Object that the mouse is hovering, if so.
  9666. */
  9667. _setCursorFromEvent: function (e, target) {
  9668. if (!target || !target.selectable) {
  9669. this.setCursor(this.defaultCursor);
  9670. return false;
  9671. }
  9672. var hoverCursor = target.hoverCursor || this.hoverCursor,
  9673. activeGroup = this.getActiveGroup(),
  9674. // only show proper corner when group selection is not active
  9675. corner = target._findTargetCorner
  9676. && (!activeGroup || !activeGroup.contains(target))
  9677. && target._findTargetCorner(this.getPointer(e, true));
  9678. if (!corner) {
  9679. this.setCursor(hoverCursor);
  9680. }
  9681. else {
  9682. this._setCornerCursor(corner, target, e);
  9683. }
  9684. //actually unclear why it should return something
  9685. //is never evaluated
  9686. return true;
  9687. },
  9688. /**
  9689. * @private
  9690. */
  9691. _setCornerCursor: function(corner, target, e) {
  9692. if (corner in cursorOffset) {
  9693. this.setCursor(this._getRotatedCornerCursor(corner, target, e));
  9694. }
  9695. else if (corner === 'mtr' && target.hasRotatingPoint) {
  9696. this.setCursor(this.rotationCursor);
  9697. }
  9698. else {
  9699. this.setCursor(this.defaultCursor);
  9700. return false;
  9701. }
  9702. },
  9703. /**
  9704. * @private
  9705. */
  9706. _getRotatedCornerCursor: function(corner, target, e) {
  9707. var n = Math.round((target.getAngle() % 360) / 45);
  9708. if (n < 0) {
  9709. n += 8; // full circle ahead
  9710. }
  9711. n += cursorOffset[corner];
  9712. if (e[this.altActionKey] && cursorOffset[corner] % 2 === 0) {
  9713. //if we are holding shift and we are on a mx corner...
  9714. n += 2;
  9715. }
  9716. // normalize n to be from 0 to 7
  9717. n %= 8;
  9718. return this.cursorMap[n];
  9719. }
  9720. });
  9721. })();
  9722. (function() {
  9723. var min = Math.min,
  9724. max = Math.max;
  9725. fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ {
  9726. /**
  9727. * @private
  9728. * @param {Event} e Event object
  9729. * @param {fabric.Object} target
  9730. * @return {Boolean}
  9731. */
  9732. _shouldGroup: function(e, target) {
  9733. var activeObject = this.getActiveObject();
  9734. return e[this.selectionKey] && target && target.selectable &&
  9735. (this.getActiveGroup() || (activeObject && activeObject !== target))
  9736. && this.selection;
  9737. },
  9738. /**
  9739. * @private
  9740. * @param {Event} e Event object
  9741. * @param {fabric.Object} target
  9742. */
  9743. _handleGrouping: function (e, target) {
  9744. var activeGroup = this.getActiveGroup();
  9745. if (target === activeGroup) {
  9746. // if it's a group, find target again, using activeGroup objects
  9747. target = this.findTarget(e, true);
  9748. // if even object is not found, bail out
  9749. if (!target) {
  9750. return;
  9751. }
  9752. }
  9753. if (activeGroup) {
  9754. this._updateActiveGroup(target, e);
  9755. }
  9756. else {
  9757. this._createActiveGroup(target, e);
  9758. }
  9759. if (this._activeGroup) {
  9760. this._activeGroup.saveCoords();
  9761. }
  9762. },
  9763. /**
  9764. * @private
  9765. */
  9766. _updateActiveGroup: function(target, e) {
  9767. var activeGroup = this.getActiveGroup();
  9768. if (activeGroup.contains(target)) {
  9769. activeGroup.removeWithUpdate(target);
  9770. target.set('active', false);
  9771. if (activeGroup.size() === 1) {
  9772. // remove group alltogether if after removal it only contains 1 object
  9773. this.discardActiveGroup(e);
  9774. // activate last remaining object
  9775. this.setActiveObject(activeGroup.item(0));
  9776. return;
  9777. }
  9778. }
  9779. else {
  9780. activeGroup.addWithUpdate(target);
  9781. }
  9782. this.fire('selection:created', { target: activeGroup, e: e });
  9783. activeGroup.set('active', true);
  9784. },
  9785. /**
  9786. * @private
  9787. */
  9788. _createActiveGroup: function(target, e) {
  9789. if (this._activeObject && target !== this._activeObject) {
  9790. var group = this._createGroup(target);
  9791. group.addWithUpdate();
  9792. this.setActiveGroup(group);
  9793. this._activeObject = null;
  9794. this.fire('selection:created', { target: group, e: e });
  9795. }
  9796. target.set('active', true);
  9797. },
  9798. /**
  9799. * @private
  9800. * @param {Object} target
  9801. */
  9802. _createGroup: function(target) {
  9803. var objects = this.getObjects(),
  9804. isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target),
  9805. groupObjects = isActiveLower
  9806. ? [this._activeObject, target]
  9807. : [target, this._activeObject];
  9808. this._activeObject.isEditing && this._activeObject.exitEditing();
  9809. return new fabric.Group(groupObjects, {
  9810. canvas: this
  9811. });
  9812. },
  9813. /**
  9814. * @private
  9815. * @param {Event} e mouse event
  9816. */
  9817. _groupSelectedObjects: function (e) {
  9818. var group = this._collectObjects();
  9819. // do not create group for 1 element only
  9820. if (group.length === 1) {
  9821. this.setActiveObject(group[0], e);
  9822. }
  9823. else if (group.length > 1) {
  9824. group = new fabric.Group(group.reverse(), {
  9825. canvas: this
  9826. });
  9827. group.addWithUpdate();
  9828. this.setActiveGroup(group, e);
  9829. group.saveCoords();
  9830. this.fire('selection:created', { target: group });
  9831. this.renderAll();
  9832. }
  9833. },
  9834. /**
  9835. * @private
  9836. */
  9837. _collectObjects: function() {
  9838. var group = [],
  9839. currentObject,
  9840. x1 = this._groupSelector.ex,
  9841. y1 = this._groupSelector.ey,
  9842. x2 = x1 + this._groupSelector.left,
  9843. y2 = y1 + this._groupSelector.top,
  9844. selectionX1Y1 = new fabric.Point(min(x1, x2), min(y1, y2)),
  9845. selectionX2Y2 = new fabric.Point(max(x1, x2), max(y1, y2)),
  9846. isClick = x1 === x2 && y1 === y2;
  9847. for (var i = this._objects.length; i--; ) {
  9848. currentObject = this._objects[i];
  9849. if (!currentObject || !currentObject.selectable || !currentObject.visible) {
  9850. continue;
  9851. }
  9852. if (currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2) ||
  9853. currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2) ||
  9854. currentObject.containsPoint(selectionX1Y1) ||
  9855. currentObject.containsPoint(selectionX2Y2)
  9856. ) {
  9857. currentObject.set('active', true);
  9858. group.push(currentObject);
  9859. // only add one object if it's a click
  9860. if (isClick) {
  9861. break;
  9862. }
  9863. }
  9864. }
  9865. return group;
  9866. },
  9867. /**
  9868. * @private
  9869. */
  9870. _maybeGroupObjects: function(e) {
  9871. if (this.selection && this._groupSelector) {
  9872. this._groupSelectedObjects(e);
  9873. }
  9874. var activeGroup = this.getActiveGroup();
  9875. if (activeGroup) {
  9876. activeGroup.setObjectsCoords().setCoords();
  9877. activeGroup.isMoving = false;
  9878. this.setCursor(this.defaultCursor);
  9879. }
  9880. // clear selection and current transformation
  9881. this._groupSelector = null;
  9882. this._currentTransform = null;
  9883. }
  9884. });
  9885. })();
  9886. (function () {
  9887. var supportQuality = fabric.StaticCanvas.supports('toDataURLWithQuality');
  9888. fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ {
  9889. /**
  9890. * Exports canvas element to a dataurl image. Note that when multiplier is used, cropping is scaled appropriately
  9891. * @param {Object} [options] Options object
  9892. * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png"
  9893. * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg.
  9894. * @param {Number} [options.multiplier=1] Multiplier to scale by
  9895. * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14
  9896. * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14
  9897. * @param {Number} [options.width] Cropping width. Introduced in v1.2.14
  9898. * @param {Number} [options.height] Cropping height. Introduced in v1.2.14
  9899. * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format
  9900. * @see {@link http://jsfiddle.net/fabricjs/NfZVb/|jsFiddle demo}
  9901. * @example <caption>Generate jpeg dataURL with lower quality</caption>
  9902. * var dataURL = canvas.toDataURL({
  9903. * format: 'jpeg',
  9904. * quality: 0.8
  9905. * });
  9906. * @example <caption>Generate cropped png dataURL (clipping of canvas)</caption>
  9907. * var dataURL = canvas.toDataURL({
  9908. * format: 'png',
  9909. * left: 100,
  9910. * top: 100,
  9911. * width: 200,
  9912. * height: 200
  9913. * });
  9914. * @example <caption>Generate double scaled png dataURL</caption>
  9915. * var dataURL = canvas.toDataURL({
  9916. * format: 'png',
  9917. * multiplier: 2
  9918. * });
  9919. */
  9920. toDataURL: function (options) {
  9921. options || (options = { });
  9922. var format = options.format || 'png',
  9923. quality = options.quality || 1,
  9924. multiplier = options.multiplier || 1,
  9925. cropping = {
  9926. left: options.left || 0,
  9927. top: options.top || 0,
  9928. width: options.width || 0,
  9929. height: options.height || 0,
  9930. };
  9931. return this.__toDataURLWithMultiplier(format, quality, cropping, multiplier);
  9932. },
  9933. /**
  9934. * @private
  9935. */
  9936. __toDataURLWithMultiplier: function(format, quality, cropping, multiplier) {
  9937. var origWidth = this.getWidth(),
  9938. origHeight = this.getHeight(),
  9939. scaledWidth = (cropping.width || this.getWidth()) * multiplier,
  9940. scaledHeight = (cropping.height || this.getHeight()) * multiplier,
  9941. zoom = this.getZoom(),
  9942. newZoom = zoom * multiplier,
  9943. vp = this.viewportTransform,
  9944. translateX = (vp[4] - cropping.left) * multiplier,
  9945. translateY = (vp[5] - cropping.top) * multiplier,
  9946. newVp = [newZoom, 0, 0, newZoom, translateX, translateY],
  9947. originalInteractive = this.interactive;
  9948. this.viewportTransform = newVp;
  9949. // setting interactive to false avoid exporting controls
  9950. this.interactive && (this.interactive = false);
  9951. if (origWidth !== scaledWidth || origHeight !== scaledHeight) {
  9952. // this.setDimensions is going to renderAll also;
  9953. this.setDimensions({ width: scaledWidth, height: scaledHeight });
  9954. }
  9955. else {
  9956. this.renderAll();
  9957. }
  9958. var data = this.__toDataURL(format, quality, cropping);
  9959. originalInteractive && (this.interactive = originalInteractive);
  9960. this.viewportTransform = vp;
  9961. //setDimensions with no option object is taking care of:
  9962. //this.width, this.height, this.renderAll()
  9963. this.setDimensions({ width: origWidth, height: origHeight });
  9964. return data;
  9965. },
  9966. /**
  9967. * @private
  9968. */
  9969. __toDataURL: function(format, quality) {
  9970. var canvasEl = this.contextContainer.canvas;
  9971. // to avoid common confusion https://github.com/kangax/fabric.js/issues/806
  9972. if (format === 'jpg') {
  9973. format = 'jpeg';
  9974. }
  9975. var data = supportQuality
  9976. ? canvasEl.toDataURL('image/' + format, quality)
  9977. : canvasEl.toDataURL('image/' + format);
  9978. return data;
  9979. },
  9980. /**
  9981. * Exports canvas element to a dataurl image (allowing to change image size via multiplier).
  9982. * @deprecated since 1.0.13
  9983. * @param {String} format (png|jpeg)
  9984. * @param {Number} multiplier
  9985. * @param {Number} quality (0..1)
  9986. * @return {String}
  9987. */
  9988. toDataURLWithMultiplier: function (format, multiplier, quality) {
  9989. return this.toDataURL({
  9990. format: format,
  9991. multiplier: multiplier,
  9992. quality: quality
  9993. });
  9994. },
  9995. });
  9996. })();
  9997. fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ {
  9998. /**
  9999. * Populates canvas with data from the specified dataless JSON.
  10000. * JSON format must conform to the one of {@link fabric.Canvas#toDatalessJSON}
  10001. * @deprecated since 1.2.2
  10002. * @param {String|Object} json JSON string or object
  10003. * @param {Function} callback Callback, invoked when json is parsed
  10004. * and corresponding objects (e.g: {@link fabric.Image})
  10005. * are initialized
  10006. * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created.
  10007. * @return {fabric.Canvas} instance
  10008. * @chainable
  10009. * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#deserialization}
  10010. */
  10011. loadFromDatalessJSON: function (json, callback, reviver) {
  10012. return this.loadFromJSON(json, callback, reviver);
  10013. },
  10014. /**
  10015. * Populates canvas with data from the specified JSON.
  10016. * JSON format must conform to the one of {@link fabric.Canvas#toJSON}
  10017. * @param {String|Object} json JSON string or object
  10018. * @param {Function} callback Callback, invoked when json is parsed
  10019. * and corresponding objects (e.g: {@link fabric.Image})
  10020. * are initialized
  10021. * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created.
  10022. * @return {fabric.Canvas} instance
  10023. * @chainable
  10024. * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#deserialization}
  10025. * @see {@link http://jsfiddle.net/fabricjs/fmgXt/|jsFiddle demo}
  10026. * @example <caption>loadFromJSON</caption>
  10027. * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas));
  10028. * @example <caption>loadFromJSON with reviver</caption>
  10029. * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas), function(o, object) {
  10030. * // `o` = json object
  10031. * // `object` = fabric.Object instance
  10032. * // ... do some stuff ...
  10033. * });
  10034. */
  10035. loadFromJSON: function (json, callback, reviver) {
  10036. if (!json) {
  10037. return;
  10038. }
  10039. // serialize if it wasn't already
  10040. var serialized = (typeof json === 'string')
  10041. ? JSON.parse(json)
  10042. : fabric.util.object.clone(json);
  10043. this.clear();
  10044. var _this = this;
  10045. this._enlivenObjects(serialized.objects, function () {
  10046. _this._setBgOverlay(serialized, function () {
  10047. // remove parts i cannot set as options
  10048. delete serialized.objects;
  10049. delete serialized.backgroundImage;
  10050. delete serialized.overlayImage;
  10051. delete serialized.background;
  10052. delete serialized.overlay;
  10053. // this._initOptions does too many things to just
  10054. // call it. Normally loading an Object from JSON
  10055. // create the Object instance. Here the Canvas is
  10056. // already an instance and we are just loading things over it
  10057. _this._setOptions(serialized);
  10058. callback && callback();
  10059. });
  10060. }, reviver);
  10061. return this;
  10062. },
  10063. /**
  10064. * @private
  10065. * @param {Object} serialized Object with background and overlay information
  10066. * @param {Function} callback Invoked after all background and overlay images/patterns loaded
  10067. */
  10068. _setBgOverlay: function(serialized, callback) {
  10069. var _this = this,
  10070. loaded = {
  10071. backgroundColor: false,
  10072. overlayColor: false,
  10073. backgroundImage: false,
  10074. overlayImage: false
  10075. };
  10076. if (!serialized.backgroundImage && !serialized.overlayImage && !serialized.background && !serialized.overlay) {
  10077. callback && callback();
  10078. return;
  10079. }
  10080. var cbIfLoaded = function () {
  10081. if (loaded.backgroundImage && loaded.overlayImage && loaded.backgroundColor && loaded.overlayColor) {
  10082. _this.renderAll();
  10083. callback && callback();
  10084. }
  10085. };
  10086. this.__setBgOverlay('backgroundImage', serialized.backgroundImage, loaded, cbIfLoaded);
  10087. this.__setBgOverlay('overlayImage', serialized.overlayImage, loaded, cbIfLoaded);
  10088. this.__setBgOverlay('backgroundColor', serialized.background, loaded, cbIfLoaded);
  10089. this.__setBgOverlay('overlayColor', serialized.overlay, loaded, cbIfLoaded);
  10090. },
  10091. /**
  10092. * @private
  10093. * @param {String} property Property to set (backgroundImage, overlayImage, backgroundColor, overlayColor)
  10094. * @param {(Object|String)} value Value to set
  10095. * @param {Object} loaded Set loaded property to true if property is set
  10096. * @param {Object} callback Callback function to invoke after property is set
  10097. */
  10098. __setBgOverlay: function(property, value, loaded, callback) {
  10099. var _this = this;
  10100. if (!value) {
  10101. loaded[property] = true;
  10102. callback && callback();
  10103. return;
  10104. }
  10105. if (property === 'backgroundImage' || property === 'overlayImage') {
  10106. fabric.util.enlivenObjects([value], function(enlivedObject){
  10107. _this[property] = enlivedObject[0];
  10108. loaded[property] = true;
  10109. callback && callback();
  10110. });
  10111. }
  10112. else {
  10113. this['set' + fabric.util.string.capitalize(property, true)](value, function() {
  10114. loaded[property] = true;
  10115. callback && callback();
  10116. });
  10117. }
  10118. },
  10119. /**
  10120. * @private
  10121. * @param {Array} objects
  10122. * @param {Function} callback
  10123. * @param {Function} [reviver]
  10124. */
  10125. _enlivenObjects: function (objects, callback, reviver) {
  10126. var _this = this;
  10127. if (!objects || objects.length === 0) {
  10128. callback && callback();
  10129. return;
  10130. }
  10131. var renderOnAddRemove = this.renderOnAddRemove;
  10132. this.renderOnAddRemove = false;
  10133. fabric.util.enlivenObjects(objects, function(enlivenedObjects) {
  10134. enlivenedObjects.forEach(function(obj, index) {
  10135. // we splice the array just in case some custom classes restored from JSON
  10136. // will add more object to canvas at canvas init.
  10137. _this.insertAt(obj, index);
  10138. });
  10139. _this.renderOnAddRemove = renderOnAddRemove;
  10140. callback && callback();
  10141. }, null, reviver);
  10142. },
  10143. /**
  10144. * @private
  10145. * @param {String} format
  10146. * @param {Function} callback
  10147. */
  10148. _toDataURL: function (format, callback) {
  10149. this.clone(function (clone) {
  10150. callback(clone.toDataURL(format));
  10151. });
  10152. },
  10153. /**
  10154. * @private
  10155. * @param {String} format
  10156. * @param {Number} multiplier
  10157. * @param {Function} callback
  10158. */
  10159. _toDataURLWithMultiplier: function (format, multiplier, callback) {
  10160. this.clone(function (clone) {
  10161. callback(clone.toDataURLWithMultiplier(format, multiplier));
  10162. });
  10163. },
  10164. /**
  10165. * Clones canvas instance
  10166. * @param {Object} [callback] Receives cloned instance as a first argument
  10167. * @param {Array} [properties] Array of properties to include in the cloned canvas and children
  10168. */
  10169. clone: function (callback, properties) {
  10170. var data = JSON.stringify(this.toJSON(properties));
  10171. this.cloneWithoutData(function(clone) {
  10172. clone.loadFromJSON(data, function() {
  10173. callback && callback(clone);
  10174. });
  10175. });
  10176. },
  10177. /**
  10178. * Clones canvas instance without cloning existing data.
  10179. * This essentially copies canvas dimensions, clipping properties, etc.
  10180. * but leaves data empty (so that you can populate it with your own)
  10181. * @param {Object} [callback] Receives cloned instance as a first argument
  10182. */
  10183. cloneWithoutData: function(callback) {
  10184. var el = fabric.document.createElement('canvas');
  10185. el.width = this.getWidth();
  10186. el.height = this.getHeight();
  10187. var clone = new fabric.Canvas(el);
  10188. clone.clipTo = this.clipTo;
  10189. if (this.backgroundImage) {
  10190. clone.setBackgroundImage(this.backgroundImage.src, function() {
  10191. clone.renderAll();
  10192. callback && callback(clone);
  10193. });
  10194. clone.backgroundImageOpacity = this.backgroundImageOpacity;
  10195. clone.backgroundImageStretch = this.backgroundImageStretch;
  10196. }
  10197. else {
  10198. callback && callback(clone);
  10199. }
  10200. }
  10201. });
  10202. (function(global) {
  10203. 'use strict';
  10204. var fabric = global.fabric || (global.fabric = { }),
  10205. extend = fabric.util.object.extend,
  10206. clone = fabric.util.object.clone,
  10207. toFixed = fabric.util.toFixed,
  10208. capitalize = fabric.util.string.capitalize,
  10209. degreesToRadians = fabric.util.degreesToRadians,
  10210. supportsLineDash = fabric.StaticCanvas.supports('setLineDash'),
  10211. objectCaching = !fabric.isLikelyNode;
  10212. if (fabric.Object) {
  10213. return;
  10214. }
  10215. /**
  10216. * Root object class from which all 2d shape classes inherit from
  10217. * @class fabric.Object
  10218. * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#objects}
  10219. * @see {@link fabric.Object#initialize} for constructor definition
  10220. *
  10221. * @fires added
  10222. * @fires removed
  10223. *
  10224. * @fires selected
  10225. * @fires deselected
  10226. * @fires modified
  10227. * @fires rotating
  10228. * @fires scaling
  10229. * @fires moving
  10230. * @fires skewing
  10231. *
  10232. * @fires mousedown
  10233. * @fires mouseup
  10234. * @fires mouseover
  10235. * @fires mouseout
  10236. * @fires mousewheel
  10237. */
  10238. fabric.Object = fabric.util.createClass(fabric.CommonMethods, /** @lends fabric.Object.prototype */ {
  10239. /**
  10240. * Retrieves object's {@link fabric.Object#clipTo|clipping function}
  10241. * @method getClipTo
  10242. * @memberOf fabric.Object.prototype
  10243. * @return {Function}
  10244. */
  10245. /**
  10246. * Sets object's {@link fabric.Object#clipTo|clipping function}
  10247. * @method setClipTo
  10248. * @memberOf fabric.Object.prototype
  10249. * @param {Function} clipTo Clipping function
  10250. * @return {fabric.Object} thisArg
  10251. * @chainable
  10252. */
  10253. /**
  10254. * Retrieves object's {@link fabric.Object#transformMatrix|transformMatrix}
  10255. * @method getTransformMatrix
  10256. * @memberOf fabric.Object.prototype
  10257. * @return {Array} transformMatrix
  10258. */
  10259. /**
  10260. * Sets object's {@link fabric.Object#transformMatrix|transformMatrix}
  10261. * @method setTransformMatrix
  10262. * @memberOf fabric.Object.prototype
  10263. * @param {Array} transformMatrix
  10264. * @return {fabric.Object} thisArg
  10265. * @chainable
  10266. */
  10267. /**
  10268. * Retrieves object's {@link fabric.Object#visible|visible} state
  10269. * @method getVisible
  10270. * @memberOf fabric.Object.prototype
  10271. * @return {Boolean} True if visible
  10272. */
  10273. /**
  10274. * Sets object's {@link fabric.Object#visible|visible} state
  10275. * @method setVisible
  10276. * @memberOf fabric.Object.prototype
  10277. * @param {Boolean} value visible value
  10278. * @return {fabric.Object} thisArg
  10279. * @chainable
  10280. */
  10281. /**
  10282. * Retrieves object's {@link fabric.Object#shadow|shadow}
  10283. * @method getShadow
  10284. * @memberOf fabric.Object.prototype
  10285. * @return {Object} Shadow instance
  10286. */
  10287. /**
  10288. * Retrieves object's {@link fabric.Object#stroke|stroke}
  10289. * @method getStroke
  10290. * @memberOf fabric.Object.prototype
  10291. * @return {String} stroke value
  10292. */
  10293. /**
  10294. * Sets object's {@link fabric.Object#stroke|stroke}
  10295. * @method setStroke
  10296. * @memberOf fabric.Object.prototype
  10297. * @param {String} value stroke value
  10298. * @return {fabric.Object} thisArg
  10299. * @chainable
  10300. */
  10301. /**
  10302. * Retrieves object's {@link fabric.Object#strokeWidth|strokeWidth}
  10303. * @method getStrokeWidth
  10304. * @memberOf fabric.Object.prototype
  10305. * @return {Number} strokeWidth value
  10306. */
  10307. /**
  10308. * Sets object's {@link fabric.Object#strokeWidth|strokeWidth}
  10309. * @method setStrokeWidth
  10310. * @memberOf fabric.Object.prototype
  10311. * @param {Number} value strokeWidth value
  10312. * @return {fabric.Object} thisArg
  10313. * @chainable
  10314. */
  10315. /**
  10316. * Retrieves object's {@link fabric.Object#originX|originX}
  10317. * @method getOriginX
  10318. * @memberOf fabric.Object.prototype
  10319. * @return {String} originX value
  10320. */
  10321. /**
  10322. * Sets object's {@link fabric.Object#originX|originX}
  10323. * @method setOriginX
  10324. * @memberOf fabric.Object.prototype
  10325. * @param {String} value originX value
  10326. * @return {fabric.Object} thisArg
  10327. * @chainable
  10328. */
  10329. /**
  10330. * Retrieves object's {@link fabric.Object#originY|originY}
  10331. * @method getOriginY
  10332. * @memberOf fabric.Object.prototype
  10333. * @return {String} originY value
  10334. */
  10335. /**
  10336. * Sets object's {@link fabric.Object#originY|originY}
  10337. * @method setOriginY
  10338. * @memberOf fabric.Object.prototype
  10339. * @param {String} value originY value
  10340. * @return {fabric.Object} thisArg
  10341. * @chainable
  10342. */
  10343. /**
  10344. * Retrieves object's {@link fabric.Object#fill|fill}
  10345. * @method getFill
  10346. * @memberOf fabric.Object.prototype
  10347. * @return {String} Fill value
  10348. */
  10349. /**
  10350. * Sets object's {@link fabric.Object#fill|fill}
  10351. * @method setFill
  10352. * @memberOf fabric.Object.prototype
  10353. * @param {String} value Fill value
  10354. * @return {fabric.Object} thisArg
  10355. * @chainable
  10356. */
  10357. /**
  10358. * Retrieves object's {@link fabric.Object#opacity|opacity}
  10359. * @method getOpacity
  10360. * @memberOf fabric.Object.prototype
  10361. * @return {Number} Opacity value (0-1)
  10362. */
  10363. /**
  10364. * Sets object's {@link fabric.Object#opacity|opacity}
  10365. * @method setOpacity
  10366. * @memberOf fabric.Object.prototype
  10367. * @param {Number} value Opacity value (0-1)
  10368. * @return {fabric.Object} thisArg
  10369. * @chainable
  10370. */
  10371. /**
  10372. * Retrieves object's {@link fabric.Object#angle|angle} (in degrees)
  10373. * @method getAngle
  10374. * @memberOf fabric.Object.prototype
  10375. * @return {Number}
  10376. */
  10377. /**
  10378. * Retrieves object's {@link fabric.Object#top|top position}
  10379. * @method getTop
  10380. * @memberOf fabric.Object.prototype
  10381. * @return {Number} Top value (in pixels)
  10382. */
  10383. /**
  10384. * Sets object's {@link fabric.Object#top|top position}
  10385. * @method setTop
  10386. * @memberOf fabric.Object.prototype
  10387. * @param {Number} value Top value (in pixels)
  10388. * @return {fabric.Object} thisArg
  10389. * @chainable
  10390. */
  10391. /**
  10392. * Retrieves object's {@link fabric.Object#left|left position}
  10393. * @method getLeft
  10394. * @memberOf fabric.Object.prototype
  10395. * @return {Number} Left value (in pixels)
  10396. */
  10397. /**
  10398. * Sets object's {@link fabric.Object#left|left position}
  10399. * @method setLeft
  10400. * @memberOf fabric.Object.prototype
  10401. * @param {Number} value Left value (in pixels)
  10402. * @return {fabric.Object} thisArg
  10403. * @chainable
  10404. */
  10405. /**
  10406. * Retrieves object's {@link fabric.Object#scaleX|scaleX} value
  10407. * @method getScaleX
  10408. * @memberOf fabric.Object.prototype
  10409. * @return {Number} scaleX value
  10410. */
  10411. /**
  10412. * Sets object's {@link fabric.Object#scaleX|scaleX} value
  10413. * @method setScaleX
  10414. * @memberOf fabric.Object.prototype
  10415. * @param {Number} value scaleX value
  10416. * @return {fabric.Object} thisArg
  10417. * @chainable
  10418. */
  10419. /**
  10420. * Retrieves object's {@link fabric.Object#scaleY|scaleY} value
  10421. * @method getScaleY
  10422. * @memberOf fabric.Object.prototype
  10423. * @return {Number} scaleY value
  10424. */
  10425. /**
  10426. * Sets object's {@link fabric.Object#scaleY|scaleY} value
  10427. * @method setScaleY
  10428. * @memberOf fabric.Object.prototype
  10429. * @param {Number} value scaleY value
  10430. * @return {fabric.Object} thisArg
  10431. * @chainable
  10432. */
  10433. /**
  10434. * Retrieves object's {@link fabric.Object#flipX|flipX} value
  10435. * @method getFlipX
  10436. * @memberOf fabric.Object.prototype
  10437. * @return {Boolean} flipX value
  10438. */
  10439. /**
  10440. * Sets object's {@link fabric.Object#flipX|flipX} value
  10441. * @method setFlipX
  10442. * @memberOf fabric.Object.prototype
  10443. * @param {Boolean} value flipX value
  10444. * @return {fabric.Object} thisArg
  10445. * @chainable
  10446. */
  10447. /**
  10448. * Retrieves object's {@link fabric.Object#flipY|flipY} value
  10449. * @method getFlipY
  10450. * @memberOf fabric.Object.prototype
  10451. * @return {Boolean} flipY value
  10452. */
  10453. /**
  10454. * Sets object's {@link fabric.Object#flipY|flipY} value
  10455. * @method setFlipY
  10456. * @memberOf fabric.Object.prototype
  10457. * @param {Boolean} value flipY value
  10458. * @return {fabric.Object} thisArg
  10459. * @chainable
  10460. */
  10461. /**
  10462. * Type of an object (rect, circle, path, etc.).
  10463. * Note that this property is meant to be read-only and not meant to be modified.
  10464. * If you modify, certain parts of Fabric (such as JSON loading) won't work correctly.
  10465. * @type String
  10466. * @default
  10467. */
  10468. type: 'object',
  10469. /**
  10470. * Horizontal origin of transformation of an object (one of "left", "right", "center")
  10471. * See http://jsfiddle.net/1ow02gea/40/ on how originX/originY affect objects in groups
  10472. * @type String
  10473. * @default
  10474. */
  10475. originX: 'left',
  10476. /**
  10477. * Vertical origin of transformation of an object (one of "top", "bottom", "center")
  10478. * See http://jsfiddle.net/1ow02gea/40/ on how originX/originY affect objects in groups
  10479. * @type String
  10480. * @default
  10481. */
  10482. originY: 'top',
  10483. /**
  10484. * Top position of an object. Note that by default it's relative to object top. You can change this by setting originY={top/center/bottom}
  10485. * @type Number
  10486. * @default
  10487. */
  10488. top: 0,
  10489. /**
  10490. * Left position of an object. Note that by default it's relative to object left. You can change this by setting originX={left/center/right}
  10491. * @type Number
  10492. * @default
  10493. */
  10494. left: 0,
  10495. /**
  10496. * Object width
  10497. * @type Number
  10498. * @default
  10499. */
  10500. width: 0,
  10501. /**
  10502. * Object height
  10503. * @type Number
  10504. * @default
  10505. */
  10506. height: 0,
  10507. /**
  10508. * Object scale factor (horizontal)
  10509. * @type Number
  10510. * @default
  10511. */
  10512. scaleX: 1,
  10513. /**
  10514. * Object scale factor (vertical)
  10515. * @type Number
  10516. * @default
  10517. */
  10518. scaleY: 1,
  10519. /**
  10520. * When true, an object is rendered as flipped horizontally
  10521. * @type Boolean
  10522. * @default
  10523. */
  10524. flipX: false,
  10525. /**
  10526. * When true, an object is rendered as flipped vertically
  10527. * @type Boolean
  10528. * @default
  10529. */
  10530. flipY: false,
  10531. /**
  10532. * Opacity of an object
  10533. * @type Number
  10534. * @default
  10535. */
  10536. opacity: 1,
  10537. /**
  10538. * Angle of rotation of an object (in degrees)
  10539. * @type Number
  10540. * @default
  10541. */
  10542. angle: 0,
  10543. /**
  10544. * Angle of skew on x axes of an object (in degrees)
  10545. * @type Number
  10546. * @default
  10547. */
  10548. skewX: 0,
  10549. /**
  10550. * Angle of skew on y axes of an object (in degrees)
  10551. * @type Number
  10552. * @default
  10553. */
  10554. skewY: 0,
  10555. /**
  10556. * Size of object's controlling corners (in pixels)
  10557. * @type Number
  10558. * @default
  10559. */
  10560. cornerSize: 13,
  10561. /**
  10562. * When true, object's controlling corners are rendered as transparent inside (i.e. stroke instead of fill)
  10563. * @type Boolean
  10564. * @default
  10565. */
  10566. transparentCorners: true,
  10567. /**
  10568. * Default cursor value used when hovering over this object on canvas
  10569. * @type String
  10570. * @default
  10571. */
  10572. hoverCursor: null,
  10573. /**
  10574. * Default cursor value used when moving this object on canvas
  10575. * @type String
  10576. * @default
  10577. */
  10578. moveCursor: null,
  10579. /**
  10580. * Padding between object and its controlling borders (in pixels)
  10581. * @type Number
  10582. * @default
  10583. */
  10584. padding: 0,
  10585. /**
  10586. * Color of controlling borders of an object (when it's active)
  10587. * @type String
  10588. * @default
  10589. */
  10590. borderColor: 'rgba(102,153,255,0.75)',
  10591. /**
  10592. * Array specifying dash pattern of an object's borders (hasBorder must be true)
  10593. * @since 1.6.2
  10594. * @type Array
  10595. */
  10596. borderDashArray: null,
  10597. /**
  10598. * Color of controlling corners of an object (when it's active)
  10599. * @type String
  10600. * @default
  10601. */
  10602. cornerColor: 'rgba(102,153,255,0.5)',
  10603. /**
  10604. * Color of controlling corners of an object (when it's active and transparentCorners false)
  10605. * @since 1.6.2
  10606. * @type String
  10607. * @default
  10608. */
  10609. cornerStrokeColor: null,
  10610. /**
  10611. * Specify style of control, 'rect' or 'circle'
  10612. * @since 1.6.2
  10613. * @type String
  10614. */
  10615. cornerStyle: 'rect',
  10616. /**
  10617. * Array specifying dash pattern of an object's control (hasBorder must be true)
  10618. * @since 1.6.2
  10619. * @type Array
  10620. */
  10621. cornerDashArray: null,
  10622. /**
  10623. * When true, this object will use center point as the origin of transformation
  10624. * when being scaled via the controls.
  10625. * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean).
  10626. * @since 1.3.4
  10627. * @type Boolean
  10628. * @default
  10629. */
  10630. centeredScaling: false,
  10631. /**
  10632. * When true, this object will use center point as the origin of transformation
  10633. * when being rotated via the controls.
  10634. * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean).
  10635. * @since 1.3.4
  10636. * @type Boolean
  10637. * @default
  10638. */
  10639. centeredRotation: true,
  10640. /**
  10641. * Color of object's fill
  10642. * @type String
  10643. * @default
  10644. */
  10645. fill: 'rgb(0,0,0)',
  10646. /**
  10647. * Fill rule used to fill an object
  10648. * accepted values are nonzero, evenodd
  10649. * <b>Backwards incompatibility note:</b> This property was used for setting globalCompositeOperation until v1.4.12 (use `fabric.Object#globalCompositeOperation` instead)
  10650. * @type String
  10651. * @default
  10652. */
  10653. fillRule: 'nonzero',
  10654. /**
  10655. * Composite rule used for canvas globalCompositeOperation
  10656. * @type String
  10657. * @default
  10658. */
  10659. globalCompositeOperation: 'source-over',
  10660. /**
  10661. * Background color of an object.
  10662. * @type String
  10663. * @default
  10664. */
  10665. backgroundColor: '',
  10666. /**
  10667. * Selection Background color of an object. colored layer behind the object when it is active.
  10668. * does not mix good with globalCompositeOperation methods.
  10669. * @type String
  10670. * @default
  10671. */
  10672. selectionBackgroundColor: '',
  10673. /**
  10674. * When defined, an object is rendered via stroke and this property specifies its color
  10675. * @type String
  10676. * @default
  10677. */
  10678. stroke: null,
  10679. /**
  10680. * Width of a stroke used to render this object
  10681. * @type Number
  10682. * @default
  10683. */
  10684. strokeWidth: 1,
  10685. /**
  10686. * Array specifying dash pattern of an object's stroke (stroke must be defined)
  10687. * @type Array
  10688. */
  10689. strokeDashArray: null,
  10690. /**
  10691. * Line endings style of an object's stroke (one of "butt", "round", "square")
  10692. * @type String
  10693. * @default
  10694. */
  10695. strokeLineCap: 'butt',
  10696. /**
  10697. * Corner style of an object's stroke (one of "bevil", "round", "miter")
  10698. * @type String
  10699. * @default
  10700. */
  10701. strokeLineJoin: 'miter',
  10702. /**
  10703. * Maximum miter length (used for strokeLineJoin = "miter") of an object's stroke
  10704. * @type Number
  10705. * @default
  10706. */
  10707. strokeMiterLimit: 10,
  10708. /**
  10709. * Shadow object representing shadow of this shape
  10710. * @type fabric.Shadow
  10711. * @default
  10712. */
  10713. shadow: null,
  10714. /**
  10715. * Opacity of object's controlling borders when object is active and moving
  10716. * @type Number
  10717. * @default
  10718. */
  10719. borderOpacityWhenMoving: 0.4,
  10720. /**
  10721. * Scale factor of object's controlling borders
  10722. * @type Number
  10723. * @default
  10724. */
  10725. borderScaleFactor: 1,
  10726. /**
  10727. * Transform matrix (similar to SVG's transform matrix)
  10728. * @type Array
  10729. */
  10730. transformMatrix: null,
  10731. /**
  10732. * Minimum allowed scale value of an object
  10733. * @type Number
  10734. * @default
  10735. */
  10736. minScaleLimit: 0.01,
  10737. /**
  10738. * When set to `false`, an object can not be selected for modification (using either point-click-based or group-based selection).
  10739. * But events still fire on it.
  10740. * @type Boolean
  10741. * @default
  10742. */
  10743. selectable: true,
  10744. /**
  10745. * When set to `false`, an object can not be a target of events. All events propagate through it. Introduced in v1.3.4
  10746. * @type Boolean
  10747. * @default
  10748. */
  10749. evented: true,
  10750. /**
  10751. * When set to `false`, an object is not rendered on canvas
  10752. * @type Boolean
  10753. * @default
  10754. */
  10755. visible: true,
  10756. /**
  10757. * When set to `false`, object's controls are not displayed and can not be used to manipulate object
  10758. * @type Boolean
  10759. * @default
  10760. */
  10761. hasControls: true,
  10762. /**
  10763. * When set to `false`, object's controlling borders are not rendered
  10764. * @type Boolean
  10765. * @default
  10766. */
  10767. hasBorders: true,
  10768. /**
  10769. * When set to `false`, object's controlling rotating point will not be visible or selectable
  10770. * @type Boolean
  10771. * @default
  10772. */
  10773. hasRotatingPoint: true,
  10774. /**
  10775. * Offset for object's controlling rotating point (when enabled via `hasRotatingPoint`)
  10776. * @type Number
  10777. * @default
  10778. */
  10779. rotatingPointOffset: 40,
  10780. /**
  10781. * When set to `true`, objects are "found" on canvas on per-pixel basis rather than according to bounding box
  10782. * @type Boolean
  10783. * @default
  10784. */
  10785. perPixelTargetFind: false,
  10786. /**
  10787. * When `false`, default object's values are not included in its serialization
  10788. * @type Boolean
  10789. * @default
  10790. */
  10791. includeDefaultValues: true,
  10792. /**
  10793. * Function that determines clipping of an object (context is passed as a first argument)
  10794. * Note that context origin is at the object's center point (not left/top corner)
  10795. * @type Function
  10796. */
  10797. clipTo: null,
  10798. /**
  10799. * When `true`, object horizontal movement is locked
  10800. * @type Boolean
  10801. * @default
  10802. */
  10803. lockMovementX: false,
  10804. /**
  10805. * When `true`, object vertical movement is locked
  10806. * @type Boolean
  10807. * @default
  10808. */
  10809. lockMovementY: false,
  10810. /**
  10811. * When `true`, object rotation is locked
  10812. * @type Boolean
  10813. * @default
  10814. */
  10815. lockRotation: false,
  10816. /**
  10817. * When `true`, object horizontal scaling is locked
  10818. * @type Boolean
  10819. * @default
  10820. */
  10821. lockScalingX: false,
  10822. /**
  10823. * When `true`, object vertical scaling is locked
  10824. * @type Boolean
  10825. * @default
  10826. */
  10827. lockScalingY: false,
  10828. /**
  10829. * When `true`, object non-uniform scaling is locked
  10830. * @type Boolean
  10831. * @default
  10832. */
  10833. lockUniScaling: false,
  10834. /**
  10835. * When `true`, object horizontal skewing is locked
  10836. * @type Boolean
  10837. * @default
  10838. */
  10839. lockSkewingX: false,
  10840. /**
  10841. * When `true`, object vertical skewing is locked
  10842. * @type Boolean
  10843. * @default
  10844. */
  10845. lockSkewingY: false,
  10846. /**
  10847. * When `true`, object cannot be flipped by scaling into negative values
  10848. * @type Boolean
  10849. * @default
  10850. */
  10851. lockScalingFlip: false,
  10852. /**
  10853. * When `true`, object is not exported in SVG or OBJECT/JSON
  10854. * since 1.6.3
  10855. * @type Boolean
  10856. * @default
  10857. */
  10858. excludeFromExport: false,
  10859. /**
  10860. * When `true`, object is cached on an additional canvas.
  10861. * default to true
  10862. * since 1.7.0
  10863. * @type Boolean
  10864. * @default true
  10865. */
  10866. objectCaching: objectCaching,
  10867. /**
  10868. * When `true`, object properties are checked for cache invalidation. In some particular
  10869. * situation you may want this to be disabled ( spray brush, very big pathgroups, groups)
  10870. * or if your application does not allow you to modify properties for groups child you want
  10871. * to disable it for groups.
  10872. * default to false
  10873. * since 1.7.0
  10874. * @type Boolean
  10875. * @default false
  10876. */
  10877. statefullCache: false,
  10878. /**
  10879. * When `true`, cache does not get updated during scaling. The picture will get blocky if scaled
  10880. * too much and will be redrawn with correct details at the end of scaling.
  10881. * this setting is performance and application dependant.
  10882. * default to true
  10883. * since 1.7.0
  10884. * @type Boolean
  10885. * @default true
  10886. */
  10887. noScaleCache: true,
  10888. /**
  10889. * When set to `true`, object's cache will be rerendered next render call.
  10890. * since 1.7.0
  10891. * @type Boolean
  10892. * @default false
  10893. */
  10894. dirty: false,
  10895. /**
  10896. * When set to `true`, force the object to have its own cache, even if it is inside a group
  10897. * it may be needed when your object behave in a particular way on the cache and always needs
  10898. * its own isolated canvas to render correctly.
  10899. * since 1.7.5
  10900. * @type Boolean
  10901. * @default false
  10902. */
  10903. needsItsOwnCache: false,
  10904. /**
  10905. * List of properties to consider when checking if state
  10906. * of an object is changed (fabric.Object#hasStateChanged)
  10907. * as well as for history (undo/redo) purposes
  10908. * @type Array
  10909. */
  10910. stateProperties: (
  10911. 'top left width height scaleX scaleY flipX flipY originX originY transformMatrix ' +
  10912. 'stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit ' +
  10913. 'angle opacity fill fillRule globalCompositeOperation shadow clipTo visible backgroundColor ' +
  10914. 'skewX skewY'
  10915. ).split(' '),
  10916. /**
  10917. * List of properties to consider when checking if cache needs refresh
  10918. * @type Array
  10919. */
  10920. cacheProperties: (
  10921. 'fill stroke strokeWidth strokeDashArray width height stroke strokeWidth strokeDashArray' +
  10922. ' strokeLineCap strokeLineJoin strokeMiterLimit fillRule backgroundColor'
  10923. ).split(' '),
  10924. /**
  10925. * Constructor
  10926. * @param {Object} [options] Options object
  10927. */
  10928. initialize: function(options) {
  10929. options = options || { };
  10930. if (options) {
  10931. this.setOptions(options);
  10932. }
  10933. if (this.objectCaching) {
  10934. this._createCacheCanvas();
  10935. this.setupState({ propertySet: 'cacheProperties' });
  10936. }
  10937. },
  10938. /**
  10939. * Create a the canvas used to keep the cached copy of the object
  10940. * @private
  10941. */
  10942. _createCacheCanvas: function() {
  10943. this._cacheCanvas = fabric.document.createElement('canvas');
  10944. this._cacheContext = this._cacheCanvas.getContext('2d');
  10945. this._updateCacheCanvas();
  10946. },
  10947. /**
  10948. * Return the dimension and the zoom level needed to create a cache canvas
  10949. * big enough to host the object to be cached.
  10950. * @private
  10951. * @return {Object}.width width of canvas
  10952. * @return {Object}.height height of canvas
  10953. * @return {Object}.zoomX zoomX zoom value to unscale the canvas before drawing cache
  10954. * @return {Object}.zoomY zoomY zoom value to unscale the canvas before drawing cache
  10955. */
  10956. _getCacheCanvasDimensions: function() {
  10957. var zoom = this.canvas && this.canvas.getZoom() || 1,
  10958. objectScale = this.getObjectScaling(),
  10959. dim = this._getNonTransformedDimensions(),
  10960. retina = this.canvas && this.canvas._isRetinaScaling() ? fabric.devicePixelRatio : 1,
  10961. zoomX = objectScale.scaleX * zoom * retina,
  10962. zoomY = objectScale.scaleY * zoom * retina,
  10963. width = dim.x * zoomX,
  10964. height = dim.y * zoomY;
  10965. return {
  10966. width: width + 2,
  10967. height: height + 2,
  10968. zoomX: zoomX,
  10969. zoomY: zoomY
  10970. };
  10971. },
  10972. /**
  10973. * Update width and height of the canvas for cache
  10974. * returns true or false if canvas needed resize.
  10975. * @private
  10976. * @return {Boolean} true if the canvas has been resized
  10977. */
  10978. _updateCacheCanvas: function() {
  10979. if (this.noScaleCache && this.canvas && this.canvas._currentTransform) {
  10980. var action = this.canvas._currentTransform.action;
  10981. if (action.slice(0, 5) === 'scale') {
  10982. return false;
  10983. }
  10984. }
  10985. var dims = this._getCacheCanvasDimensions(),
  10986. width = dims.width, height = dims.height,
  10987. zoomX = dims.zoomX, zoomY = dims.zoomY;
  10988. if (width !== this.cacheWidth || height !== this.cacheHeight) {
  10989. this._cacheCanvas.width = Math.ceil(width);
  10990. this._cacheCanvas.height = Math.ceil(height);
  10991. this._cacheContext.translate(width / 2, height / 2);
  10992. this._cacheContext.scale(zoomX, zoomY);
  10993. this.cacheWidth = width;
  10994. this.cacheHeight = height;
  10995. this.zoomX = zoomX;
  10996. this.zoomY = zoomY;
  10997. return true;
  10998. }
  10999. return false;
  11000. },
  11001. /**
  11002. * Sets object's properties from options
  11003. * @param {Object} [options] Options object
  11004. */
  11005. setOptions: function(options) {
  11006. this._setOptions(options);
  11007. this._initGradient(options.fill, 'fill');
  11008. this._initGradient(options.stroke, 'stroke');
  11009. this._initClipping(options);
  11010. this._initPattern(options.fill, 'fill');
  11011. this._initPattern(options.stroke, 'stroke');
  11012. },
  11013. /**
  11014. * Transforms context when rendering an object
  11015. * @param {CanvasRenderingContext2D} ctx Context
  11016. * @param {Boolean} fromLeft When true, context is transformed to object's top/left corner. This is used when rendering text on Node
  11017. */
  11018. transform: function(ctx, fromLeft) {
  11019. if (this.group && !this.group._transformDone && this.group === this.canvas._activeGroup) {
  11020. this.group.transform(ctx);
  11021. }
  11022. var center = fromLeft ? this._getLeftTopCoords() : this.getCenterPoint();
  11023. ctx.translate(center.x, center.y);
  11024. this.angle && ctx.rotate(degreesToRadians(this.angle));
  11025. ctx.scale(
  11026. this.scaleX * (this.flipX ? -1 : 1),
  11027. this.scaleY * (this.flipY ? -1 : 1)
  11028. );
  11029. this.skewX && ctx.transform(1, 0, Math.tan(degreesToRadians(this.skewX)), 1, 0, 0);
  11030. this.skewY && ctx.transform(1, Math.tan(degreesToRadians(this.skewY)), 0, 1, 0, 0);
  11031. },
  11032. /**
  11033. * Returns an object representation of an instance
  11034. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  11035. * @return {Object} Object representation of an instance
  11036. */
  11037. toObject: function(propertiesToInclude) {
  11038. var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS,
  11039. object = {
  11040. type: this.type,
  11041. originX: this.originX,
  11042. originY: this.originY,
  11043. left: toFixed(this.left, NUM_FRACTION_DIGITS),
  11044. top: toFixed(this.top, NUM_FRACTION_DIGITS),
  11045. width: toFixed(this.width, NUM_FRACTION_DIGITS),
  11046. height: toFixed(this.height, NUM_FRACTION_DIGITS),
  11047. fill: (this.fill && this.fill.toObject) ? this.fill.toObject() : this.fill,
  11048. stroke: (this.stroke && this.stroke.toObject) ? this.stroke.toObject() : this.stroke,
  11049. strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS),
  11050. strokeDashArray: this.strokeDashArray ? this.strokeDashArray.concat() : this.strokeDashArray,
  11051. strokeLineCap: this.strokeLineCap,
  11052. strokeLineJoin: this.strokeLineJoin,
  11053. strokeMiterLimit: toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS),
  11054. scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS),
  11055. scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS),
  11056. angle: toFixed(this.getAngle(), NUM_FRACTION_DIGITS),
  11057. flipX: this.flipX,
  11058. flipY: this.flipY,
  11059. opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS),
  11060. shadow: (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow,
  11061. visible: this.visible,
  11062. clipTo: this.clipTo && String(this.clipTo),
  11063. backgroundColor: this.backgroundColor,
  11064. fillRule: this.fillRule,
  11065. globalCompositeOperation: this.globalCompositeOperation,
  11066. transformMatrix: this.transformMatrix ? this.transformMatrix.concat() : null,
  11067. skewX: toFixed(this.skewX, NUM_FRACTION_DIGITS),
  11068. skewY: toFixed(this.skewY, NUM_FRACTION_DIGITS)
  11069. };
  11070. fabric.util.populateWithProperties(this, object, propertiesToInclude);
  11071. if (!this.includeDefaultValues) {
  11072. object = this._removeDefaultValues(object);
  11073. }
  11074. return object;
  11075. },
  11076. /**
  11077. * Returns (dataless) object representation of an instance
  11078. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  11079. * @return {Object} Object representation of an instance
  11080. */
  11081. toDatalessObject: function(propertiesToInclude) {
  11082. // will be overwritten by subclasses
  11083. return this.toObject(propertiesToInclude);
  11084. },
  11085. /**
  11086. * @private
  11087. * @param {Object} object
  11088. */
  11089. _removeDefaultValues: function(object) {
  11090. var prototype = fabric.util.getKlass(object.type).prototype,
  11091. stateProperties = prototype.stateProperties;
  11092. stateProperties.forEach(function(prop) {
  11093. if (object[prop] === prototype[prop]) {
  11094. delete object[prop];
  11095. }
  11096. var isArray = Object.prototype.toString.call(object[prop]) === '[object Array]' &&
  11097. Object.prototype.toString.call(prototype[prop]) === '[object Array]';
  11098. // basically a check for [] === []
  11099. if (isArray && object[prop].length === 0 && prototype[prop].length === 0) {
  11100. delete object[prop];
  11101. }
  11102. });
  11103. return object;
  11104. },
  11105. /**
  11106. * Returns a string representation of an instance
  11107. * @return {String}
  11108. */
  11109. toString: function() {
  11110. return '#<fabric.' + capitalize(this.type) + '>';
  11111. },
  11112. /**
  11113. * Return the object scale factor counting also the group scaling
  11114. * @return {Object} object with scaleX and scaleY properties
  11115. */
  11116. getObjectScaling: function() {
  11117. var scaleX = this.scaleX, scaleY = this.scaleY;
  11118. if (this.group) {
  11119. var scaling = this.group.getObjectScaling();
  11120. scaleX *= scaling.scaleX;
  11121. scaleY *= scaling.scaleY;
  11122. }
  11123. return { scaleX: scaleX, scaleY: scaleY };
  11124. },
  11125. /**
  11126. * @private
  11127. * @param {String} key
  11128. * @param {*} value
  11129. * @return {fabric.Object} thisArg
  11130. */
  11131. _set: function(key, value) {
  11132. var shouldConstrainValue = (key === 'scaleX' || key === 'scaleY');
  11133. if (shouldConstrainValue) {
  11134. value = this._constrainScale(value);
  11135. }
  11136. if (key === 'scaleX' && value < 0) {
  11137. this.flipX = !this.flipX;
  11138. value *= -1;
  11139. }
  11140. else if (key === 'scaleY' && value < 0) {
  11141. this.flipY = !this.flipY;
  11142. value *= -1;
  11143. }
  11144. else if (key === 'shadow' && value && !(value instanceof fabric.Shadow)) {
  11145. value = new fabric.Shadow(value);
  11146. }
  11147. else if (key === 'dirty' && this.group) {
  11148. this.group.set('dirty', value);
  11149. }
  11150. this[key] = value;
  11151. if (this.cacheProperties.indexOf(key) > -1) {
  11152. if (this.group) {
  11153. this.group.set('dirty', true);
  11154. }
  11155. this.dirty = true;
  11156. }
  11157. if (this.group && this.stateProperties.indexOf(key) > -1) {
  11158. this.group.set('dirty', true);
  11159. }
  11160. if (key === 'width' || key === 'height') {
  11161. this.minScaleLimit = Math.min(0.1, 1 / Math.max(this.width, this.height));
  11162. }
  11163. return this;
  11164. },
  11165. /**
  11166. * This callback function is called by the parent group of an object every
  11167. * time a non-delegated property changes on the group. It is passed the key
  11168. * and value as parameters. Not adding in this function's signature to avoid
  11169. * Travis build error about unused variables.
  11170. */
  11171. setOnGroup: function() {
  11172. // implemented by sub-classes, as needed.
  11173. },
  11174. /**
  11175. * Sets sourcePath of an object
  11176. * @param {String} value Value to set sourcePath to
  11177. * @return {fabric.Object} thisArg
  11178. * @chainable
  11179. */
  11180. setSourcePath: function(value) {
  11181. this.sourcePath = value;
  11182. return this;
  11183. },
  11184. /**
  11185. * Retrieves viewportTransform from Object's canvas if possible
  11186. * @method getViewportTransform
  11187. * @memberOf fabric.Object.prototype
  11188. * @return {Boolean} flipY value // TODO
  11189. */
  11190. getViewportTransform: function() {
  11191. if (this.canvas && this.canvas.viewportTransform) {
  11192. return this.canvas.viewportTransform;
  11193. }
  11194. return fabric.iMatrix.concat();
  11195. },
  11196. /**
  11197. * Renders an object on a specified context
  11198. * @param {CanvasRenderingContext2D} ctx Context to render on
  11199. * @param {Boolean} [noTransform] When true, context is not transformed
  11200. */
  11201. render: function(ctx, noTransform) {
  11202. // do not render if width/height are zeros or object is not visible
  11203. if ((this.width === 0 && this.height === 0) || !this.visible) {
  11204. return;
  11205. }
  11206. if (this.canvas && this.canvas.skipOffscreen && !this.group && !this.isOnScreen()) {
  11207. return;
  11208. }
  11209. ctx.save();
  11210. //setup fill rule for current object
  11211. this._setupCompositeOperation(ctx);
  11212. this.drawSelectionBackground(ctx);
  11213. if (!noTransform) {
  11214. this.transform(ctx);
  11215. }
  11216. this._setOpacity(ctx);
  11217. this._setShadow(ctx);
  11218. if (this.transformMatrix) {
  11219. ctx.transform.apply(ctx, this.transformMatrix);
  11220. }
  11221. this.clipTo && fabric.util.clipContext(this, ctx);
  11222. if (this.objectCaching && (!this.group || this.needsItsOwnCache)) {
  11223. if (!this._cacheCanvas) {
  11224. this._createCacheCanvas();
  11225. }
  11226. if (this.isCacheDirty(noTransform)) {
  11227. this.statefullCache && this.saveState({ propertySet: 'cacheProperties' });
  11228. this.drawObject(this._cacheContext, noTransform);
  11229. this.dirty = false;
  11230. }
  11231. this.drawCacheOnCanvas(ctx);
  11232. }
  11233. else {
  11234. this.drawObject(ctx, noTransform);
  11235. if (noTransform && this.objectCaching && this.statefullCache) {
  11236. this.saveState({ propertySet: 'cacheProperties' });
  11237. }
  11238. }
  11239. this.clipTo && ctx.restore();
  11240. ctx.restore();
  11241. },
  11242. /**
  11243. * Execute the drawing operation for an object on a specified context
  11244. * @param {CanvasRenderingContext2D} ctx Context to render on
  11245. * @param {Boolean} [noTransform] When true, context is not transformed
  11246. */
  11247. drawObject: function(ctx, noTransform) {
  11248. this._renderBackground(ctx);
  11249. this._setStrokeStyles(ctx);
  11250. this._setFillStyles(ctx);
  11251. this._render(ctx, noTransform);
  11252. },
  11253. /**
  11254. * Paint the cached copy of the object on the target context.
  11255. * @param {CanvasRenderingContext2D} ctx Context to render on
  11256. */
  11257. drawCacheOnCanvas: function(ctx) {
  11258. ctx.scale(1 / this.zoomX, 1 / this.zoomY);
  11259. ctx.drawImage(this._cacheCanvas, -this.cacheWidth / 2, -this.cacheHeight / 2);
  11260. },
  11261. /**
  11262. * Check if cache is dirty
  11263. * @param {Boolean} skipCanvas skip canvas checks because this object is painted
  11264. * on parent canvas.
  11265. */
  11266. isCacheDirty: function(skipCanvas) {
  11267. if (!skipCanvas && this._updateCacheCanvas()) {
  11268. // in this case the context is already cleared.
  11269. return true;
  11270. }
  11271. else {
  11272. if (this.dirty || (this.statefullCache && this.hasStateChanged('cacheProperties'))) {
  11273. if (!skipCanvas) {
  11274. var width = this.cacheWidth / this.zoomX;
  11275. var height = this.cacheHeight / this.zoomY;
  11276. this._cacheContext.clearRect(-width / 2, -height / 2, width, height);
  11277. }
  11278. return true;
  11279. }
  11280. }
  11281. return false;
  11282. },
  11283. /**
  11284. * Draws a background for the object big as its untrasformed dimensions
  11285. * @private
  11286. * @param {CanvasRenderingContext2D} ctx Context to render on
  11287. */
  11288. _renderBackground: function(ctx) {
  11289. if (!this.backgroundColor) {
  11290. return;
  11291. }
  11292. var dim = this._getNonTransformedDimensions();
  11293. ctx.fillStyle = this.backgroundColor;
  11294. ctx.fillRect(
  11295. -dim.x / 2,
  11296. -dim.y / 2,
  11297. dim.x,
  11298. dim.y
  11299. );
  11300. // if there is background color no other shadows
  11301. // should be casted
  11302. this._removeShadow(ctx);
  11303. },
  11304. /**
  11305. * @private
  11306. * @param {CanvasRenderingContext2D} ctx Context to render on
  11307. */
  11308. _setOpacity: function(ctx) {
  11309. ctx.globalAlpha *= this.opacity;
  11310. },
  11311. _setStrokeStyles: function(ctx) {
  11312. if (this.stroke) {
  11313. ctx.lineWidth = this.strokeWidth;
  11314. ctx.lineCap = this.strokeLineCap;
  11315. ctx.lineJoin = this.strokeLineJoin;
  11316. ctx.miterLimit = this.strokeMiterLimit;
  11317. ctx.strokeStyle = this.stroke.toLive
  11318. ? this.stroke.toLive(ctx, this)
  11319. : this.stroke;
  11320. }
  11321. },
  11322. _setFillStyles: function(ctx) {
  11323. if (this.fill) {
  11324. ctx.fillStyle = this.fill.toLive
  11325. ? this.fill.toLive(ctx, this)
  11326. : this.fill;
  11327. }
  11328. },
  11329. /**
  11330. * @private
  11331. * Sets line dash
  11332. * @param {CanvasRenderingContext2D} ctx Context to set the dash line on
  11333. * @param {Array} dashArray array representing dashes
  11334. * @param {Function} alternative function to call if browaser does not support lineDash
  11335. */
  11336. _setLineDash: function(ctx, dashArray, alternative) {
  11337. if (!dashArray) {
  11338. return;
  11339. }
  11340. // Spec requires the concatenation of two copies the dash list when the number of elements is odd
  11341. if (1 & dashArray.length) {
  11342. dashArray.push.apply(dashArray, dashArray);
  11343. }
  11344. if (supportsLineDash) {
  11345. ctx.setLineDash(dashArray);
  11346. }
  11347. else {
  11348. alternative && alternative(ctx);
  11349. }
  11350. },
  11351. /**
  11352. * Renders controls and borders for the object
  11353. * @param {CanvasRenderingContext2D} ctx Context to render on
  11354. * @param {Boolean} [noTransform] When true, context is not transformed
  11355. */
  11356. _renderControls: function(ctx, noTransform) {
  11357. if (!this.active || noTransform
  11358. || (this.group && this.group !== this.canvas.getActiveGroup())) {
  11359. return;
  11360. }
  11361. var vpt = this.getViewportTransform(),
  11362. matrix = this.calcTransformMatrix(),
  11363. options;
  11364. matrix = fabric.util.multiplyTransformMatrices(vpt, matrix);
  11365. options = fabric.util.qrDecompose(matrix);
  11366. ctx.save();
  11367. ctx.translate(options.translateX, options.translateY);
  11368. ctx.lineWidth = 1 * this.borderScaleFactor;
  11369. if (!this.group) {
  11370. ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
  11371. }
  11372. if (this.group && this.group === this.canvas.getActiveGroup()) {
  11373. ctx.rotate(degreesToRadians(options.angle));
  11374. this.drawBordersInGroup(ctx, options);
  11375. }
  11376. else {
  11377. ctx.rotate(degreesToRadians(this.angle));
  11378. this.drawBorders(ctx);
  11379. }
  11380. this.drawControls(ctx);
  11381. ctx.restore();
  11382. },
  11383. /**
  11384. * @private
  11385. * @param {CanvasRenderingContext2D} ctx Context to render on
  11386. */
  11387. _setShadow: function(ctx) {
  11388. if (!this.shadow) {
  11389. return;
  11390. }
  11391. var multX = (this.canvas && this.canvas.viewportTransform[0]) || 1,
  11392. multY = (this.canvas && this.canvas.viewportTransform[3]) || 1,
  11393. scaling = this.getObjectScaling();
  11394. if (this.canvas && this.canvas._isRetinaScaling()) {
  11395. multX *= fabric.devicePixelRatio;
  11396. multY *= fabric.devicePixelRatio;
  11397. }
  11398. ctx.shadowColor = this.shadow.color;
  11399. ctx.shadowBlur = this.shadow.blur * (multX + multY) * (scaling.scaleX + scaling.scaleY) / 4;
  11400. ctx.shadowOffsetX = this.shadow.offsetX * multX * scaling.scaleX;
  11401. ctx.shadowOffsetY = this.shadow.offsetY * multY * scaling.scaleY;
  11402. },
  11403. /**
  11404. * @private
  11405. * @param {CanvasRenderingContext2D} ctx Context to render on
  11406. */
  11407. _removeShadow: function(ctx) {
  11408. if (!this.shadow) {
  11409. return;
  11410. }
  11411. ctx.shadowColor = '';
  11412. ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0;
  11413. },
  11414. /**
  11415. * @private
  11416. * @param {CanvasRenderingContext2D} ctx Context to render on
  11417. * @param {Object} filler fabric.Pattern or fabric.Gradient
  11418. */
  11419. _applyPatternGradientTransform: function(ctx, filler) {
  11420. if (!filler.toLive) {
  11421. return;
  11422. }
  11423. var transform = filler.gradientTransform || filler.patternTransform;
  11424. if (transform) {
  11425. ctx.transform.apply(ctx, transform);
  11426. }
  11427. var offsetX = -this.width / 2 + filler.offsetX || 0,
  11428. offsetY = -this.height / 2 + filler.offsetY || 0;
  11429. ctx.translate(offsetX, offsetY);
  11430. },
  11431. /**
  11432. * @private
  11433. * @param {CanvasRenderingContext2D} ctx Context to render on
  11434. */
  11435. _renderFill: function(ctx) {
  11436. if (!this.fill) {
  11437. return;
  11438. }
  11439. ctx.save();
  11440. this._applyPatternGradientTransform(ctx, this.fill);
  11441. if (this.fillRule === 'evenodd') {
  11442. ctx.fill('evenodd');
  11443. }
  11444. else {
  11445. ctx.fill();
  11446. }
  11447. ctx.restore();
  11448. },
  11449. /**
  11450. * @private
  11451. * @param {CanvasRenderingContext2D} ctx Context to render on
  11452. */
  11453. _renderStroke: function(ctx) {
  11454. if (!this.stroke || this.strokeWidth === 0) {
  11455. return;
  11456. }
  11457. if (this.shadow && !this.shadow.affectStroke) {
  11458. this._removeShadow(ctx);
  11459. }
  11460. ctx.save();
  11461. this._setLineDash(ctx, this.strokeDashArray, this._renderDashedStroke);
  11462. this._applyPatternGradientTransform(ctx, this.stroke);
  11463. ctx.stroke();
  11464. ctx.restore();
  11465. },
  11466. /**
  11467. * Clones an instance, some objects are async, so using callback method will work for every object.
  11468. * Using the direct return does not work for images and groups.
  11469. * @param {Function} callback Callback is invoked with a clone as a first argument
  11470. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  11471. * @return {fabric.Object} clone of an instance
  11472. */
  11473. clone: function(callback, propertiesToInclude) {
  11474. if (this.constructor.fromObject) {
  11475. return this.constructor.fromObject(this.toObject(propertiesToInclude), callback);
  11476. }
  11477. return new fabric.Object(this.toObject(propertiesToInclude));
  11478. },
  11479. /**
  11480. * Creates an instance of fabric.Image out of an object
  11481. * @param {Function} callback callback, invoked with an instance as a first argument
  11482. * @param {Object} [options] for clone as image, passed to toDataURL
  11483. * @param {Boolean} [options.enableRetinaScaling] enable retina scaling for the cloned image
  11484. * @return {fabric.Object} thisArg
  11485. */
  11486. cloneAsImage: function(callback, options) {
  11487. var dataUrl = this.toDataURL(options);
  11488. fabric.util.loadImage(dataUrl, function(img) {
  11489. if (callback) {
  11490. callback(new fabric.Image(img));
  11491. }
  11492. });
  11493. return this;
  11494. },
  11495. /**
  11496. * Converts an object into a data-url-like string
  11497. * @param {Object} options Options object
  11498. * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png"
  11499. * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg.
  11500. * @param {Number} [options.multiplier=1] Multiplier to scale by
  11501. * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14
  11502. * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14
  11503. * @param {Number} [options.width] Cropping width. Introduced in v1.2.14
  11504. * @param {Number} [options.height] Cropping height. Introduced in v1.2.14
  11505. * @param {Boolean} [options.enableRetina] Enable retina scaling for clone image. Introduce in 1.6.4
  11506. * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format
  11507. */
  11508. toDataURL: function(options) {
  11509. options || (options = { });
  11510. var el = fabric.util.createCanvasElement(),
  11511. boundingRect = this.getBoundingRect();
  11512. el.width = boundingRect.width;
  11513. el.height = boundingRect.height;
  11514. fabric.util.wrapElement(el, 'div');
  11515. var canvas = new fabric.StaticCanvas(el, { enableRetinaScaling: options.enableRetinaScaling });
  11516. // to avoid common confusion https://github.com/kangax/fabric.js/issues/806
  11517. if (options.format === 'jpg') {
  11518. options.format = 'jpeg';
  11519. }
  11520. if (options.format === 'jpeg') {
  11521. canvas.backgroundColor = '#fff';
  11522. }
  11523. var origParams = {
  11524. active: this.get('active'),
  11525. left: this.getLeft(),
  11526. top: this.getTop()
  11527. };
  11528. this.set('active', false);
  11529. this.setPositionByOrigin(new fabric.Point(canvas.getWidth() / 2, canvas.getHeight() / 2), 'center', 'center');
  11530. var originalCanvas = this.canvas;
  11531. canvas.add(this);
  11532. var data = canvas.toDataURL(options);
  11533. this.set(origParams).setCoords();
  11534. this.canvas = originalCanvas;
  11535. canvas.dispose();
  11536. canvas = null;
  11537. return data;
  11538. },
  11539. /**
  11540. * Returns true if specified type is identical to the type of an instance
  11541. * @param {String} type Type to check against
  11542. * @return {Boolean}
  11543. */
  11544. isType: function(type) {
  11545. return this.type === type;
  11546. },
  11547. /**
  11548. * Returns complexity of an instance
  11549. * @return {Number} complexity of this instance (is 1 unless subclassed)
  11550. */
  11551. complexity: function() {
  11552. return 1;
  11553. },
  11554. /**
  11555. * Returns a JSON representation of an instance
  11556. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  11557. * @return {Object} JSON
  11558. */
  11559. toJSON: function(propertiesToInclude) {
  11560. // delegate, not alias
  11561. return this.toObject(propertiesToInclude);
  11562. },
  11563. /**
  11564. * Sets gradient (fill or stroke) of an object
  11565. * <b>Backwards incompatibility note:</b> This method was named "setGradientFill" until v1.1.0
  11566. * @param {String} property Property name 'stroke' or 'fill'
  11567. * @param {Object} [options] Options object
  11568. * @param {String} [options.type] Type of gradient 'radial' or 'linear'
  11569. * @param {Number} [options.x1=0] x-coordinate of start point
  11570. * @param {Number} [options.y1=0] y-coordinate of start point
  11571. * @param {Number} [options.x2=0] x-coordinate of end point
  11572. * @param {Number} [options.y2=0] y-coordinate of end point
  11573. * @param {Number} [options.r1=0] Radius of start point (only for radial gradients)
  11574. * @param {Number} [options.r2=0] Radius of end point (only for radial gradients)
  11575. * @param {Object} [options.colorStops] Color stops object eg. {0: 'ff0000', 1: '000000'}
  11576. * @param {Object} [options.gradientTransform] transforMatrix for gradient
  11577. * @return {fabric.Object} thisArg
  11578. * @chainable
  11579. * @see {@link http://jsfiddle.net/fabricjs/58y8b/|jsFiddle demo}
  11580. * @example <caption>Set linear gradient</caption>
  11581. * object.setGradient('fill', {
  11582. * type: 'linear',
  11583. * x1: -object.width / 2,
  11584. * y1: 0,
  11585. * x2: object.width / 2,
  11586. * y2: 0,
  11587. * colorStops: {
  11588. * 0: 'red',
  11589. * 0.5: '#005555',
  11590. * 1: 'rgba(0,0,255,0.5)'
  11591. * }
  11592. * });
  11593. * canvas.renderAll();
  11594. * @example <caption>Set radial gradient</caption>
  11595. * object.setGradient('fill', {
  11596. * type: 'radial',
  11597. * x1: 0,
  11598. * y1: 0,
  11599. * x2: 0,
  11600. * y2: 0,
  11601. * r1: object.width / 2,
  11602. * r2: 10,
  11603. * colorStops: {
  11604. * 0: 'red',
  11605. * 0.5: '#005555',
  11606. * 1: 'rgba(0,0,255,0.5)'
  11607. * }
  11608. * });
  11609. * canvas.renderAll();
  11610. */
  11611. setGradient: function(property, options) {
  11612. options || (options = { });
  11613. var gradient = { colorStops: [] };
  11614. gradient.type = options.type || (options.r1 || options.r2 ? 'radial' : 'linear');
  11615. gradient.coords = {
  11616. x1: options.x1,
  11617. y1: options.y1,
  11618. x2: options.x2,
  11619. y2: options.y2
  11620. };
  11621. if (options.r1 || options.r2) {
  11622. gradient.coords.r1 = options.r1;
  11623. gradient.coords.r2 = options.r2;
  11624. }
  11625. gradient.gradientTransform = options.gradientTransform;
  11626. fabric.Gradient.prototype.addColorStop.call(gradient, options.colorStops);
  11627. return this.set(property, fabric.Gradient.forObject(this, gradient));
  11628. },
  11629. /**
  11630. * Sets pattern fill of an object
  11631. * @param {Object} options Options object
  11632. * @param {(String|HTMLImageElement)} options.source Pattern source
  11633. * @param {String} [options.repeat=repeat] Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat)
  11634. * @param {Number} [options.offsetX=0] Pattern horizontal offset from object's left/top corner
  11635. * @param {Number} [options.offsetY=0] Pattern vertical offset from object's left/top corner
  11636. * @return {fabric.Object} thisArg
  11637. * @chainable
  11638. * @see {@link http://jsfiddle.net/fabricjs/QT3pa/|jsFiddle demo}
  11639. * @example <caption>Set pattern</caption>
  11640. * fabric.util.loadImage('http://fabricjs.com/assets/escheresque_ste.png', function(img) {
  11641. * object.setPatternFill({
  11642. * source: img,
  11643. * repeat: 'repeat'
  11644. * });
  11645. * canvas.renderAll();
  11646. * });
  11647. */
  11648. setPatternFill: function(options) {
  11649. return this.set('fill', new fabric.Pattern(options));
  11650. },
  11651. /**
  11652. * Sets {@link fabric.Object#shadow|shadow} of an object
  11653. * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)")
  11654. * @param {String} [options.color=rgb(0,0,0)] Shadow color
  11655. * @param {Number} [options.blur=0] Shadow blur
  11656. * @param {Number} [options.offsetX=0] Shadow horizontal offset
  11657. * @param {Number} [options.offsetY=0] Shadow vertical offset
  11658. * @return {fabric.Object} thisArg
  11659. * @chainable
  11660. * @see {@link http://jsfiddle.net/fabricjs/7gvJG/|jsFiddle demo}
  11661. * @example <caption>Set shadow with string notation</caption>
  11662. * object.setShadow('2px 2px 10px rgba(0,0,0,0.2)');
  11663. * canvas.renderAll();
  11664. * @example <caption>Set shadow with object notation</caption>
  11665. * object.setShadow({
  11666. * color: 'red',
  11667. * blur: 10,
  11668. * offsetX: 20,
  11669. * offsetY: 20
  11670. * });
  11671. * canvas.renderAll();
  11672. */
  11673. setShadow: function(options) {
  11674. return this.set('shadow', options ? new fabric.Shadow(options) : null);
  11675. },
  11676. /**
  11677. * Sets "color" of an instance (alias of `set('fill', &hellip;)`)
  11678. * @param {String} color Color value
  11679. * @return {fabric.Object} thisArg
  11680. * @chainable
  11681. */
  11682. setColor: function(color) {
  11683. this.set('fill', color);
  11684. return this;
  11685. },
  11686. /**
  11687. * Sets "angle" of an instance
  11688. * @param {Number} angle Angle value (in degrees)
  11689. * @return {fabric.Object} thisArg
  11690. * @chainable
  11691. */
  11692. setAngle: function(angle) {
  11693. var shouldCenterOrigin = (this.originX !== 'center' || this.originY !== 'center') && this.centeredRotation;
  11694. if (shouldCenterOrigin) {
  11695. this._setOriginToCenter();
  11696. }
  11697. this.set('angle', angle);
  11698. if (shouldCenterOrigin) {
  11699. this._resetOrigin();
  11700. }
  11701. return this;
  11702. },
  11703. /**
  11704. * Centers object horizontally on canvas to which it was added last.
  11705. * You might need to call `setCoords` on an object after centering, to update controls area.
  11706. * @return {fabric.Object} thisArg
  11707. * @chainable
  11708. */
  11709. centerH: function () {
  11710. this.canvas && this.canvas.centerObjectH(this);
  11711. return this;
  11712. },
  11713. /**
  11714. * Centers object horizontally on current viewport of canvas to which it was added last.
  11715. * You might need to call `setCoords` on an object after centering, to update controls area.
  11716. * @return {fabric.Object} thisArg
  11717. * @chainable
  11718. */
  11719. viewportCenterH: function () {
  11720. this.canvas && this.canvas.viewportCenterObjectH(this);
  11721. return this;
  11722. },
  11723. /**
  11724. * Centers object vertically on canvas to which it was added last.
  11725. * You might need to call `setCoords` on an object after centering, to update controls area.
  11726. * @return {fabric.Object} thisArg
  11727. * @chainable
  11728. */
  11729. centerV: function () {
  11730. this.canvas && this.canvas.centerObjectV(this);
  11731. return this;
  11732. },
  11733. /**
  11734. * Centers object vertically on current viewport of canvas to which it was added last.
  11735. * You might need to call `setCoords` on an object after centering, to update controls area.
  11736. * @return {fabric.Object} thisArg
  11737. * @chainable
  11738. */
  11739. viewportCenterV: function () {
  11740. this.canvas && this.canvas.viewportCenterObjectV(this);
  11741. return this;
  11742. },
  11743. /**
  11744. * Centers object vertically and horizontally on canvas to which is was added last
  11745. * You might need to call `setCoords` on an object after centering, to update controls area.
  11746. * @return {fabric.Object} thisArg
  11747. * @chainable
  11748. */
  11749. center: function () {
  11750. this.canvas && this.canvas.centerObject(this);
  11751. return this;
  11752. },
  11753. /**
  11754. * Centers object on current viewport of canvas to which it was added last.
  11755. * You might need to call `setCoords` on an object after centering, to update controls area.
  11756. * @return {fabric.Object} thisArg
  11757. * @chainable
  11758. */
  11759. viewportCenter: function () {
  11760. this.canvas && this.canvas.viewportCenterObject(this);
  11761. return this;
  11762. },
  11763. /**
  11764. * Removes object from canvas to which it was added last
  11765. * @return {fabric.Object} thisArg
  11766. * @chainable
  11767. */
  11768. remove: function() {
  11769. this.canvas && this.canvas.remove(this);
  11770. return this;
  11771. },
  11772. /**
  11773. * Returns coordinates of a pointer relative to an object
  11774. * @param {Event} e Event to operate upon
  11775. * @param {Object} [pointer] Pointer to operate upon (instead of event)
  11776. * @return {Object} Coordinates of a pointer (x, y)
  11777. */
  11778. getLocalPointer: function(e, pointer) {
  11779. pointer = pointer || this.canvas.getPointer(e);
  11780. var pClicked = new fabric.Point(pointer.x, pointer.y),
  11781. objectLeftTop = this._getLeftTopCoords();
  11782. if (this.angle) {
  11783. pClicked = fabric.util.rotatePoint(
  11784. pClicked, objectLeftTop, degreesToRadians(-this.angle));
  11785. }
  11786. return {
  11787. x: pClicked.x - objectLeftTop.x,
  11788. y: pClicked.y - objectLeftTop.y
  11789. };
  11790. },
  11791. /**
  11792. * Sets canvas globalCompositeOperation for specific object
  11793. * custom composition operation for the particular object can be specifed using globalCompositeOperation property
  11794. * @param {CanvasRenderingContext2D} ctx Rendering canvas context
  11795. */
  11796. _setupCompositeOperation: function (ctx) {
  11797. if (this.globalCompositeOperation) {
  11798. ctx.globalCompositeOperation = this.globalCompositeOperation;
  11799. }
  11800. }
  11801. });
  11802. fabric.util.createAccessors(fabric.Object);
  11803. /**
  11804. * Alias for {@link fabric.Object.prototype.setAngle}
  11805. * @alias rotate -> setAngle
  11806. * @memberOf fabric.Object
  11807. */
  11808. fabric.Object.prototype.rotate = fabric.Object.prototype.setAngle;
  11809. extend(fabric.Object.prototype, fabric.Observable);
  11810. /**
  11811. * Defines the number of fraction digits to use when serializing object values.
  11812. * You can use it to increase/decrease precision of such values like left, top, scaleX, scaleY, etc.
  11813. * @static
  11814. * @memberOf fabric.Object
  11815. * @constant
  11816. * @type Number
  11817. */
  11818. fabric.Object.NUM_FRACTION_DIGITS = 2;
  11819. fabric.Object._fromObject = function(className, object, callback, forceAsync, extraParam) {
  11820. var klass = fabric[className];
  11821. object = clone(object, true);
  11822. if (forceAsync) {
  11823. fabric.util.enlivenPatterns([object.fill, object.stroke], function(patterns) {
  11824. if (typeof patterns[0] !== 'undefined') {
  11825. object.fill = patterns[0];
  11826. }
  11827. if (typeof patterns[1] !== 'undefined') {
  11828. object.stroke = patterns[1];
  11829. }
  11830. var instance = extraParam ? new klass(object[extraParam], object) : new klass(object);
  11831. callback && callback(instance);
  11832. });
  11833. }
  11834. else {
  11835. var instance = extraParam ? new klass(object[extraParam], object) : new klass(object);
  11836. callback && callback(instance);
  11837. return instance;
  11838. }
  11839. };
  11840. /**
  11841. * Unique id used internally when creating SVG elements
  11842. * @static
  11843. * @memberOf fabric.Object
  11844. * @type Number
  11845. */
  11846. fabric.Object.__uid = 0;
  11847. })(typeof exports !== 'undefined' ? exports : this);
  11848. (function() {
  11849. var degreesToRadians = fabric.util.degreesToRadians,
  11850. originXOffset = {
  11851. left: -0.5,
  11852. center: 0,
  11853. right: 0.5
  11854. },
  11855. originYOffset = {
  11856. top: -0.5,
  11857. center: 0,
  11858. bottom: 0.5
  11859. };
  11860. fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
  11861. /**
  11862. * Translates the coordinates from origin to center coordinates (based on the object's dimensions)
  11863. * @param {fabric.Point} point The point which corresponds to the originX and originY params
  11864. * @param {String} fromOriginX Horizontal origin: 'left', 'center' or 'right'
  11865. * @param {String} fromOriginY Vertical origin: 'top', 'center' or 'bottom'
  11866. * @param {String} toOriginX Horizontal origin: 'left', 'center' or 'right'
  11867. * @param {String} toOriginY Vertical origin: 'top', 'center' or 'bottom'
  11868. * @return {fabric.Point}
  11869. */
  11870. translateToGivenOrigin: function(point, fromOriginX, fromOriginY, toOriginX, toOriginY) {
  11871. var x = point.x,
  11872. y = point.y,
  11873. offsetX, offsetY, dim;
  11874. if (typeof fromOriginX === 'string') {
  11875. fromOriginX = originXOffset[fromOriginX];
  11876. }
  11877. else {
  11878. fromOriginX -= 0.5;
  11879. }
  11880. if (typeof toOriginX === 'string') {
  11881. toOriginX = originXOffset[toOriginX];
  11882. }
  11883. else {
  11884. toOriginX -= 0.5;
  11885. }
  11886. offsetX = toOriginX - fromOriginX;
  11887. if (typeof fromOriginY === 'string') {
  11888. fromOriginY = originYOffset[fromOriginY];
  11889. }
  11890. else {
  11891. fromOriginY -= 0.5;
  11892. }
  11893. if (typeof toOriginY === 'string') {
  11894. toOriginY = originYOffset[toOriginY];
  11895. }
  11896. else {
  11897. toOriginY -= 0.5;
  11898. }
  11899. offsetY = toOriginY - fromOriginY;
  11900. if (offsetX || offsetY) {
  11901. dim = this._getTransformedDimensions();
  11902. x = point.x + offsetX * dim.x;
  11903. y = point.y + offsetY * dim.y;
  11904. }
  11905. return new fabric.Point(x, y);
  11906. },
  11907. /**
  11908. * Translates the coordinates from origin to center coordinates (based on the object's dimensions)
  11909. * @param {fabric.Point} point The point which corresponds to the originX and originY params
  11910. * @param {String} originX Horizontal origin: 'left', 'center' or 'right'
  11911. * @param {String} originY Vertical origin: 'top', 'center' or 'bottom'
  11912. * @return {fabric.Point}
  11913. */
  11914. translateToCenterPoint: function(point, originX, originY) {
  11915. var p = this.translateToGivenOrigin(point, originX, originY, 'center', 'center');
  11916. if (this.angle) {
  11917. return fabric.util.rotatePoint(p, point, degreesToRadians(this.angle));
  11918. }
  11919. return p;
  11920. },
  11921. /**
  11922. * Translates the coordinates from center to origin coordinates (based on the object's dimensions)
  11923. * @param {fabric.Point} center The point which corresponds to center of the object
  11924. * @param {String} originX Horizontal origin: 'left', 'center' or 'right'
  11925. * @param {String} originY Vertical origin: 'top', 'center' or 'bottom'
  11926. * @return {fabric.Point}
  11927. */
  11928. translateToOriginPoint: function(center, originX, originY) {
  11929. var p = this.translateToGivenOrigin(center, 'center', 'center', originX, originY);
  11930. if (this.angle) {
  11931. return fabric.util.rotatePoint(p, center, degreesToRadians(this.angle));
  11932. }
  11933. return p;
  11934. },
  11935. /**
  11936. * Returns the real center coordinates of the object
  11937. * @return {fabric.Point}
  11938. */
  11939. getCenterPoint: function() {
  11940. var leftTop = new fabric.Point(this.left, this.top);
  11941. return this.translateToCenterPoint(leftTop, this.originX, this.originY);
  11942. },
  11943. /**
  11944. * Returns the coordinates of the object based on center coordinates
  11945. * @param {fabric.Point} point The point which corresponds to the originX and originY params
  11946. * @return {fabric.Point}
  11947. */
  11948. // getOriginPoint: function(center) {
  11949. // return this.translateToOriginPoint(center, this.originX, this.originY);
  11950. // },
  11951. /**
  11952. * Returns the coordinates of the object as if it has a different origin
  11953. * @param {String} originX Horizontal origin: 'left', 'center' or 'right'
  11954. * @param {String} originY Vertical origin: 'top', 'center' or 'bottom'
  11955. * @return {fabric.Point}
  11956. */
  11957. getPointByOrigin: function(originX, originY) {
  11958. var center = this.getCenterPoint();
  11959. return this.translateToOriginPoint(center, originX, originY);
  11960. },
  11961. /**
  11962. * Returns the point in local coordinates
  11963. * @param {fabric.Point} point The point relative to the global coordinate system
  11964. * @param {String} originX Horizontal origin: 'left', 'center' or 'right'
  11965. * @param {String} originY Vertical origin: 'top', 'center' or 'bottom'
  11966. * @return {fabric.Point}
  11967. */
  11968. toLocalPoint: function(point, originX, originY) {
  11969. var center = this.getCenterPoint(),
  11970. p, p2;
  11971. if (typeof originX !== 'undefined' && typeof originY !== 'undefined' ) {
  11972. p = this.translateToGivenOrigin(center, 'center', 'center', originX, originY);
  11973. }
  11974. else {
  11975. p = new fabric.Point(this.left, this.top);
  11976. }
  11977. p2 = new fabric.Point(point.x, point.y);
  11978. if (this.angle) {
  11979. p2 = fabric.util.rotatePoint(p2, center, -degreesToRadians(this.angle));
  11980. }
  11981. return p2.subtractEquals(p);
  11982. },
  11983. /**
  11984. * Returns the point in global coordinates
  11985. * @param {fabric.Point} The point relative to the local coordinate system
  11986. * @return {fabric.Point}
  11987. */
  11988. // toGlobalPoint: function(point) {
  11989. // return fabric.util.rotatePoint(point, this.getCenterPoint(), degreesToRadians(this.angle)).addEquals(new fabric.Point(this.left, this.top));
  11990. // },
  11991. /**
  11992. * Sets the position of the object taking into consideration the object's origin
  11993. * @param {fabric.Point} pos The new position of the object
  11994. * @param {String} originX Horizontal origin: 'left', 'center' or 'right'
  11995. * @param {String} originY Vertical origin: 'top', 'center' or 'bottom'
  11996. * @return {void}
  11997. */
  11998. setPositionByOrigin: function(pos, originX, originY) {
  11999. var center = this.translateToCenterPoint(pos, originX, originY),
  12000. position = this.translateToOriginPoint(center, this.originX, this.originY);
  12001. this.set('left', position.x);
  12002. this.set('top', position.y);
  12003. },
  12004. /**
  12005. * @param {String} to One of 'left', 'center', 'right'
  12006. */
  12007. adjustPosition: function(to) {
  12008. var angle = degreesToRadians(this.angle),
  12009. hypotFull = this.getWidth(),
  12010. xFull = Math.cos(angle) * hypotFull,
  12011. yFull = Math.sin(angle) * hypotFull,
  12012. offsetFrom, offsetTo;
  12013. //TODO: this function does not consider mixed situation like top, center.
  12014. if (typeof this.originX === 'string') {
  12015. offsetFrom = originXOffset[this.originX];
  12016. }
  12017. else {
  12018. offsetFrom = this.originX - 0.5;
  12019. }
  12020. if (typeof to === 'string') {
  12021. offsetTo = originXOffset[to];
  12022. }
  12023. else {
  12024. offsetTo = to - 0.5;
  12025. }
  12026. this.left += xFull * (offsetTo - offsetFrom);
  12027. this.top += yFull * (offsetTo - offsetFrom);
  12028. this.setCoords();
  12029. this.originX = to;
  12030. },
  12031. /**
  12032. * Sets the origin/position of the object to it's center point
  12033. * @private
  12034. * @return {void}
  12035. */
  12036. _setOriginToCenter: function() {
  12037. this._originalOriginX = this.originX;
  12038. this._originalOriginY = this.originY;
  12039. var center = this.getCenterPoint();
  12040. this.originX = 'center';
  12041. this.originY = 'center';
  12042. this.left = center.x;
  12043. this.top = center.y;
  12044. },
  12045. /**
  12046. * Resets the origin/position of the object to it's original origin
  12047. * @private
  12048. * @return {void}
  12049. */
  12050. _resetOrigin: function() {
  12051. var originPoint = this.translateToOriginPoint(
  12052. this.getCenterPoint(),
  12053. this._originalOriginX,
  12054. this._originalOriginY);
  12055. this.originX = this._originalOriginX;
  12056. this.originY = this._originalOriginY;
  12057. this.left = originPoint.x;
  12058. this.top = originPoint.y;
  12059. this._originalOriginX = null;
  12060. this._originalOriginY = null;
  12061. },
  12062. /**
  12063. * @private
  12064. */
  12065. _getLeftTopCoords: function() {
  12066. return this.translateToOriginPoint(this.getCenterPoint(), 'left', 'top');
  12067. }
  12068. });
  12069. })();
  12070. (function() {
  12071. function getCoords(coords) {
  12072. return [
  12073. new fabric.Point(coords.tl.x, coords.tl.y),
  12074. new fabric.Point(coords.tr.x, coords.tr.y),
  12075. new fabric.Point(coords.br.x, coords.br.y),
  12076. new fabric.Point(coords.bl.x, coords.bl.y)
  12077. ];
  12078. }
  12079. var degreesToRadians = fabric.util.degreesToRadians,
  12080. multiplyMatrices = fabric.util.multiplyTransformMatrices;
  12081. fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
  12082. /**
  12083. * Describe object's corner position in canvas element coordinates.
  12084. * properties are tl,mt,tr,ml,mr,bl,mb,br,mtr for the main controls.
  12085. * each property is an object with x, y and corner.
  12086. * The `corner` property contains in a similar manner the 4 points of the
  12087. * interactive area of the corner.
  12088. * The coordinates depends from this properties: width, height, scaleX, scaleY
  12089. * skewX, skewY, angle, strokeWidth, viewportTransform, top, left, padding.
  12090. * The coordinates get updated with @method setCoords.
  12091. * You can calculate them without updating with @method calcCoords;
  12092. * @memberOf fabric.Object.prototype
  12093. */
  12094. oCoords: null,
  12095. /**
  12096. * Describe object's corner position in canvas object absolute coordinates
  12097. * properties are tl,tr,bl,br and describe the four main corner.
  12098. * each property is an object with x, y, instance of Fabric.Point.
  12099. * The coordinates depends from this properties: width, height, scaleX, scaleY
  12100. * skewX, skewY, angle, strokeWidth, top, left.
  12101. * Those coordinates are usefull to understand where an object is. They get updated
  12102. * with oCoords but they do not need to be updated when zoom or panning change.
  12103. * The coordinates get updated with @method setCoords.
  12104. * You can calculate them without updating with @method calcCoords(true);
  12105. * @memberOf fabric.Object.prototype
  12106. */
  12107. aCoords: null,
  12108. /**
  12109. * return correct set of coordinates for intersection
  12110. */
  12111. getCoords: function(absolute, calculate) {
  12112. if (!this.oCoords) {
  12113. this.setCoords();
  12114. }
  12115. var coords = absolute ? this.aCoords : this.oCoords;
  12116. return getCoords(calculate ? this.calcCoords(absolute) : coords);
  12117. },
  12118. /**
  12119. * Checks if object intersects with an area formed by 2 points
  12120. * @param {Object} pointTL top-left point of area
  12121. * @param {Object} pointBR bottom-right point of area
  12122. * @param {Boolean} [absolute] use coordinates without viewportTransform
  12123. * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords
  12124. * @return {Boolean} true if object intersects with an area formed by 2 points
  12125. */
  12126. intersectsWithRect: function(pointTL, pointBR, absolute, calculate) {
  12127. var coords = this.getCoords(absolute, calculate),
  12128. intersection = fabric.Intersection.intersectPolygonRectangle(
  12129. coords,
  12130. pointTL,
  12131. pointBR
  12132. );
  12133. return intersection.status === 'Intersection';
  12134. },
  12135. /**
  12136. * Checks if object intersects with another object
  12137. * @param {Object} other Object to test
  12138. * @param {Boolean} [absolute] use coordinates without viewportTransform
  12139. * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords
  12140. * @return {Boolean} true if object intersects with another object
  12141. */
  12142. intersectsWithObject: function(other, absolute, calculate) {
  12143. var intersection = fabric.Intersection.intersectPolygonPolygon(
  12144. this.getCoords(absolute, calculate),
  12145. other.getCoords(absolute, calculate)
  12146. );
  12147. return intersection.status === 'Intersection'
  12148. || other.isContainedWithinObject(this, absolute, calculate)
  12149. || this.isContainedWithinObject(other, absolute, calculate);
  12150. },
  12151. /**
  12152. * Checks if object is fully contained within area of another object
  12153. * @param {Object} other Object to test
  12154. * @param {Boolean} [absolute] use coordinates without viewportTransform
  12155. * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords
  12156. * @return {Boolean} true if object is fully contained within area of another object
  12157. */
  12158. isContainedWithinObject: function(other, absolute, calculate) {
  12159. var points = this.getCoords(absolute, calculate),
  12160. i = 0, lines = other._getImageLines(
  12161. calculate ? other.calcCoords(absolute) : absolute ? other.aCoords : other.oCoords
  12162. );
  12163. for (; i < 4; i++) {
  12164. if (!other.containsPoint(points[i], lines)) {
  12165. return false;
  12166. }
  12167. }
  12168. return true;
  12169. },
  12170. /**
  12171. * Checks if object is fully contained within area formed by 2 points
  12172. * @param {Object} pointTL top-left point of area
  12173. * @param {Object} pointBR bottom-right point of area
  12174. * @param {Boolean} [absolute] use coordinates without viewportTransform
  12175. * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords
  12176. * @return {Boolean} true if object is fully contained within area formed by 2 points
  12177. */
  12178. isContainedWithinRect: function(pointTL, pointBR, absolute, calculate) {
  12179. var boundingRect = this.getBoundingRect(absolute, calculate);
  12180. return (
  12181. boundingRect.left >= pointTL.x &&
  12182. boundingRect.left + boundingRect.width <= pointBR.x &&
  12183. boundingRect.top >= pointTL.y &&
  12184. boundingRect.top + boundingRect.height <= pointBR.y
  12185. );
  12186. },
  12187. /**
  12188. * Checks if point is inside the object
  12189. * @param {fabric.Point} point Point to check against
  12190. * @param {Object} [lines] object returned from @method _getImageLines
  12191. * @param {Boolean} [absolute] use coordinates without viewportTransform
  12192. * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords
  12193. * @return {Boolean} true if point is inside the object
  12194. */
  12195. containsPoint: function(point, lines, absolute, calculate) {
  12196. var lines = lines || this._getImageLines(
  12197. calculate ? this.calcCoords(absolute) : absolute ? this.aCoords : this.oCoords
  12198. ),
  12199. xPoints = this._findCrossPoints(point, lines);
  12200. // if xPoints is odd then point is inside the object
  12201. return (xPoints !== 0 && xPoints % 2 === 1);
  12202. },
  12203. /**
  12204. * Checks if object is contained within the canvas with current viewportTransform
  12205. * the check is done stopping at first point that appear on screen
  12206. * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords
  12207. * @return {Boolean} true if object is fully contained within canvas
  12208. */
  12209. isOnScreen: function(calculate) {
  12210. if (!this.canvas) {
  12211. return false;
  12212. }
  12213. var pointTL = this.canvas.vptCoords.tl, pointBR = this.canvas.vptCoords.br;
  12214. var points = this.getCoords(true, calculate), point;
  12215. for (var i = 0; i < 4; i++) {
  12216. point = points[i];
  12217. if (point.x <= pointBR.x && point.x >= pointTL.x && point.y <= pointBR.y && point.y >= pointTL.y) {
  12218. return true;
  12219. }
  12220. }
  12221. // no points on screen, check intersection with absolute coordinates
  12222. if (this.intersectsWithRect(pointTL, pointBR, true)) {
  12223. return true;
  12224. }
  12225. // worst case scenario the object is so big that contanins the screen
  12226. var centerPoint = { x: (pointTL.x + pointBR.x) / 2, y: (pointTL.y + pointBR.y) / 2 };
  12227. if (this.containsPoint(centerPoint, null, true)) {
  12228. return true;
  12229. }
  12230. return false;
  12231. },
  12232. /**
  12233. * Method that returns an object with the object edges in it, given the coordinates of the corners
  12234. * @private
  12235. * @param {Object} oCoords Coordinates of the object corners
  12236. */
  12237. _getImageLines: function(oCoords) {
  12238. return {
  12239. topline: {
  12240. o: oCoords.tl,
  12241. d: oCoords.tr
  12242. },
  12243. rightline: {
  12244. o: oCoords.tr,
  12245. d: oCoords.br
  12246. },
  12247. bottomline: {
  12248. o: oCoords.br,
  12249. d: oCoords.bl
  12250. },
  12251. leftline: {
  12252. o: oCoords.bl,
  12253. d: oCoords.tl
  12254. }
  12255. };
  12256. },
  12257. /**
  12258. * Helper method to determine how many cross points are between the 4 object edges
  12259. * and the horizontal line determined by a point on canvas
  12260. * @private
  12261. * @param {fabric.Point} point Point to check
  12262. * @param {Object} lines Coordinates of the object being evaluated
  12263. */
  12264. // remove yi, not used but left code here just in case.
  12265. _findCrossPoints: function(point, lines) {
  12266. var b1, b2, a1, a2, xi, // yi,
  12267. xcount = 0,
  12268. iLine;
  12269. for (var lineKey in lines) {
  12270. iLine = lines[lineKey];
  12271. // optimisation 1: line below point. no cross
  12272. if ((iLine.o.y < point.y) && (iLine.d.y < point.y)) {
  12273. continue;
  12274. }
  12275. // optimisation 2: line above point. no cross
  12276. if ((iLine.o.y >= point.y) && (iLine.d.y >= point.y)) {
  12277. continue;
  12278. }
  12279. // optimisation 3: vertical line case
  12280. if ((iLine.o.x === iLine.d.x) && (iLine.o.x >= point.x)) {
  12281. xi = iLine.o.x;
  12282. // yi = point.y;
  12283. }
  12284. // calculate the intersection point
  12285. else {
  12286. b1 = 0;
  12287. b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x);
  12288. a1 = point.y - b1 * point.x;
  12289. a2 = iLine.o.y - b2 * iLine.o.x;
  12290. xi = -(a1 - a2) / (b1 - b2);
  12291. // yi = a1 + b1 * xi;
  12292. }
  12293. // dont count xi < point.x cases
  12294. if (xi >= point.x) {
  12295. xcount += 1;
  12296. }
  12297. // optimisation 4: specific for square images
  12298. if (xcount === 2) {
  12299. break;
  12300. }
  12301. }
  12302. return xcount;
  12303. },
  12304. /**
  12305. * Returns width of an object's bounding rectangle
  12306. * @deprecated since 1.0.4
  12307. * @return {Number} width value
  12308. */
  12309. getBoundingRectWidth: function() {
  12310. return this.getBoundingRect().width;
  12311. },
  12312. /**
  12313. * Returns height of an object's bounding rectangle
  12314. * @deprecated since 1.0.4
  12315. * @return {Number} height value
  12316. */
  12317. getBoundingRectHeight: function() {
  12318. return this.getBoundingRect().height;
  12319. },
  12320. /**
  12321. * Returns coordinates of object's bounding rectangle (left, top, width, height)
  12322. * the box is intented as aligned to axis of canvas.
  12323. * @param {Boolean} [absolute] use coordinates without viewportTransform
  12324. * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords
  12325. * @return {Object} Object with left, top, width, height properties
  12326. */
  12327. getBoundingRect: function(absolute, calculate) {
  12328. var coords = this.getCoords(absolute, calculate);
  12329. return fabric.util.makeBoundingBoxFromPoints(coords);
  12330. },
  12331. /**
  12332. * Returns width of an object bounding box counting transformations
  12333. * @return {Number} width value
  12334. */
  12335. getWidth: function() {
  12336. return this._getTransformedDimensions().x;
  12337. },
  12338. /**
  12339. * Returns height of an object bounding box counting transformations
  12340. * to be renamed in 2.0
  12341. * @return {Number} height value
  12342. */
  12343. getHeight: function() {
  12344. return this._getTransformedDimensions().y;
  12345. },
  12346. /**
  12347. * Makes sure the scale is valid and modifies it if necessary
  12348. * @private
  12349. * @param {Number} value
  12350. * @return {Number}
  12351. */
  12352. _constrainScale: function(value) {
  12353. if (Math.abs(value) < this.minScaleLimit) {
  12354. if (value < 0) {
  12355. return -this.minScaleLimit;
  12356. }
  12357. else {
  12358. return this.minScaleLimit;
  12359. }
  12360. }
  12361. return value;
  12362. },
  12363. /**
  12364. * Scales an object (equally by x and y)
  12365. * @param {Number} value Scale factor
  12366. * @return {fabric.Object} thisArg
  12367. * @chainable
  12368. */
  12369. scale: function(value) {
  12370. value = this._constrainScale(value);
  12371. if (value < 0) {
  12372. this.flipX = !this.flipX;
  12373. this.flipY = !this.flipY;
  12374. value *= -1;
  12375. }
  12376. this.scaleX = value;
  12377. this.scaleY = value;
  12378. return this.setCoords();
  12379. },
  12380. /**
  12381. * Scales an object to a given width, with respect to bounding box (scaling by x/y equally)
  12382. * @param {Number} value New width value
  12383. * @return {fabric.Object} thisArg
  12384. * @chainable
  12385. */
  12386. scaleToWidth: function(value) {
  12387. // adjust to bounding rect factor so that rotated shapes would fit as well
  12388. var boundingRectFactor = this.getBoundingRect().width / this.getWidth();
  12389. return this.scale(value / this.width / boundingRectFactor);
  12390. },
  12391. /**
  12392. * Scales an object to a given height, with respect to bounding box (scaling by x/y equally)
  12393. * @param {Number} value New height value
  12394. * @return {fabric.Object} thisArg
  12395. * @chainable
  12396. */
  12397. scaleToHeight: function(value) {
  12398. // adjust to bounding rect factor so that rotated shapes would fit as well
  12399. var boundingRectFactor = this.getBoundingRect().height / this.getHeight();
  12400. return this.scale(value / this.height / boundingRectFactor);
  12401. },
  12402. /**
  12403. * Calculate and returns the .coords of an object.
  12404. * @return {Object} Object with tl, tr, br, bl ....
  12405. * @chainable
  12406. */
  12407. calcCoords: function(absolute) {
  12408. var theta = degreesToRadians(this.angle),
  12409. vpt = this.getViewportTransform(),
  12410. dim = absolute ? this._getTransformedDimensions() : this._calculateCurrentDimensions(),
  12411. currentWidth = dim.x, currentHeight = dim.y,
  12412. sinTh = Math.sin(theta),
  12413. cosTh = Math.cos(theta),
  12414. _angle = currentWidth > 0 ? Math.atan(currentHeight / currentWidth) : 0,
  12415. _hypotenuse = (currentWidth / Math.cos(_angle)) / 2,
  12416. offsetX = Math.cos(_angle + theta) * _hypotenuse,
  12417. offsetY = Math.sin(_angle + theta) * _hypotenuse,
  12418. center = this.getCenterPoint(),
  12419. // offset added for rotate and scale actions
  12420. coords = absolute ? center : fabric.util.transformPoint(center, vpt),
  12421. tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY),
  12422. tr = new fabric.Point(tl.x + (currentWidth * cosTh), tl.y + (currentWidth * sinTh)),
  12423. bl = new fabric.Point(tl.x - (currentHeight * sinTh), tl.y + (currentHeight * cosTh)),
  12424. br = new fabric.Point(coords.x + offsetX, coords.y + offsetY);
  12425. if (!absolute) {
  12426. var ml = new fabric.Point((tl.x + bl.x) / 2, (tl.y + bl.y) / 2),
  12427. mt = new fabric.Point((tr.x + tl.x) / 2, (tr.y + tl.y) / 2),
  12428. mr = new fabric.Point((br.x + tr.x) / 2, (br.y + tr.y) / 2),
  12429. mb = new fabric.Point((br.x + bl.x) / 2, (br.y + bl.y) / 2),
  12430. mtr = new fabric.Point(mt.x + sinTh * this.rotatingPointOffset, mt.y - cosTh * this.rotatingPointOffset);
  12431. }
  12432. // debugging
  12433. /* setTimeout(function() {
  12434. canvas.contextTop.fillStyle = 'green';
  12435. canvas.contextTop.fillRect(mb.x, mb.y, 3, 3);
  12436. canvas.contextTop.fillRect(bl.x, bl.y, 3, 3);
  12437. canvas.contextTop.fillRect(br.x, br.y, 3, 3);
  12438. canvas.contextTop.fillRect(tl.x, tl.y, 3, 3);
  12439. canvas.contextTop.fillRect(tr.x, tr.y, 3, 3);
  12440. canvas.contextTop.fillRect(ml.x, ml.y, 3, 3);
  12441. canvas.contextTop.fillRect(mr.x, mr.y, 3, 3);
  12442. canvas.contextTop.fillRect(mt.x, mt.y, 3, 3);
  12443. canvas.contextTop.fillRect(mtr.x, mtr.y, 3, 3);
  12444. }, 50); */
  12445. var coords = {
  12446. // corners
  12447. tl: tl, tr: tr, br: br, bl: bl,
  12448. };
  12449. if (!absolute) {
  12450. // middle
  12451. coords.ml = ml;
  12452. coords.mt = mt;
  12453. coords.mr = mr;
  12454. coords.mb = mb;
  12455. // rotating point
  12456. coords.mtr = mtr;
  12457. }
  12458. return coords;
  12459. },
  12460. /**
  12461. * Sets corner position coordinates based on current angle, width and height
  12462. * See https://github.com/kangax/fabric.js/wiki/When-to-call-setCoords
  12463. * @param {Boolean} [ignoreZoom] set oCoords with or without the viewport transform.
  12464. * @param {Boolean} [skipAbsolute] skip calculation of aCoords, usefull in setViewportTransform
  12465. * @return {fabric.Object} thisArg
  12466. * @chainable
  12467. */
  12468. setCoords: function(ignoreZoom, skipAbsolute) {
  12469. this.oCoords = this.calcCoords(ignoreZoom);
  12470. if (!skipAbsolute) {
  12471. this.aCoords = this.calcCoords(true);
  12472. }
  12473. // set coordinates of the draggable boxes in the corners used to scale/rotate the image
  12474. ignoreZoom || (this._setCornerCoords && this._setCornerCoords());
  12475. return this;
  12476. },
  12477. /**
  12478. * calculate rotation matrix of an object
  12479. * @return {Array} rotation matrix for the object
  12480. */
  12481. _calcRotateMatrix: function() {
  12482. if (this.angle) {
  12483. var theta = degreesToRadians(this.angle), cos = Math.cos(theta), sin = Math.sin(theta);
  12484. return [cos, sin, -sin, cos, 0, 0];
  12485. }
  12486. return fabric.iMatrix.concat();
  12487. },
  12488. /**
  12489. * calculate trasform Matrix that represent current transformation from
  12490. * object properties.
  12491. * @param {Boolean} [skipGroup] return transformMatrix for object and not go upward with parents
  12492. * @return {Array} matrix Transform Matrix for the object
  12493. */
  12494. calcTransformMatrix: function(skipGroup) {
  12495. var center = this.getCenterPoint(),
  12496. translateMatrix = [1, 0, 0, 1, center.x, center.y],
  12497. rotateMatrix = this._calcRotateMatrix(),
  12498. dimensionMatrix = this._calcDimensionsTransformMatrix(this.skewX, this.skewY, true),
  12499. matrix = this.group && !skipGroup ? this.group.calcTransformMatrix() : fabric.iMatrix.concat();
  12500. matrix = multiplyMatrices(matrix, translateMatrix);
  12501. matrix = multiplyMatrices(matrix, rotateMatrix);
  12502. matrix = multiplyMatrices(matrix, dimensionMatrix);
  12503. return matrix;
  12504. },
  12505. _calcDimensionsTransformMatrix: function(skewX, skewY, flipping) {
  12506. var skewMatrixX = [1, 0, Math.tan(degreesToRadians(skewX)), 1],
  12507. skewMatrixY = [1, Math.tan(degreesToRadians(skewY)), 0, 1],
  12508. scaleX = this.scaleX * (flipping && this.flipX ? -1 : 1),
  12509. scaleY = this.scaleY * (flipping && this.flipY ? -1 : 1),
  12510. scaleMatrix = [scaleX, 0, 0, scaleY],
  12511. m = multiplyMatrices(scaleMatrix, skewMatrixX, true);
  12512. return multiplyMatrices(m, skewMatrixY, true);
  12513. },
  12514. /*
  12515. * Calculate object dimensions from its properties
  12516. * @private
  12517. * @return {Object} .x width dimension
  12518. * @return {Object} .y height dimension
  12519. */
  12520. _getNonTransformedDimensions: function() {
  12521. var strokeWidth = this.strokeWidth,
  12522. w = this.width + strokeWidth,
  12523. h = this.height + strokeWidth;
  12524. return { x: w, y: h };
  12525. },
  12526. /*
  12527. * Calculate object bounding boxdimensions from its properties scale, skew.
  12528. * @private
  12529. * @return {Object} .x width dimension
  12530. * @return {Object} .y height dimension
  12531. */
  12532. _getTransformedDimensions: function(skewX, skewY) {
  12533. if (typeof skewX === 'undefined') {
  12534. skewX = this.skewX;
  12535. }
  12536. if (typeof skewY === 'undefined') {
  12537. skewY = this.skewY;
  12538. }
  12539. var dimensions = this._getNonTransformedDimensions(),
  12540. dimX = dimensions.x / 2, dimY = dimensions.y / 2,
  12541. points = [
  12542. {
  12543. x: -dimX,
  12544. y: -dimY
  12545. },
  12546. {
  12547. x: dimX,
  12548. y: -dimY
  12549. },
  12550. {
  12551. x: -dimX,
  12552. y: dimY
  12553. },
  12554. {
  12555. x: dimX,
  12556. y: dimY
  12557. }],
  12558. i, transformMatrix = this._calcDimensionsTransformMatrix(skewX, skewY, false),
  12559. bbox;
  12560. for (i = 0; i < points.length; i++) {
  12561. points[i] = fabric.util.transformPoint(points[i], transformMatrix);
  12562. }
  12563. bbox = fabric.util.makeBoundingBoxFromPoints(points);
  12564. return { x: bbox.width, y: bbox.height };
  12565. },
  12566. /*
  12567. * Calculate object dimensions for controls. include padding and canvas zoom
  12568. * private
  12569. */
  12570. _calculateCurrentDimensions: function() {
  12571. var vpt = this.getViewportTransform(),
  12572. dim = this._getTransformedDimensions(),
  12573. p = fabric.util.transformPoint(dim, vpt, true);
  12574. return p.scalarAdd(2 * this.padding);
  12575. },
  12576. });
  12577. })();
  12578. fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
  12579. /**
  12580. * Moves an object to the bottom of the stack of drawn objects
  12581. * @return {fabric.Object} thisArg
  12582. * @chainable
  12583. */
  12584. sendToBack: function() {
  12585. if (this.group) {
  12586. fabric.StaticCanvas.prototype.sendToBack.call(this.group, this);
  12587. }
  12588. else {
  12589. this.canvas.sendToBack(this);
  12590. }
  12591. return this;
  12592. },
  12593. /**
  12594. * Moves an object to the top of the stack of drawn objects
  12595. * @return {fabric.Object} thisArg
  12596. * @chainable
  12597. */
  12598. bringToFront: function() {
  12599. if (this.group) {
  12600. fabric.StaticCanvas.prototype.bringToFront.call(this.group, this);
  12601. }
  12602. else {
  12603. this.canvas.bringToFront(this);
  12604. }
  12605. return this;
  12606. },
  12607. /**
  12608. * Moves an object down in stack of drawn objects
  12609. * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object
  12610. * @return {fabric.Object} thisArg
  12611. * @chainable
  12612. */
  12613. sendBackwards: function(intersecting) {
  12614. if (this.group) {
  12615. fabric.StaticCanvas.prototype.sendBackwards.call(this.group, this, intersecting);
  12616. }
  12617. else {
  12618. this.canvas.sendBackwards(this, intersecting);
  12619. }
  12620. return this;
  12621. },
  12622. /**
  12623. * Moves an object up in stack of drawn objects
  12624. * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object
  12625. * @return {fabric.Object} thisArg
  12626. * @chainable
  12627. */
  12628. bringForward: function(intersecting) {
  12629. if (this.group) {
  12630. fabric.StaticCanvas.prototype.bringForward.call(this.group, this, intersecting);
  12631. }
  12632. else {
  12633. this.canvas.bringForward(this, intersecting);
  12634. }
  12635. return this;
  12636. },
  12637. /**
  12638. * Moves an object to specified level in stack of drawn objects
  12639. * @param {Number} index New position of object
  12640. * @return {fabric.Object} thisArg
  12641. * @chainable
  12642. */
  12643. moveTo: function(index) {
  12644. if (this.group) {
  12645. fabric.StaticCanvas.prototype.moveTo.call(this.group, this, index);
  12646. }
  12647. else {
  12648. this.canvas.moveTo(this, index);
  12649. }
  12650. return this;
  12651. }
  12652. });
  12653. /* _TO_SVG_START_ */
  12654. (function() {
  12655. function getSvgColorString(prop, value) {
  12656. if (!value) {
  12657. return prop + ': none; ';
  12658. }
  12659. else if (value.toLive) {
  12660. return prop + ': url(#SVGID_' + value.id + '); ';
  12661. }
  12662. else {
  12663. var color = new fabric.Color(value),
  12664. str = prop + ': ' + color.toRgb() + '; ',
  12665. opacity = color.getAlpha();
  12666. if (opacity !== 1) {
  12667. //change the color in rgb + opacity
  12668. str += prop + '-opacity: ' + opacity.toString() + '; ';
  12669. }
  12670. return str;
  12671. }
  12672. }
  12673. fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
  12674. /**
  12675. * Returns styles-string for svg-export
  12676. * @param {Boolean} skipShadow a boolean to skip shadow filter output
  12677. * @return {String}
  12678. */
  12679. getSvgStyles: function(skipShadow) {
  12680. var fillRule = this.fillRule,
  12681. strokeWidth = this.strokeWidth ? this.strokeWidth : '0',
  12682. strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : 'none',
  12683. strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt',
  12684. strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : 'miter',
  12685. strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : '4',
  12686. opacity = typeof this.opacity !== 'undefined' ? this.opacity : '1',
  12687. visibility = this.visible ? '' : ' visibility: hidden;',
  12688. filter = skipShadow ? '' : this.getSvgFilter(),
  12689. fill = getSvgColorString('fill', this.fill),
  12690. stroke = getSvgColorString('stroke', this.stroke);
  12691. return [
  12692. stroke,
  12693. 'stroke-width: ', strokeWidth, '; ',
  12694. 'stroke-dasharray: ', strokeDashArray, '; ',
  12695. 'stroke-linecap: ', strokeLineCap, '; ',
  12696. 'stroke-linejoin: ', strokeLineJoin, '; ',
  12697. 'stroke-miterlimit: ', strokeMiterLimit, '; ',
  12698. fill,
  12699. 'fill-rule: ', fillRule, '; ',
  12700. 'opacity: ', opacity, ';',
  12701. filter,
  12702. visibility
  12703. ].join('');
  12704. },
  12705. /**
  12706. * Returns filter for svg shadow
  12707. * @return {String}
  12708. */
  12709. getSvgFilter: function() {
  12710. return this.shadow ? 'filter: url(#SVGID_' + this.shadow.id + ');' : '';
  12711. },
  12712. /**
  12713. * Returns id attribute for svg output
  12714. * @return {String}
  12715. */
  12716. getSvgId: function() {
  12717. return this.id ? 'id="' + this.id + '" ' : '';
  12718. },
  12719. /**
  12720. * Returns transform-string for svg-export
  12721. * @return {String}
  12722. */
  12723. getSvgTransform: function() {
  12724. if (this.group && this.group.type === 'path-group') {
  12725. return '';
  12726. }
  12727. var toFixed = fabric.util.toFixed,
  12728. angle = this.getAngle(),
  12729. skewX = (this.getSkewX() % 360),
  12730. skewY = (this.getSkewY() % 360),
  12731. center = this.getCenterPoint(),
  12732. NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS,
  12733. translatePart = this.type === 'path-group' ? '' : 'translate(' +
  12734. toFixed(center.x, NUM_FRACTION_DIGITS) +
  12735. ' ' +
  12736. toFixed(center.y, NUM_FRACTION_DIGITS) +
  12737. ')',
  12738. anglePart = angle !== 0
  12739. ? (' rotate(' + toFixed(angle, NUM_FRACTION_DIGITS) + ')')
  12740. : '',
  12741. scalePart = (this.scaleX === 1 && this.scaleY === 1)
  12742. ? '' :
  12743. (' scale(' +
  12744. toFixed(this.scaleX, NUM_FRACTION_DIGITS) +
  12745. ' ' +
  12746. toFixed(this.scaleY, NUM_FRACTION_DIGITS) +
  12747. ')'),
  12748. skewXPart = skewX !== 0 ? ' skewX(' + toFixed(skewX, NUM_FRACTION_DIGITS) + ')' : '',
  12749. skewYPart = skewY !== 0 ? ' skewY(' + toFixed(skewY, NUM_FRACTION_DIGITS) + ')' : '',
  12750. addTranslateX = this.type === 'path-group' ? this.width : 0,
  12751. flipXPart = this.flipX ? ' matrix(-1 0 0 1 ' + addTranslateX + ' 0) ' : '',
  12752. addTranslateY = this.type === 'path-group' ? this.height : 0,
  12753. flipYPart = this.flipY ? ' matrix(1 0 0 -1 0 ' + addTranslateY + ')' : '';
  12754. return [
  12755. translatePart, anglePart, scalePart, flipXPart, flipYPart, skewXPart, skewYPart
  12756. ].join('');
  12757. },
  12758. /**
  12759. * Returns transform-string for svg-export from the transform matrix of single elements
  12760. * @return {String}
  12761. */
  12762. getSvgTransformMatrix: function() {
  12763. return this.transformMatrix ? ' matrix(' + this.transformMatrix.join(' ') + ') ' : '';
  12764. },
  12765. /**
  12766. * @private
  12767. */
  12768. _createBaseSVGMarkup: function() {
  12769. var markup = [];
  12770. if (this.fill && this.fill.toLive) {
  12771. markup.push(this.fill.toSVG(this, false));
  12772. }
  12773. if (this.stroke && this.stroke.toLive) {
  12774. markup.push(this.stroke.toSVG(this, false));
  12775. }
  12776. if (this.shadow) {
  12777. markup.push(this.shadow.toSVG(this));
  12778. }
  12779. return markup;
  12780. }
  12781. });
  12782. })();
  12783. /* _TO_SVG_END_ */
  12784. (function() {
  12785. var extend = fabric.util.object.extend,
  12786. originalSet = 'stateProperties';
  12787. /*
  12788. Depends on `stateProperties`
  12789. */
  12790. function saveProps(origin, destination, props) {
  12791. var tmpObj = { }, deep = true;
  12792. props.forEach(function(prop) {
  12793. tmpObj[prop] = origin[prop];
  12794. });
  12795. extend(origin[destination], tmpObj, deep);
  12796. }
  12797. function _isEqual(origValue, currentValue, firstPass) {
  12798. if (!fabric.isLikelyNode && origValue instanceof Element) {
  12799. // avoid checking deep html elements
  12800. return origValue === currentValue;
  12801. }
  12802. else if (origValue instanceof Array) {
  12803. if (origValue.length !== currentValue.length) {
  12804. return false;
  12805. }
  12806. for (var i = 0, len = origValue.length; i < len; i++) {
  12807. if (origValue[i] !== currentValue[i]) {
  12808. return false;
  12809. }
  12810. }
  12811. return true;
  12812. }
  12813. else if (origValue && typeof origValue === 'object') {
  12814. if (!firstPass && Object.keys(origValue).length !== Object.keys(currentValue).length) {
  12815. return false;
  12816. }
  12817. for (var key in origValue) {
  12818. if (!_isEqual(origValue[key], currentValue[key])) {
  12819. return false;
  12820. }
  12821. }
  12822. return true;
  12823. }
  12824. else {
  12825. return origValue === currentValue;
  12826. }
  12827. }
  12828. fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
  12829. /**
  12830. * Returns true if object state (one of its state properties) was changed
  12831. * @param {String} [propertySet] optional name for the set of property we want to save
  12832. * @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called
  12833. */
  12834. hasStateChanged: function(propertySet) {
  12835. propertySet = propertySet || originalSet;
  12836. propertySet = '_' + propertySet;
  12837. return !_isEqual(this[propertySet], this, true);
  12838. },
  12839. /**
  12840. * Saves state of an object
  12841. * @param {Object} [options] Object with additional `stateProperties` array to include when saving state
  12842. * @return {fabric.Object} thisArg
  12843. */
  12844. saveState: function(options) {
  12845. var propertySet = options && options.propertySet || originalSet,
  12846. destination = '_' + propertySet;
  12847. if (!this[destination]) {
  12848. return this.setupState(options);
  12849. }
  12850. saveProps(this, destination, this[propertySet]);
  12851. if (options && options.stateProperties) {
  12852. saveProps(this, destination, options.stateProperties);
  12853. }
  12854. return this;
  12855. },
  12856. /**
  12857. * Setups state of an object
  12858. * @param {Object} [options] Object with additional `stateProperties` array to include when saving state
  12859. * @return {fabric.Object} thisArg
  12860. */
  12861. setupState: function(options) {
  12862. options = options || { };
  12863. var propertySet = options.propertySet || originalSet;
  12864. options.propertySet = propertySet;
  12865. this['_' + propertySet] = { };
  12866. this.saveState(options);
  12867. return this;
  12868. }
  12869. });
  12870. })();
  12871. (function() {
  12872. var degreesToRadians = fabric.util.degreesToRadians,
  12873. /* eslint-disable camelcase */
  12874. isVML = function() { return typeof G_vmlCanvasManager !== 'undefined'; };
  12875. /* eslint-enable camelcase */
  12876. fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
  12877. /**
  12878. * The object interactivity controls.
  12879. * @private
  12880. */
  12881. _controlsVisibility: null,
  12882. /**
  12883. * Determines which corner has been clicked
  12884. * @private
  12885. * @param {Object} pointer The pointer indicating the mouse position
  12886. * @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found
  12887. */
  12888. _findTargetCorner: function(pointer) {
  12889. if (!this.hasControls || !this.active) {
  12890. return false;
  12891. }
  12892. var ex = pointer.x,
  12893. ey = pointer.y,
  12894. xPoints,
  12895. lines;
  12896. this.__corner = 0;
  12897. for (var i in this.oCoords) {
  12898. if (!this.isControlVisible(i)) {
  12899. continue;
  12900. }
  12901. if (i === 'mtr' && !this.hasRotatingPoint) {
  12902. continue;
  12903. }
  12904. if (this.get('lockUniScaling') &&
  12905. (i === 'mt' || i === 'mr' || i === 'mb' || i === 'ml')) {
  12906. continue;
  12907. }
  12908. lines = this._getImageLines(this.oCoords[i].corner);
  12909. // debugging
  12910. // canvas.contextTop.fillRect(lines.bottomline.d.x, lines.bottomline.d.y, 2, 2);
  12911. // canvas.contextTop.fillRect(lines.bottomline.o.x, lines.bottomline.o.y, 2, 2);
  12912. // canvas.contextTop.fillRect(lines.leftline.d.x, lines.leftline.d.y, 2, 2);
  12913. // canvas.contextTop.fillRect(lines.leftline.o.x, lines.leftline.o.y, 2, 2);
  12914. // canvas.contextTop.fillRect(lines.topline.d.x, lines.topline.d.y, 2, 2);
  12915. // canvas.contextTop.fillRect(lines.topline.o.x, lines.topline.o.y, 2, 2);
  12916. // canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2);
  12917. // canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2);
  12918. xPoints = this._findCrossPoints({ x: ex, y: ey }, lines);
  12919. if (xPoints !== 0 && xPoints % 2 === 1) {
  12920. this.__corner = i;
  12921. return i;
  12922. }
  12923. }
  12924. return false;
  12925. },
  12926. /**
  12927. * Sets the coordinates of the draggable boxes in the corners of
  12928. * the image used to scale/rotate it.
  12929. * @private
  12930. */
  12931. _setCornerCoords: function() {
  12932. var coords = this.oCoords,
  12933. newTheta = degreesToRadians(45 - this.angle),
  12934. /* Math.sqrt(2 * Math.pow(this.cornerSize, 2)) / 2, */
  12935. /* 0.707106 stands for sqrt(2)/2 */
  12936. cornerHypotenuse = this.cornerSize * 0.707106,
  12937. cosHalfOffset = cornerHypotenuse * Math.cos(newTheta),
  12938. sinHalfOffset = cornerHypotenuse * Math.sin(newTheta),
  12939. x, y;
  12940. for (var point in coords) {
  12941. x = coords[point].x;
  12942. y = coords[point].y;
  12943. coords[point].corner = {
  12944. tl: {
  12945. x: x - sinHalfOffset,
  12946. y: y - cosHalfOffset
  12947. },
  12948. tr: {
  12949. x: x + cosHalfOffset,
  12950. y: y - sinHalfOffset
  12951. },
  12952. bl: {
  12953. x: x - cosHalfOffset,
  12954. y: y + sinHalfOffset
  12955. },
  12956. br: {
  12957. x: x + sinHalfOffset,
  12958. y: y + cosHalfOffset
  12959. }
  12960. };
  12961. }
  12962. },
  12963. /**
  12964. * Draws a colored layer behind the object, inside its selection borders.
  12965. * Requires public options: padding, selectionBackgroundColor
  12966. * this function is called when the context is transformed
  12967. * @param {CanvasRenderingContext2D} ctx Context to draw on
  12968. * @return {fabric.Object} thisArg
  12969. * @chainable
  12970. */
  12971. drawSelectionBackground: function(ctx) {
  12972. if (!this.selectionBackgroundColor || this.group || !this.active) {
  12973. return this;
  12974. }
  12975. ctx.save();
  12976. var center = this.getCenterPoint(), wh = this._calculateCurrentDimensions(),
  12977. vpt = this.canvas.viewportTransform;
  12978. ctx.translate(center.x, center.y);
  12979. ctx.scale(1 / vpt[0], 1 / vpt[3]);
  12980. ctx.rotate(degreesToRadians(this.angle));
  12981. ctx.fillStyle = this.selectionBackgroundColor;
  12982. ctx.fillRect(-wh.x / 2, -wh.y / 2, wh.x, wh.y);
  12983. ctx.restore();
  12984. return this;
  12985. },
  12986. /**
  12987. * Draws borders of an object's bounding box.
  12988. * Requires public properties: width, height
  12989. * Requires public options: padding, borderColor
  12990. * @param {CanvasRenderingContext2D} ctx Context to draw on
  12991. * @return {fabric.Object} thisArg
  12992. * @chainable
  12993. */
  12994. drawBorders: function(ctx) {
  12995. if (!this.hasBorders) {
  12996. return this;
  12997. }
  12998. var wh = this._calculateCurrentDimensions(),
  12999. strokeWidth = 1 / this.borderScaleFactor,
  13000. width = wh.x + strokeWidth,
  13001. height = wh.y + strokeWidth;
  13002. ctx.save();
  13003. // ctx.strokeWidth = 10;
  13004. ctx.strokeStyle = this.borderColor;
  13005. //ctx.strokeStyle = '#f00';
  13006. this._setLineDash(ctx, this.borderDashArray, null);
  13007. ctx.strokeRect(
  13008. -width / 2,
  13009. -height / 2,
  13010. width,
  13011. height
  13012. );
  13013. if (this.hasRotatingPoint && this.isControlVisible('mtr') && !this.get('lockRotation') && this.hasControls) {
  13014. var rotateHeight = -height / 2;
  13015. ctx.beginPath();
  13016. ctx.moveTo(0, rotateHeight);
  13017. ctx.lineTo(0, rotateHeight - this.rotatingPointOffset);
  13018. ctx.closePath();
  13019. ctx.stroke();
  13020. }
  13021. ctx.restore();
  13022. return this;
  13023. },
  13024. /**
  13025. * Draws borders of an object's bounding box when it is inside a group.
  13026. * Requires public properties: width, height
  13027. * Requires public options: padding, borderColor
  13028. * @param {CanvasRenderingContext2D} ctx Context to draw on
  13029. * @param {object} options object representing current object parameters
  13030. * @return {fabric.Object} thisArg
  13031. * @chainable
  13032. */
  13033. drawBordersInGroup: function(ctx, options) {
  13034. if (!this.hasBorders) {
  13035. return this;
  13036. }
  13037. var p = this._getNonTransformedDimensions(),
  13038. matrix = fabric.util.customTransformMatrix(options.scaleX, options.scaleY, options.skewX),
  13039. wh = fabric.util.transformPoint(p, matrix),
  13040. strokeWidth = 1 / this.borderScaleFactor,
  13041. width = wh.x + strokeWidth,
  13042. height = wh.y + strokeWidth;
  13043. ctx.save();
  13044. this._setLineDash(ctx, this.borderDashArray, null);
  13045. ctx.strokeStyle = this.borderColor;
  13046. ctx.strokeRect(
  13047. -width / 2,
  13048. -height / 2,
  13049. width,
  13050. height
  13051. );
  13052. ctx.restore();
  13053. return this;
  13054. },
  13055. /**
  13056. * Draws corners of an object's bounding box.
  13057. * Requires public properties: width, height
  13058. * Requires public options: cornerSize, padding
  13059. * @param {CanvasRenderingContext2D} ctx Context to draw on
  13060. * @return {fabric.Object} thisArg
  13061. * @chainable
  13062. */
  13063. drawControls: function(ctx) {
  13064. if (!this.hasControls) {
  13065. return this;
  13066. }
  13067. var wh = this._calculateCurrentDimensions(),
  13068. width = wh.x,
  13069. height = wh.y,
  13070. scaleOffset = this.cornerSize,
  13071. left = -(width + scaleOffset) / 2,
  13072. top = -(height + scaleOffset) / 2,
  13073. methodName = this.transparentCorners ? 'stroke' : 'fill';
  13074. ctx.save();
  13075. ctx.strokeStyle = ctx.fillStyle = this.cornerColor;
  13076. if (!this.transparentCorners) {
  13077. ctx.strokeStyle = this.cornerStrokeColor;
  13078. }
  13079. this._setLineDash(ctx, this.cornerDashArray, null);
  13080. // top-left
  13081. this._drawControl('tl', ctx, methodName,
  13082. left,
  13083. top);
  13084. // top-right
  13085. this._drawControl('tr', ctx, methodName,
  13086. left + width,
  13087. top);
  13088. // bottom-left
  13089. this._drawControl('bl', ctx, methodName,
  13090. left,
  13091. top + height);
  13092. // bottom-right
  13093. this._drawControl('br', ctx, methodName,
  13094. left + width,
  13095. top + height);
  13096. if (!this.get('lockUniScaling')) {
  13097. // middle-top
  13098. this._drawControl('mt', ctx, methodName,
  13099. left + width / 2,
  13100. top);
  13101. // middle-bottom
  13102. this._drawControl('mb', ctx, methodName,
  13103. left + width / 2,
  13104. top + height);
  13105. // middle-right
  13106. this._drawControl('mr', ctx, methodName,
  13107. left + width,
  13108. top + height / 2);
  13109. // middle-left
  13110. this._drawControl('ml', ctx, methodName,
  13111. left,
  13112. top + height / 2);
  13113. }
  13114. // middle-top-rotate
  13115. if (this.hasRotatingPoint) {
  13116. this._drawControl('mtr', ctx, methodName,
  13117. left + width / 2,
  13118. top - this.rotatingPointOffset);
  13119. }
  13120. ctx.restore();
  13121. return this;
  13122. },
  13123. /**
  13124. * @private
  13125. */
  13126. _drawControl: function(control, ctx, methodName, left, top) {
  13127. if (!this.isControlVisible(control)) {
  13128. return;
  13129. }
  13130. var size = this.cornerSize, stroke = !this.transparentCorners && this.cornerStrokeColor;
  13131. switch (this.cornerStyle) {
  13132. case 'circle':
  13133. ctx.beginPath();
  13134. ctx.arc(left + size / 2, top + size / 2, size / 2, 0, 2 * Math.PI, false);
  13135. ctx[methodName]();
  13136. if (stroke) {
  13137. ctx.stroke();
  13138. }
  13139. break;
  13140. default:
  13141. isVML() || this.transparentCorners || ctx.clearRect(left, top, size, size);
  13142. ctx[methodName + 'Rect'](left, top, size, size);
  13143. if (stroke) {
  13144. ctx.strokeRect(left, top, size, size);
  13145. }
  13146. }
  13147. },
  13148. /**
  13149. * Returns true if the specified control is visible, false otherwise.
  13150. * @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'.
  13151. * @returns {Boolean} true if the specified control is visible, false otherwise
  13152. */
  13153. isControlVisible: function(controlName) {
  13154. return this._getControlsVisibility()[controlName];
  13155. },
  13156. /**
  13157. * Sets the visibility of the specified control.
  13158. * @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'.
  13159. * @param {Boolean} visible true to set the specified control visible, false otherwise
  13160. * @return {fabric.Object} thisArg
  13161. * @chainable
  13162. */
  13163. setControlVisible: function(controlName, visible) {
  13164. this._getControlsVisibility()[controlName] = visible;
  13165. return this;
  13166. },
  13167. /**
  13168. * Sets the visibility state of object controls.
  13169. * @param {Object} [options] Options object
  13170. * @param {Boolean} [options.bl] true to enable the bottom-left control, false to disable it
  13171. * @param {Boolean} [options.br] true to enable the bottom-right control, false to disable it
  13172. * @param {Boolean} [options.mb] true to enable the middle-bottom control, false to disable it
  13173. * @param {Boolean} [options.ml] true to enable the middle-left control, false to disable it
  13174. * @param {Boolean} [options.mr] true to enable the middle-right control, false to disable it
  13175. * @param {Boolean} [options.mt] true to enable the middle-top control, false to disable it
  13176. * @param {Boolean} [options.tl] true to enable the top-left control, false to disable it
  13177. * @param {Boolean} [options.tr] true to enable the top-right control, false to disable it
  13178. * @param {Boolean} [options.mtr] true to enable the middle-top-rotate control, false to disable it
  13179. * @return {fabric.Object} thisArg
  13180. * @chainable
  13181. */
  13182. setControlsVisibility: function(options) {
  13183. options || (options = { });
  13184. for (var p in options) {
  13185. this.setControlVisible(p, options[p]);
  13186. }
  13187. return this;
  13188. },
  13189. /**
  13190. * Returns the instance of the control visibility set for this object.
  13191. * @private
  13192. * @returns {Object}
  13193. */
  13194. _getControlsVisibility: function() {
  13195. if (!this._controlsVisibility) {
  13196. this._controlsVisibility = {
  13197. tl: true,
  13198. tr: true,
  13199. br: true,
  13200. bl: true,
  13201. ml: true,
  13202. mt: true,
  13203. mr: true,
  13204. mb: true,
  13205. mtr: true
  13206. };
  13207. }
  13208. return this._controlsVisibility;
  13209. }
  13210. });
  13211. })();
  13212. fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ {
  13213. /**
  13214. * Animation duration (in ms) for fx* methods
  13215. * @type Number
  13216. * @default
  13217. */
  13218. FX_DURATION: 500,
  13219. /**
  13220. * Centers object horizontally with animation.
  13221. * @param {fabric.Object} object Object to center
  13222. * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
  13223. * @param {Function} [callbacks.onComplete] Invoked on completion
  13224. * @param {Function} [callbacks.onChange] Invoked on every step of animation
  13225. * @return {fabric.Canvas} thisArg
  13226. * @chainable
  13227. */
  13228. fxCenterObjectH: function (object, callbacks) {
  13229. callbacks = callbacks || { };
  13230. var empty = function() { },
  13231. onComplete = callbacks.onComplete || empty,
  13232. onChange = callbacks.onChange || empty,
  13233. _this = this;
  13234. fabric.util.animate({
  13235. startValue: object.get('left'),
  13236. endValue: this.getCenter().left,
  13237. duration: this.FX_DURATION,
  13238. onChange: function(value) {
  13239. object.set('left', value);
  13240. _this.renderAll();
  13241. onChange();
  13242. },
  13243. onComplete: function() {
  13244. object.setCoords();
  13245. onComplete();
  13246. }
  13247. });
  13248. return this;
  13249. },
  13250. /**
  13251. * Centers object vertically with animation.
  13252. * @param {fabric.Object} object Object to center
  13253. * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
  13254. * @param {Function} [callbacks.onComplete] Invoked on completion
  13255. * @param {Function} [callbacks.onChange] Invoked on every step of animation
  13256. * @return {fabric.Canvas} thisArg
  13257. * @chainable
  13258. */
  13259. fxCenterObjectV: function (object, callbacks) {
  13260. callbacks = callbacks || { };
  13261. var empty = function() { },
  13262. onComplete = callbacks.onComplete || empty,
  13263. onChange = callbacks.onChange || empty,
  13264. _this = this;
  13265. fabric.util.animate({
  13266. startValue: object.get('top'),
  13267. endValue: this.getCenter().top,
  13268. duration: this.FX_DURATION,
  13269. onChange: function(value) {
  13270. object.set('top', value);
  13271. _this.renderAll();
  13272. onChange();
  13273. },
  13274. onComplete: function() {
  13275. object.setCoords();
  13276. onComplete();
  13277. }
  13278. });
  13279. return this;
  13280. },
  13281. /**
  13282. * Same as `fabric.Canvas#remove` but animated
  13283. * @param {fabric.Object} object Object to remove
  13284. * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
  13285. * @param {Function} [callbacks.onComplete] Invoked on completion
  13286. * @param {Function} [callbacks.onChange] Invoked on every step of animation
  13287. * @return {fabric.Canvas} thisArg
  13288. * @chainable
  13289. */
  13290. fxRemove: function (object, callbacks) {
  13291. callbacks = callbacks || { };
  13292. var empty = function() { },
  13293. onComplete = callbacks.onComplete || empty,
  13294. onChange = callbacks.onChange || empty,
  13295. _this = this;
  13296. fabric.util.animate({
  13297. startValue: object.get('opacity'),
  13298. endValue: 0,
  13299. duration: this.FX_DURATION,
  13300. onStart: function() {
  13301. object.set('active', false);
  13302. },
  13303. onChange: function(value) {
  13304. object.set('opacity', value);
  13305. _this.renderAll();
  13306. onChange();
  13307. },
  13308. onComplete: function () {
  13309. _this.remove(object);
  13310. onComplete();
  13311. }
  13312. });
  13313. return this;
  13314. }
  13315. });
  13316. fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
  13317. /**
  13318. * Animates object's properties
  13319. * @param {String|Object} property Property to animate (if string) or properties to animate (if object)
  13320. * @param {Number|Object} value Value to animate property to (if string was given first) or options object
  13321. * @return {fabric.Object} thisArg
  13322. * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#animation}
  13323. * @chainable
  13324. *
  13325. * As object — multiple properties
  13326. *
  13327. * object.animate({ left: ..., top: ... });
  13328. * object.animate({ left: ..., top: ... }, { duration: ... });
  13329. *
  13330. * As string — one property
  13331. *
  13332. * object.animate('left', ...);
  13333. * object.animate('left', { duration: ... });
  13334. *
  13335. */
  13336. animate: function() {
  13337. if (arguments[0] && typeof arguments[0] === 'object') {
  13338. var propsToAnimate = [], prop, skipCallbacks;
  13339. for (prop in arguments[0]) {
  13340. propsToAnimate.push(prop);
  13341. }
  13342. for (var i = 0, len = propsToAnimate.length; i < len; i++) {
  13343. prop = propsToAnimate[i];
  13344. skipCallbacks = i !== len - 1;
  13345. this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks);
  13346. }
  13347. }
  13348. else {
  13349. this._animate.apply(this, arguments);
  13350. }
  13351. return this;
  13352. },
  13353. /**
  13354. * @private
  13355. * @param {String} property Property to animate
  13356. * @param {String} to Value to animate to
  13357. * @param {Object} [options] Options object
  13358. * @param {Boolean} [skipCallbacks] When true, callbacks like onchange and oncomplete are not invoked
  13359. */
  13360. _animate: function(property, to, options, skipCallbacks) {
  13361. var _this = this, propPair;
  13362. to = to.toString();
  13363. if (!options) {
  13364. options = { };
  13365. }
  13366. else {
  13367. options = fabric.util.object.clone(options);
  13368. }
  13369. if (~property.indexOf('.')) {
  13370. propPair = property.split('.');
  13371. }
  13372. var currentValue = propPair
  13373. ? this.get(propPair[0])[propPair[1]]
  13374. : this.get(property);
  13375. if (!('from' in options)) {
  13376. options.from = currentValue;
  13377. }
  13378. if (~to.indexOf('=')) {
  13379. to = currentValue + parseFloat(to.replace('=', ''));
  13380. }
  13381. else {
  13382. to = parseFloat(to);
  13383. }
  13384. fabric.util.animate({
  13385. startValue: options.from,
  13386. endValue: to,
  13387. byValue: options.by,
  13388. easing: options.easing,
  13389. duration: options.duration,
  13390. abort: options.abort && function() {
  13391. return options.abort.call(_this);
  13392. },
  13393. onChange: function(value) {
  13394. if (propPair) {
  13395. _this[propPair[0]][propPair[1]] = value;
  13396. }
  13397. else {
  13398. _this.set(property, value);
  13399. }
  13400. if (skipCallbacks) {
  13401. return;
  13402. }
  13403. options.onChange && options.onChange();
  13404. },
  13405. onComplete: function() {
  13406. if (skipCallbacks) {
  13407. return;
  13408. }
  13409. _this.setCoords();
  13410. options.onComplete && options.onComplete();
  13411. }
  13412. });
  13413. }
  13414. });
  13415. (function(global) {
  13416. 'use strict';
  13417. var fabric = global.fabric || (global.fabric = { }),
  13418. extend = fabric.util.object.extend,
  13419. clone = fabric.util.object.clone,
  13420. coordProps = { x1: 1, x2: 1, y1: 1, y2: 1 },
  13421. supportsLineDash = fabric.StaticCanvas.supports('setLineDash');
  13422. if (fabric.Line) {
  13423. fabric.warn('fabric.Line is already defined');
  13424. return;
  13425. }
  13426. var cacheProperties = fabric.Object.prototype.cacheProperties.concat();
  13427. cacheProperties.push(
  13428. 'x1',
  13429. 'x2',
  13430. 'y1',
  13431. 'y2'
  13432. );
  13433. /**
  13434. * Line class
  13435. * @class fabric.Line
  13436. * @extends fabric.Object
  13437. * @see {@link fabric.Line#initialize} for constructor definition
  13438. */
  13439. fabric.Line = fabric.util.createClass(fabric.Object, /** @lends fabric.Line.prototype */ {
  13440. /**
  13441. * Type of an object
  13442. * @type String
  13443. * @default
  13444. */
  13445. type: 'line',
  13446. /**
  13447. * x value or first line edge
  13448. * @type Number
  13449. * @default
  13450. */
  13451. x1: 0,
  13452. /**
  13453. * y value or first line edge
  13454. * @type Number
  13455. * @default
  13456. */
  13457. y1: 0,
  13458. /**
  13459. * x value or second line edge
  13460. * @type Number
  13461. * @default
  13462. */
  13463. x2: 0,
  13464. /**
  13465. * y value or second line edge
  13466. * @type Number
  13467. * @default
  13468. */
  13469. y2: 0,
  13470. cacheProperties: cacheProperties,
  13471. /**
  13472. * Constructor
  13473. * @param {Array} [points] Array of points
  13474. * @param {Object} [options] Options object
  13475. * @return {fabric.Line} thisArg
  13476. */
  13477. initialize: function(points, options) {
  13478. if (!points) {
  13479. points = [0, 0, 0, 0];
  13480. }
  13481. this.callSuper('initialize', options);
  13482. this.set('x1', points[0]);
  13483. this.set('y1', points[1]);
  13484. this.set('x2', points[2]);
  13485. this.set('y2', points[3]);
  13486. this._setWidthHeight(options);
  13487. },
  13488. /**
  13489. * @private
  13490. * @param {Object} [options] Options
  13491. */
  13492. _setWidthHeight: function(options) {
  13493. options || (options = { });
  13494. this.width = Math.abs(this.x2 - this.x1);
  13495. this.height = Math.abs(this.y2 - this.y1);
  13496. this.left = 'left' in options
  13497. ? options.left
  13498. : this._getLeftToOriginX();
  13499. this.top = 'top' in options
  13500. ? options.top
  13501. : this._getTopToOriginY();
  13502. },
  13503. /**
  13504. * @private
  13505. * @param {String} key
  13506. * @param {*} value
  13507. */
  13508. _set: function(key, value) {
  13509. this.callSuper('_set', key, value);
  13510. if (typeof coordProps[key] !== 'undefined') {
  13511. this._setWidthHeight();
  13512. }
  13513. return this;
  13514. },
  13515. /**
  13516. * @private
  13517. * @return {Number} leftToOriginX Distance from left edge of canvas to originX of Line.
  13518. */
  13519. _getLeftToOriginX: makeEdgeToOriginGetter(
  13520. { // property names
  13521. origin: 'originX',
  13522. axis1: 'x1',
  13523. axis2: 'x2',
  13524. dimension: 'width'
  13525. },
  13526. { // possible values of origin
  13527. nearest: 'left',
  13528. center: 'center',
  13529. farthest: 'right'
  13530. }
  13531. ),
  13532. /**
  13533. * @private
  13534. * @return {Number} topToOriginY Distance from top edge of canvas to originY of Line.
  13535. */
  13536. _getTopToOriginY: makeEdgeToOriginGetter(
  13537. { // property names
  13538. origin: 'originY',
  13539. axis1: 'y1',
  13540. axis2: 'y2',
  13541. dimension: 'height'
  13542. },
  13543. { // possible values of origin
  13544. nearest: 'top',
  13545. center: 'center',
  13546. farthest: 'bottom'
  13547. }
  13548. ),
  13549. /**
  13550. * @private
  13551. * @param {CanvasRenderingContext2D} ctx Context to render on
  13552. * @param {Boolean} noTransform
  13553. */
  13554. _render: function(ctx, noTransform) {
  13555. ctx.beginPath();
  13556. if (noTransform) {
  13557. // Line coords are distances from left-top of canvas to origin of line.
  13558. // To render line in a path-group, we need to translate them to
  13559. // distances from center of path-group to center of line.
  13560. var cp = this.getCenterPoint(),
  13561. offset = this.strokeWidth / 2;
  13562. ctx.translate(
  13563. cp.x - (this.strokeLineCap === 'butt' && this.height === 0 ? 0 : offset),
  13564. cp.y - (this.strokeLineCap === 'butt' && this.width === 0 ? 0 : offset)
  13565. );
  13566. }
  13567. if (!this.strokeDashArray || this.strokeDashArray && supportsLineDash) {
  13568. // move from center (of virtual box) to its left/top corner
  13569. // we can't assume x1, y1 is top left and x2, y2 is bottom right
  13570. var p = this.calcLinePoints();
  13571. ctx.moveTo(p.x1, p.y1);
  13572. ctx.lineTo(p.x2, p.y2);
  13573. }
  13574. ctx.lineWidth = this.strokeWidth;
  13575. // TODO: test this
  13576. // make sure setting "fill" changes color of a line
  13577. // (by copying fillStyle to strokeStyle, since line is stroked, not filled)
  13578. var origStrokeStyle = ctx.strokeStyle;
  13579. ctx.strokeStyle = this.stroke || ctx.fillStyle;
  13580. this.stroke && this._renderStroke(ctx);
  13581. ctx.strokeStyle = origStrokeStyle;
  13582. },
  13583. /**
  13584. * @private
  13585. * @param {CanvasRenderingContext2D} ctx Context to render on
  13586. */
  13587. _renderDashedStroke: function(ctx) {
  13588. var p = this.calcLinePoints();
  13589. ctx.beginPath();
  13590. fabric.util.drawDashedLine(ctx, p.x1, p.y1, p.x2, p.y2, this.strokeDashArray);
  13591. ctx.closePath();
  13592. },
  13593. /**
  13594. * Returns object representation of an instance
  13595. * @methd toObject
  13596. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  13597. * @return {Object} object representation of an instance
  13598. */
  13599. toObject: function(propertiesToInclude) {
  13600. return extend(this.callSuper('toObject', propertiesToInclude), this.calcLinePoints());
  13601. },
  13602. /*
  13603. * Calculate object dimensions from its properties
  13604. * @private
  13605. */
  13606. _getNonTransformedDimensions: function() {
  13607. var dim = this.callSuper('_getNonTransformedDimensions');
  13608. if (this.strokeLineCap === 'butt') {
  13609. if (this.width === 0) {
  13610. dim.y -= this.strokeWidth;
  13611. }
  13612. if (this.height === 0) {
  13613. dim.x -= this.strokeWidth;
  13614. }
  13615. }
  13616. return dim;
  13617. },
  13618. /**
  13619. * Recalculates line points given width and height
  13620. * @private
  13621. */
  13622. calcLinePoints: function() {
  13623. var xMult = this.x1 <= this.x2 ? -1 : 1,
  13624. yMult = this.y1 <= this.y2 ? -1 : 1,
  13625. x1 = (xMult * this.width * 0.5),
  13626. y1 = (yMult * this.height * 0.5),
  13627. x2 = (xMult * this.width * -0.5),
  13628. y2 = (yMult * this.height * -0.5);
  13629. return {
  13630. x1: x1,
  13631. x2: x2,
  13632. y1: y1,
  13633. y2: y2
  13634. };
  13635. },
  13636. /* _TO_SVG_START_ */
  13637. /**
  13638. * Returns SVG representation of an instance
  13639. * @param {Function} [reviver] Method for further parsing of svg representation.
  13640. * @return {String} svg representation of an instance
  13641. */
  13642. toSVG: function(reviver) {
  13643. var markup = this._createBaseSVGMarkup(),
  13644. p = { x1: this.x1, x2: this.x2, y1: this.y1, y2: this.y2 };
  13645. if (!(this.group && this.group.type === 'path-group')) {
  13646. p = this.calcLinePoints();
  13647. }
  13648. markup.push(
  13649. '<line ', this.getSvgId(),
  13650. 'x1="', p.x1,
  13651. '" y1="', p.y1,
  13652. '" x2="', p.x2,
  13653. '" y2="', p.y2,
  13654. '" style="', this.getSvgStyles(),
  13655. '" transform="', this.getSvgTransform(),
  13656. this.getSvgTransformMatrix(),
  13657. '"/>\n'
  13658. );
  13659. return reviver ? reviver(markup.join('')) : markup.join('');
  13660. },
  13661. /* _TO_SVG_END_ */
  13662. });
  13663. /* _FROM_SVG_START_ */
  13664. /**
  13665. * List of attribute names to account for when parsing SVG element (used by {@link fabric.Line.fromElement})
  13666. * @static
  13667. * @memberOf fabric.Line
  13668. * @see http://www.w3.org/TR/SVG/shapes.html#LineElement
  13669. */
  13670. fabric.Line.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x1 y1 x2 y2'.split(' '));
  13671. /**
  13672. * Returns fabric.Line instance from an SVG element
  13673. * @static
  13674. * @memberOf fabric.Line
  13675. * @param {SVGElement} element Element to parse
  13676. * @param {Object} [options] Options object
  13677. * @return {fabric.Line} instance of fabric.Line
  13678. */
  13679. fabric.Line.fromElement = function(element, options) {
  13680. options = options || { };
  13681. var parsedAttributes = fabric.parseAttributes(element, fabric.Line.ATTRIBUTE_NAMES),
  13682. points = [
  13683. parsedAttributes.x1 || 0,
  13684. parsedAttributes.y1 || 0,
  13685. parsedAttributes.x2 || 0,
  13686. parsedAttributes.y2 || 0
  13687. ];
  13688. options.originX = 'left';
  13689. options.originY = 'top';
  13690. return new fabric.Line(points, extend(parsedAttributes, options));
  13691. };
  13692. /* _FROM_SVG_END_ */
  13693. /**
  13694. * Returns fabric.Line instance from an object representation
  13695. * @static
  13696. * @memberOf fabric.Line
  13697. * @param {Object} object Object to create an instance from
  13698. * @param {function} [callback] invoked with new instance as first argument
  13699. * @param {Boolean} [forceAsync] Force an async behaviour trying to create pattern first
  13700. * @return {fabric.Line} instance of fabric.Line
  13701. */
  13702. fabric.Line.fromObject = function(object, callback, forceAsync) {
  13703. function _callback(instance) {
  13704. delete instance.points;
  13705. callback && callback(instance);
  13706. };
  13707. var options = clone(object, true);
  13708. options.points = [object.x1, object.y1, object.x2, object.y2];
  13709. var line = fabric.Object._fromObject('Line', options, _callback, forceAsync, 'points');
  13710. if (line) {
  13711. delete line.points;
  13712. }
  13713. return line;
  13714. };
  13715. /**
  13716. * Produces a function that calculates distance from canvas edge to Line origin.
  13717. */
  13718. function makeEdgeToOriginGetter(propertyNames, originValues) {
  13719. var origin = propertyNames.origin,
  13720. axis1 = propertyNames.axis1,
  13721. axis2 = propertyNames.axis2,
  13722. dimension = propertyNames.dimension,
  13723. nearest = originValues.nearest,
  13724. center = originValues.center,
  13725. farthest = originValues.farthest;
  13726. return function() {
  13727. switch (this.get(origin)) {
  13728. case nearest:
  13729. return Math.min(this.get(axis1), this.get(axis2));
  13730. case center:
  13731. return Math.min(this.get(axis1), this.get(axis2)) + (0.5 * this.get(dimension));
  13732. case farthest:
  13733. return Math.max(this.get(axis1), this.get(axis2));
  13734. }
  13735. };
  13736. }
  13737. })(typeof exports !== 'undefined' ? exports : this);
  13738. (function(global) {
  13739. 'use strict';
  13740. var fabric = global.fabric || (global.fabric = { }),
  13741. pi = Math.PI,
  13742. extend = fabric.util.object.extend;
  13743. if (fabric.Circle) {
  13744. fabric.warn('fabric.Circle is already defined.');
  13745. return;
  13746. }
  13747. var cacheProperties = fabric.Object.prototype.cacheProperties.concat();
  13748. cacheProperties.push(
  13749. 'radius'
  13750. );
  13751. /**
  13752. * Circle class
  13753. * @class fabric.Circle
  13754. * @extends fabric.Object
  13755. * @see {@link fabric.Circle#initialize} for constructor definition
  13756. */
  13757. fabric.Circle = fabric.util.createClass(fabric.Object, /** @lends fabric.Circle.prototype */ {
  13758. /**
  13759. * Type of an object
  13760. * @type String
  13761. * @default
  13762. */
  13763. type: 'circle',
  13764. /**
  13765. * Radius of this circle
  13766. * @type Number
  13767. * @default
  13768. */
  13769. radius: 0,
  13770. /**
  13771. * Start angle of the circle, moving clockwise
  13772. * @type Number
  13773. * @default 0
  13774. */
  13775. startAngle: 0,
  13776. /**
  13777. * End angle of the circle
  13778. * @type Number
  13779. * @default 2Pi
  13780. */
  13781. endAngle: pi * 2,
  13782. cacheProperties: cacheProperties,
  13783. /**
  13784. * Constructor
  13785. * @param {Object} [options] Options object
  13786. * @return {fabric.Circle} thisArg
  13787. */
  13788. initialize: function(options) {
  13789. this.callSuper('initialize', options);
  13790. this.set('radius', options && options.radius || 0);
  13791. },
  13792. /**
  13793. * @private
  13794. * @param {String} key
  13795. * @param {*} value
  13796. * @return {fabric.Circle} thisArg
  13797. */
  13798. _set: function(key, value) {
  13799. this.callSuper('_set', key, value);
  13800. if (key === 'radius') {
  13801. this.setRadius(value);
  13802. }
  13803. return this;
  13804. },
  13805. /**
  13806. * Returns object representation of an instance
  13807. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  13808. * @return {Object} object representation of an instance
  13809. */
  13810. toObject: function(propertiesToInclude) {
  13811. return this.callSuper('toObject', ['radius', 'startAngle', 'endAngle'].concat(propertiesToInclude));
  13812. },
  13813. /* _TO_SVG_START_ */
  13814. /**
  13815. * Returns svg representation of an instance
  13816. * @param {Function} [reviver] Method for further parsing of svg representation.
  13817. * @return {String} svg representation of an instance
  13818. */
  13819. toSVG: function(reviver) {
  13820. var markup = this._createBaseSVGMarkup(), x = 0, y = 0,
  13821. angle = (this.endAngle - this.startAngle) % ( 2 * pi);
  13822. if (angle === 0) {
  13823. if (this.group && this.group.type === 'path-group') {
  13824. x = this.left + this.radius;
  13825. y = this.top + this.radius;
  13826. }
  13827. markup.push(
  13828. '<circle ', this.getSvgId(),
  13829. 'cx="' + x + '" cy="' + y + '" ',
  13830. 'r="', this.radius,
  13831. '" style="', this.getSvgStyles(),
  13832. '" transform="', this.getSvgTransform(),
  13833. ' ', this.getSvgTransformMatrix(),
  13834. '"/>\n'
  13835. );
  13836. }
  13837. else {
  13838. var startX = Math.cos(this.startAngle) * this.radius,
  13839. startY = Math.sin(this.startAngle) * this.radius,
  13840. endX = Math.cos(this.endAngle) * this.radius,
  13841. endY = Math.sin(this.endAngle) * this.radius,
  13842. largeFlag = angle > pi ? '1' : '0';
  13843. markup.push(
  13844. '<path d="M ' + startX + ' ' + startY,
  13845. ' A ' + this.radius + ' ' + this.radius,
  13846. ' 0 ', +largeFlag + ' 1', ' ' + endX + ' ' + endY,
  13847. '" style="', this.getSvgStyles(),
  13848. '" transform="', this.getSvgTransform(),
  13849. ' ', this.getSvgTransformMatrix(),
  13850. '"/>\n'
  13851. );
  13852. }
  13853. return reviver ? reviver(markup.join('')) : markup.join('');
  13854. },
  13855. /* _TO_SVG_END_ */
  13856. /**
  13857. * @private
  13858. * @param {CanvasRenderingContext2D} ctx context to render on
  13859. * @param {Boolean} [noTransform] When true, context is not transformed
  13860. */
  13861. _render: function(ctx, noTransform) {
  13862. ctx.beginPath();
  13863. ctx.arc(noTransform ? this.left + this.radius : 0,
  13864. noTransform ? this.top + this.radius : 0,
  13865. this.radius,
  13866. this.startAngle,
  13867. this.endAngle, false);
  13868. this._renderFill(ctx);
  13869. this._renderStroke(ctx);
  13870. },
  13871. /**
  13872. * Returns horizontal radius of an object (according to how an object is scaled)
  13873. * @return {Number}
  13874. */
  13875. getRadiusX: function() {
  13876. return this.get('radius') * this.get('scaleX');
  13877. },
  13878. /**
  13879. * Returns vertical radius of an object (according to how an object is scaled)
  13880. * @return {Number}
  13881. */
  13882. getRadiusY: function() {
  13883. return this.get('radius') * this.get('scaleY');
  13884. },
  13885. /**
  13886. * Sets radius of an object (and updates width accordingly)
  13887. * @return {fabric.Circle} thisArg
  13888. */
  13889. setRadius: function(value) {
  13890. this.radius = value;
  13891. return this.set('width', value * 2).set('height', value * 2);
  13892. },
  13893. });
  13894. /* _FROM_SVG_START_ */
  13895. /**
  13896. * List of attribute names to account for when parsing SVG element (used by {@link fabric.Circle.fromElement})
  13897. * @static
  13898. * @memberOf fabric.Circle
  13899. * @see: http://www.w3.org/TR/SVG/shapes.html#CircleElement
  13900. */
  13901. fabric.Circle.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy r'.split(' '));
  13902. /**
  13903. * Returns {@link fabric.Circle} instance from an SVG element
  13904. * @static
  13905. * @memberOf fabric.Circle
  13906. * @param {SVGElement} element Element to parse
  13907. * @param {Object} [options] Options object
  13908. * @throws {Error} If value of `r` attribute is missing or invalid
  13909. * @return {fabric.Circle} Instance of fabric.Circle
  13910. */
  13911. fabric.Circle.fromElement = function(element, options) {
  13912. options || (options = { });
  13913. var parsedAttributes = fabric.parseAttributes(element, fabric.Circle.ATTRIBUTE_NAMES);
  13914. if (!isValidRadius(parsedAttributes)) {
  13915. throw new Error('value of `r` attribute is required and can not be negative');
  13916. }
  13917. parsedAttributes.left = parsedAttributes.left || 0;
  13918. parsedAttributes.top = parsedAttributes.top || 0;
  13919. var obj = new fabric.Circle(extend(parsedAttributes, options));
  13920. obj.left -= obj.radius;
  13921. obj.top -= obj.radius;
  13922. return obj;
  13923. };
  13924. /**
  13925. * @private
  13926. */
  13927. function isValidRadius(attributes) {
  13928. return (('radius' in attributes) && (attributes.radius >= 0));
  13929. }
  13930. /* _FROM_SVG_END_ */
  13931. /**
  13932. * Returns {@link fabric.Circle} instance from an object representation
  13933. * @static
  13934. * @memberOf fabric.Circle
  13935. * @param {Object} object Object to create an instance from
  13936. * @param {function} [callback] invoked with new instance as first argument
  13937. * @param {Boolean} [forceAsync] Force an async behaviour trying to create pattern first
  13938. * @return {Object} Instance of fabric.Circle
  13939. */
  13940. fabric.Circle.fromObject = function(object, callback, forceAsync) {
  13941. return fabric.Object._fromObject('Circle', object, callback, forceAsync);
  13942. };
  13943. })(typeof exports !== 'undefined' ? exports : this);
  13944. (function(global) {
  13945. 'use strict';
  13946. var fabric = global.fabric || (global.fabric = { });
  13947. if (fabric.Triangle) {
  13948. fabric.warn('fabric.Triangle is already defined');
  13949. return;
  13950. }
  13951. /**
  13952. * Triangle class
  13953. * @class fabric.Triangle
  13954. * @extends fabric.Object
  13955. * @return {fabric.Triangle} thisArg
  13956. * @see {@link fabric.Triangle#initialize} for constructor definition
  13957. */
  13958. fabric.Triangle = fabric.util.createClass(fabric.Object, /** @lends fabric.Triangle.prototype */ {
  13959. /**
  13960. * Type of an object
  13961. * @type String
  13962. * @default
  13963. */
  13964. type: 'triangle',
  13965. /**
  13966. * Constructor
  13967. * @param {Object} [options] Options object
  13968. * @return {Object} thisArg
  13969. */
  13970. initialize: function(options) {
  13971. this.callSuper('initialize', options);
  13972. this.set('width', options && options.width || 100)
  13973. .set('height', options && options.height || 100);
  13974. },
  13975. /**
  13976. * @private
  13977. * @param {CanvasRenderingContext2D} ctx Context to render on
  13978. */
  13979. _render: function(ctx) {
  13980. var widthBy2 = this.width / 2,
  13981. heightBy2 = this.height / 2;
  13982. ctx.beginPath();
  13983. ctx.moveTo(-widthBy2, heightBy2);
  13984. ctx.lineTo(0, -heightBy2);
  13985. ctx.lineTo(widthBy2, heightBy2);
  13986. ctx.closePath();
  13987. this._renderFill(ctx);
  13988. this._renderStroke(ctx);
  13989. },
  13990. /**
  13991. * @private
  13992. * @param {CanvasRenderingContext2D} ctx Context to render on
  13993. */
  13994. _renderDashedStroke: function(ctx) {
  13995. var widthBy2 = this.width / 2,
  13996. heightBy2 = this.height / 2;
  13997. ctx.beginPath();
  13998. fabric.util.drawDashedLine(ctx, -widthBy2, heightBy2, 0, -heightBy2, this.strokeDashArray);
  13999. fabric.util.drawDashedLine(ctx, 0, -heightBy2, widthBy2, heightBy2, this.strokeDashArray);
  14000. fabric.util.drawDashedLine(ctx, widthBy2, heightBy2, -widthBy2, heightBy2, this.strokeDashArray);
  14001. ctx.closePath();
  14002. },
  14003. /* _TO_SVG_START_ */
  14004. /**
  14005. * Returns SVG representation of an instance
  14006. * @param {Function} [reviver] Method for further parsing of svg representation.
  14007. * @return {String} svg representation of an instance
  14008. */
  14009. toSVG: function(reviver) {
  14010. var markup = this._createBaseSVGMarkup(),
  14011. widthBy2 = this.width / 2,
  14012. heightBy2 = this.height / 2,
  14013. points = [
  14014. -widthBy2 + ' ' + heightBy2,
  14015. '0 ' + -heightBy2,
  14016. widthBy2 + ' ' + heightBy2
  14017. ]
  14018. .join(',');
  14019. markup.push(
  14020. '<polygon ', this.getSvgId(),
  14021. 'points="', points,
  14022. '" style="', this.getSvgStyles(),
  14023. '" transform="', this.getSvgTransform(),
  14024. '"/>'
  14025. );
  14026. return reviver ? reviver(markup.join('')) : markup.join('');
  14027. },
  14028. /* _TO_SVG_END_ */
  14029. });
  14030. /**
  14031. * Returns {@link fabric.Triangle} instance from an object representation
  14032. * @static
  14033. * @memberOf fabric.Triangle
  14034. * @param {Object} object Object to create an instance from
  14035. * @param {function} [callback] invoked with new instance as first argument
  14036. * @param {Boolean} [forceAsync] Force an async behaviour trying to create pattern first
  14037. * @return {fabric.Triangle}
  14038. */
  14039. fabric.Triangle.fromObject = function(object, callback, forceAsync) {
  14040. return fabric.Object._fromObject('Triangle', object, callback, forceAsync);
  14041. };
  14042. })(typeof exports !== 'undefined' ? exports : this);
  14043. (function(global) {
  14044. 'use strict';
  14045. var fabric = global.fabric || (global.fabric = { }),
  14046. piBy2 = Math.PI * 2,
  14047. extend = fabric.util.object.extend;
  14048. if (fabric.Ellipse) {
  14049. fabric.warn('fabric.Ellipse is already defined.');
  14050. return;
  14051. }
  14052. var cacheProperties = fabric.Object.prototype.cacheProperties.concat();
  14053. cacheProperties.push(
  14054. 'rx',
  14055. 'ry'
  14056. );
  14057. /**
  14058. * Ellipse class
  14059. * @class fabric.Ellipse
  14060. * @extends fabric.Object
  14061. * @return {fabric.Ellipse} thisArg
  14062. * @see {@link fabric.Ellipse#initialize} for constructor definition
  14063. */
  14064. fabric.Ellipse = fabric.util.createClass(fabric.Object, /** @lends fabric.Ellipse.prototype */ {
  14065. /**
  14066. * Type of an object
  14067. * @type String
  14068. * @default
  14069. */
  14070. type: 'ellipse',
  14071. /**
  14072. * Horizontal radius
  14073. * @type Number
  14074. * @default
  14075. */
  14076. rx: 0,
  14077. /**
  14078. * Vertical radius
  14079. * @type Number
  14080. * @default
  14081. */
  14082. ry: 0,
  14083. cacheProperties: cacheProperties,
  14084. /**
  14085. * Constructor
  14086. * @param {Object} [options] Options object
  14087. * @return {fabric.Ellipse} thisArg
  14088. */
  14089. initialize: function(options) {
  14090. this.callSuper('initialize', options);
  14091. this.set('rx', options && options.rx || 0);
  14092. this.set('ry', options && options.ry || 0);
  14093. },
  14094. /**
  14095. * @private
  14096. * @param {String} key
  14097. * @param {*} value
  14098. * @return {fabric.Ellipse} thisArg
  14099. */
  14100. _set: function(key, value) {
  14101. this.callSuper('_set', key, value);
  14102. switch (key) {
  14103. case 'rx':
  14104. this.rx = value;
  14105. this.set('width', value * 2);
  14106. break;
  14107. case 'ry':
  14108. this.ry = value;
  14109. this.set('height', value * 2);
  14110. break;
  14111. }
  14112. return this;
  14113. },
  14114. /**
  14115. * Returns horizontal radius of an object (according to how an object is scaled)
  14116. * @return {Number}
  14117. */
  14118. getRx: function() {
  14119. return this.get('rx') * this.get('scaleX');
  14120. },
  14121. /**
  14122. * Returns Vertical radius of an object (according to how an object is scaled)
  14123. * @return {Number}
  14124. */
  14125. getRy: function() {
  14126. return this.get('ry') * this.get('scaleY');
  14127. },
  14128. /**
  14129. * Returns object representation of an instance
  14130. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  14131. * @return {Object} object representation of an instance
  14132. */
  14133. toObject: function(propertiesToInclude) {
  14134. return this.callSuper('toObject', ['rx', 'ry'].concat(propertiesToInclude));
  14135. },
  14136. /* _TO_SVG_START_ */
  14137. /**
  14138. * Returns svg representation of an instance
  14139. * @param {Function} [reviver] Method for further parsing of svg representation.
  14140. * @return {String} svg representation of an instance
  14141. */
  14142. toSVG: function(reviver) {
  14143. var markup = this._createBaseSVGMarkup(), x = 0, y = 0;
  14144. if (this.group && this.group.type === 'path-group') {
  14145. x = this.left + this.rx;
  14146. y = this.top + this.ry;
  14147. }
  14148. markup.push(
  14149. '<ellipse ', this.getSvgId(),
  14150. 'cx="', x, '" cy="', y, '" ',
  14151. 'rx="', this.rx,
  14152. '" ry="', this.ry,
  14153. '" style="', this.getSvgStyles(),
  14154. '" transform="', this.getSvgTransform(),
  14155. this.getSvgTransformMatrix(),
  14156. '"/>\n'
  14157. );
  14158. return reviver ? reviver(markup.join('')) : markup.join('');
  14159. },
  14160. /* _TO_SVG_END_ */
  14161. /**
  14162. * @private
  14163. * @param {CanvasRenderingContext2D} ctx context to render on
  14164. * @param {Boolean} [noTransform] When true, context is not transformed
  14165. */
  14166. _render: function(ctx, noTransform) {
  14167. ctx.beginPath();
  14168. ctx.save();
  14169. ctx.transform(1, 0, 0, this.ry / this.rx, 0, 0);
  14170. ctx.arc(
  14171. noTransform ? this.left + this.rx : 0,
  14172. noTransform ? (this.top + this.ry) * this.rx / this.ry : 0,
  14173. this.rx,
  14174. 0,
  14175. piBy2,
  14176. false);
  14177. ctx.restore();
  14178. this._renderFill(ctx);
  14179. this._renderStroke(ctx);
  14180. },
  14181. });
  14182. /* _FROM_SVG_START_ */
  14183. /**
  14184. * List of attribute names to account for when parsing SVG element (used by {@link fabric.Ellipse.fromElement})
  14185. * @static
  14186. * @memberOf fabric.Ellipse
  14187. * @see http://www.w3.org/TR/SVG/shapes.html#EllipseElement
  14188. */
  14189. fabric.Ellipse.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy rx ry'.split(' '));
  14190. /**
  14191. * Returns {@link fabric.Ellipse} instance from an SVG element
  14192. * @static
  14193. * @memberOf fabric.Ellipse
  14194. * @param {SVGElement} element Element to parse
  14195. * @param {Object} [options] Options object
  14196. * @return {fabric.Ellipse}
  14197. */
  14198. fabric.Ellipse.fromElement = function(element, options) {
  14199. options || (options = { });
  14200. var parsedAttributes = fabric.parseAttributes(element, fabric.Ellipse.ATTRIBUTE_NAMES);
  14201. parsedAttributes.left = parsedAttributes.left || 0;
  14202. parsedAttributes.top = parsedAttributes.top || 0;
  14203. var ellipse = new fabric.Ellipse(extend(parsedAttributes, options));
  14204. ellipse.top -= ellipse.ry;
  14205. ellipse.left -= ellipse.rx;
  14206. return ellipse;
  14207. };
  14208. /* _FROM_SVG_END_ */
  14209. /**
  14210. * Returns {@link fabric.Ellipse} instance from an object representation
  14211. * @static
  14212. * @memberOf fabric.Ellipse
  14213. * @param {Object} object Object to create an instance from
  14214. * @param {function} [callback] invoked with new instance as first argument
  14215. * @param {Boolean} [forceAsync] Force an async behaviour trying to create pattern first
  14216. * @return {fabric.Ellipse}
  14217. */
  14218. fabric.Ellipse.fromObject = function(object, callback, forceAsync) {
  14219. return fabric.Object._fromObject('Ellipse', object, callback, forceAsync);
  14220. };
  14221. })(typeof exports !== 'undefined' ? exports : this);
  14222. (function(global) {
  14223. 'use strict';
  14224. var fabric = global.fabric || (global.fabric = { }),
  14225. extend = fabric.util.object.extend;
  14226. if (fabric.Rect) {
  14227. fabric.warn('fabric.Rect is already defined');
  14228. return;
  14229. }
  14230. var stateProperties = fabric.Object.prototype.stateProperties.concat();
  14231. stateProperties.push('rx', 'ry');
  14232. var cacheProperties = fabric.Object.prototype.cacheProperties.concat();
  14233. cacheProperties.push('rx', 'ry');
  14234. /**
  14235. * Rectangle class
  14236. * @class fabric.Rect
  14237. * @extends fabric.Object
  14238. * @return {fabric.Rect} thisArg
  14239. * @see {@link fabric.Rect#initialize} for constructor definition
  14240. */
  14241. fabric.Rect = fabric.util.createClass(fabric.Object, /** @lends fabric.Rect.prototype */ {
  14242. /**
  14243. * List of properties to consider when checking if state of an object is changed ({@link fabric.Object#hasStateChanged})
  14244. * as well as for history (undo/redo) purposes
  14245. * @type Array
  14246. */
  14247. stateProperties: stateProperties,
  14248. /**
  14249. * Type of an object
  14250. * @type String
  14251. * @default
  14252. */
  14253. type: 'rect',
  14254. /**
  14255. * Horizontal border radius
  14256. * @type Number
  14257. * @default
  14258. */
  14259. rx: 0,
  14260. /**
  14261. * Vertical border radius
  14262. * @type Number
  14263. * @default
  14264. */
  14265. ry: 0,
  14266. cacheProperties: cacheProperties,
  14267. /**
  14268. * Constructor
  14269. * @param {Object} [options] Options object
  14270. * @return {Object} thisArg
  14271. */
  14272. initialize: function(options) {
  14273. this.callSuper('initialize', options);
  14274. this._initRxRy();
  14275. },
  14276. /**
  14277. * Initializes rx/ry attributes
  14278. * @private
  14279. */
  14280. _initRxRy: function() {
  14281. if (this.rx && !this.ry) {
  14282. this.ry = this.rx;
  14283. }
  14284. else if (this.ry && !this.rx) {
  14285. this.rx = this.ry;
  14286. }
  14287. },
  14288. /**
  14289. * @private
  14290. * @param {CanvasRenderingContext2D} ctx Context to render on
  14291. * @param {Boolean} noTransform
  14292. */
  14293. _render: function(ctx, noTransform) {
  14294. // optimize 1x1 case (used in spray brush)
  14295. if (this.width === 1 && this.height === 1) {
  14296. ctx.fillRect(-0.5, -0.5, 1, 1);
  14297. return;
  14298. }
  14299. var rx = this.rx ? Math.min(this.rx, this.width / 2) : 0,
  14300. ry = this.ry ? Math.min(this.ry, this.height / 2) : 0,
  14301. w = this.width,
  14302. h = this.height,
  14303. x = noTransform ? this.left : -this.width / 2,
  14304. y = noTransform ? this.top : -this.height / 2,
  14305. isRounded = rx !== 0 || ry !== 0,
  14306. /* "magic number" for bezier approximations of arcs (http://itc.ktu.lt/itc354/Riskus354.pdf) */
  14307. k = 1 - 0.5522847498;
  14308. ctx.beginPath();
  14309. ctx.moveTo(x + rx, y);
  14310. ctx.lineTo(x + w - rx, y);
  14311. isRounded && ctx.bezierCurveTo(x + w - k * rx, y, x + w, y + k * ry, x + w, y + ry);
  14312. ctx.lineTo(x + w, y + h - ry);
  14313. isRounded && ctx.bezierCurveTo(x + w, y + h - k * ry, x + w - k * rx, y + h, x + w - rx, y + h);
  14314. ctx.lineTo(x + rx, y + h);
  14315. isRounded && ctx.bezierCurveTo(x + k * rx, y + h, x, y + h - k * ry, x, y + h - ry);
  14316. ctx.lineTo(x, y + ry);
  14317. isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y);
  14318. ctx.closePath();
  14319. this._renderFill(ctx);
  14320. this._renderStroke(ctx);
  14321. },
  14322. /**
  14323. * @private
  14324. * @param {CanvasRenderingContext2D} ctx Context to render on
  14325. */
  14326. _renderDashedStroke: function(ctx) {
  14327. var x = -this.width / 2,
  14328. y = -this.height / 2,
  14329. w = this.width,
  14330. h = this.height;
  14331. ctx.beginPath();
  14332. fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray);
  14333. fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray);
  14334. fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray);
  14335. fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray);
  14336. ctx.closePath();
  14337. },
  14338. /**
  14339. * Returns object representation of an instance
  14340. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  14341. * @return {Object} object representation of an instance
  14342. */
  14343. toObject: function(propertiesToInclude) {
  14344. return this.callSuper('toObject', ['rx', 'ry'].concat(propertiesToInclude));
  14345. },
  14346. /* _TO_SVG_START_ */
  14347. /**
  14348. * Returns svg representation of an instance
  14349. * @param {Function} [reviver] Method for further parsing of svg representation.
  14350. * @return {String} svg representation of an instance
  14351. */
  14352. toSVG: function(reviver) {
  14353. var markup = this._createBaseSVGMarkup(), x = this.left, y = this.top;
  14354. if (!(this.group && this.group.type === 'path-group')) {
  14355. x = -this.width / 2;
  14356. y = -this.height / 2;
  14357. }
  14358. markup.push(
  14359. '<rect ', this.getSvgId(),
  14360. 'x="', x, '" y="', y,
  14361. '" rx="', this.get('rx'), '" ry="', this.get('ry'),
  14362. '" width="', this.width, '" height="', this.height,
  14363. '" style="', this.getSvgStyles(),
  14364. '" transform="', this.getSvgTransform(),
  14365. this.getSvgTransformMatrix(),
  14366. '"/>\n');
  14367. return reviver ? reviver(markup.join('')) : markup.join('');
  14368. },
  14369. /* _TO_SVG_END_ */
  14370. });
  14371. /* _FROM_SVG_START_ */
  14372. /**
  14373. * List of attribute names to account for when parsing SVG element (used by `fabric.Rect.fromElement`)
  14374. * @static
  14375. * @memberOf fabric.Rect
  14376. * @see: http://www.w3.org/TR/SVG/shapes.html#RectElement
  14377. */
  14378. fabric.Rect.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y rx ry width height'.split(' '));
  14379. /**
  14380. * Returns {@link fabric.Rect} instance from an SVG element
  14381. * @static
  14382. * @memberOf fabric.Rect
  14383. * @param {SVGElement} element Element to parse
  14384. * @param {Object} [options] Options object
  14385. * @return {fabric.Rect} Instance of fabric.Rect
  14386. */
  14387. fabric.Rect.fromElement = function(element, options) {
  14388. if (!element) {
  14389. return null;
  14390. }
  14391. options = options || { };
  14392. var parsedAttributes = fabric.parseAttributes(element, fabric.Rect.ATTRIBUTE_NAMES);
  14393. parsedAttributes.left = parsedAttributes.left || 0;
  14394. parsedAttributes.top = parsedAttributes.top || 0;
  14395. var rect = new fabric.Rect(extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes));
  14396. rect.visible = rect.visible && rect.width > 0 && rect.height > 0;
  14397. return rect;
  14398. };
  14399. /* _FROM_SVG_END_ */
  14400. /**
  14401. * Returns {@link fabric.Rect} instance from an object representation
  14402. * @static
  14403. * @memberOf fabric.Rect
  14404. * @param {Object} object Object to create an instance from
  14405. * @param {Function} [callback] Callback to invoke when an fabric.Rect instance is created
  14406. * @param {Boolean} [forceAsync] Force an async behaviour trying to create pattern first
  14407. * @return {Object} instance of fabric.Rect
  14408. */
  14409. fabric.Rect.fromObject = function(object, callback, forceAsync) {
  14410. return fabric.Object._fromObject('Rect', object, callback, forceAsync);
  14411. };
  14412. })(typeof exports !== 'undefined' ? exports : this);
  14413. (function(global) {
  14414. 'use strict';
  14415. var fabric = global.fabric || (global.fabric = { }),
  14416. extend = fabric.util.object.extend,
  14417. min = fabric.util.array.min,
  14418. max = fabric.util.array.max,
  14419. toFixed = fabric.util.toFixed;
  14420. if (fabric.Polyline) {
  14421. fabric.warn('fabric.Polyline is already defined');
  14422. return;
  14423. }
  14424. var cacheProperties = fabric.Object.prototype.cacheProperties.concat();
  14425. cacheProperties.push('points');
  14426. /**
  14427. * Polyline class
  14428. * @class fabric.Polyline
  14429. * @extends fabric.Object
  14430. * @see {@link fabric.Polyline#initialize} for constructor definition
  14431. */
  14432. fabric.Polyline = fabric.util.createClass(fabric.Object, /** @lends fabric.Polyline.prototype */ {
  14433. /**
  14434. * Type of an object
  14435. * @type String
  14436. * @default
  14437. */
  14438. type: 'polyline',
  14439. /**
  14440. * Points array
  14441. * @type Array
  14442. * @default
  14443. */
  14444. points: null,
  14445. /**
  14446. * Minimum X from points values, necessary to offset points
  14447. * @type Number
  14448. * @default
  14449. */
  14450. minX: 0,
  14451. /**
  14452. * Minimum Y from points values, necessary to offset points
  14453. * @type Number
  14454. * @default
  14455. */
  14456. minY: 0,
  14457. cacheProperties: cacheProperties,
  14458. /**
  14459. * Constructor
  14460. * @param {Array} points Array of points (where each point is an object with x and y)
  14461. * @param {Object} [options] Options object
  14462. * @return {fabric.Polyline} thisArg
  14463. * @example
  14464. * var poly = new fabric.Polyline([
  14465. * { x: 10, y: 10 },
  14466. * { x: 50, y: 30 },
  14467. * { x: 40, y: 70 },
  14468. * { x: 60, y: 50 },
  14469. * { x: 100, y: 150 },
  14470. * { x: 40, y: 100 }
  14471. * ], {
  14472. * stroke: 'red',
  14473. * left: 100,
  14474. * top: 100
  14475. * });
  14476. */
  14477. initialize: function(points, options) {
  14478. options = options || {};
  14479. this.points = points || [];
  14480. this.callSuper('initialize', options);
  14481. this._calcDimensions();
  14482. if (!('top' in options)) {
  14483. this.top = this.minY;
  14484. }
  14485. if (!('left' in options)) {
  14486. this.left = this.minX;
  14487. }
  14488. this.pathOffset = {
  14489. x: this.minX + this.width / 2,
  14490. y: this.minY + this.height / 2
  14491. };
  14492. },
  14493. /**
  14494. * @private
  14495. */
  14496. _calcDimensions: function() {
  14497. var points = this.points,
  14498. minX = min(points, 'x'),
  14499. minY = min(points, 'y'),
  14500. maxX = max(points, 'x'),
  14501. maxY = max(points, 'y');
  14502. this.width = (maxX - minX) || 0;
  14503. this.height = (maxY - minY) || 0;
  14504. this.minX = minX || 0;
  14505. this.minY = minY || 0;
  14506. },
  14507. /**
  14508. * Returns object representation of an instance
  14509. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  14510. * @return {Object} Object representation of an instance
  14511. */
  14512. toObject: function(propertiesToInclude) {
  14513. return extend(this.callSuper('toObject', propertiesToInclude), {
  14514. points: this.points.concat()
  14515. });
  14516. },
  14517. /* _TO_SVG_START_ */
  14518. /**
  14519. * Returns svg representation of an instance
  14520. * @param {Function} [reviver] Method for further parsing of svg representation.
  14521. * @return {String} svg representation of an instance
  14522. */
  14523. toSVG: function(reviver) {
  14524. var points = [], addTransform,
  14525. markup = this._createBaseSVGMarkup();
  14526. for (var i = 0, len = this.points.length; i < len; i++) {
  14527. points.push(toFixed(this.points[i].x, 2), ',', toFixed(this.points[i].y, 2), ' ');
  14528. }
  14529. if (!(this.group && this.group.type === 'path-group')) {
  14530. addTransform = ' translate(' + (-this.pathOffset.x) + ', ' + (-this.pathOffset.y) + ') ';
  14531. }
  14532. markup.push(
  14533. '<', this.type, ' ', this.getSvgId(),
  14534. 'points="', points.join(''),
  14535. '" style="', this.getSvgStyles(),
  14536. '" transform="', this.getSvgTransform(), addTransform,
  14537. ' ', this.getSvgTransformMatrix(),
  14538. '"/>\n'
  14539. );
  14540. return reviver ? reviver(markup.join('')) : markup.join('');
  14541. },
  14542. /* _TO_SVG_END_ */
  14543. /**
  14544. * @private
  14545. * @param {CanvasRenderingContext2D} ctx Context to render on
  14546. * @param {Boolean} noTransform
  14547. */
  14548. commonRender: function(ctx, noTransform) {
  14549. var point, len = this.points.length,
  14550. x = noTransform ? 0 : this.pathOffset.x,
  14551. y = noTransform ? 0 : this.pathOffset.y;
  14552. if (!len || isNaN(this.points[len - 1].y)) {
  14553. // do not draw if no points or odd points
  14554. // NaN comes from parseFloat of a empty string in parser
  14555. return false;
  14556. }
  14557. ctx.beginPath();
  14558. ctx.moveTo(this.points[0].x - x, this.points[0].y - y);
  14559. for (var i = 0; i < len; i++) {
  14560. point = this.points[i];
  14561. ctx.lineTo(point.x - x, point.y - y);
  14562. }
  14563. return true;
  14564. },
  14565. /**
  14566. * @private
  14567. * @param {CanvasRenderingContext2D} ctx Context to render on
  14568. * @param {Boolean} noTransform
  14569. */
  14570. _render: function(ctx, noTransform) {
  14571. if (!this.commonRender(ctx, noTransform)) {
  14572. return;
  14573. }
  14574. this._renderFill(ctx);
  14575. this._renderStroke(ctx);
  14576. },
  14577. /**
  14578. * @private
  14579. * @param {CanvasRenderingContext2D} ctx Context to render on
  14580. */
  14581. _renderDashedStroke: function(ctx) {
  14582. var p1, p2;
  14583. ctx.beginPath();
  14584. for (var i = 0, len = this.points.length; i < len; i++) {
  14585. p1 = this.points[i];
  14586. p2 = this.points[i + 1] || p1;
  14587. fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray);
  14588. }
  14589. },
  14590. /**
  14591. * Returns complexity of an instance
  14592. * @return {Number} complexity of this instance
  14593. */
  14594. complexity: function() {
  14595. return this.get('points').length;
  14596. }
  14597. });
  14598. /* _FROM_SVG_START_ */
  14599. /**
  14600. * List of attribute names to account for when parsing SVG element (used by {@link fabric.Polyline.fromElement})
  14601. * @static
  14602. * @memberOf fabric.Polyline
  14603. * @see: http://www.w3.org/TR/SVG/shapes.html#PolylineElement
  14604. */
  14605. fabric.Polyline.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat();
  14606. /**
  14607. * Returns fabric.Polyline instance from an SVG element
  14608. * @static
  14609. * @memberOf fabric.Polyline
  14610. * @param {SVGElement} element Element to parse
  14611. * @param {Object} [options] Options object
  14612. * @return {fabric.Polyline} Instance of fabric.Polyline
  14613. */
  14614. fabric.Polyline.fromElement = function(element, options) {
  14615. if (!element) {
  14616. return null;
  14617. }
  14618. options || (options = { });
  14619. var points = fabric.parsePointsAttribute(element.getAttribute('points')),
  14620. parsedAttributes = fabric.parseAttributes(element, fabric.Polyline.ATTRIBUTE_NAMES);
  14621. return new fabric.Polyline(points, fabric.util.object.extend(parsedAttributes, options));
  14622. };
  14623. /* _FROM_SVG_END_ */
  14624. /**
  14625. * Returns fabric.Polyline instance from an object representation
  14626. * @static
  14627. * @memberOf fabric.Polyline
  14628. * @param {Object} object Object to create an instance from
  14629. * @param {Function} [callback] Callback to invoke when an fabric.Path instance is created
  14630. * @param {Boolean} [forceAsync] Force an async behaviour trying to create pattern first
  14631. * @return {fabric.Polyline} Instance of fabric.Polyline
  14632. */
  14633. fabric.Polyline.fromObject = function(object, callback, forceAsync) {
  14634. return fabric.Object._fromObject('Polyline', object, callback, forceAsync, 'points');
  14635. };
  14636. })(typeof exports !== 'undefined' ? exports : this);
  14637. (function(global) {
  14638. 'use strict';
  14639. var fabric = global.fabric || (global.fabric = { }),
  14640. extend = fabric.util.object.extend;
  14641. if (fabric.Polygon) {
  14642. fabric.warn('fabric.Polygon is already defined');
  14643. return;
  14644. }
  14645. /**
  14646. * Polygon class
  14647. * @class fabric.Polygon
  14648. * @extends fabric.Polyline
  14649. * @see {@link fabric.Polygon#initialize} for constructor definition
  14650. */
  14651. fabric.Polygon = fabric.util.createClass(fabric.Polyline, /** @lends fabric.Polygon.prototype */ {
  14652. /**
  14653. * Type of an object
  14654. * @type String
  14655. * @default
  14656. */
  14657. type: 'polygon',
  14658. /**
  14659. * @private
  14660. * @param {CanvasRenderingContext2D} ctx Context to render on
  14661. * @param {Boolean} noTransform
  14662. */
  14663. _render: function(ctx, noTransform) {
  14664. if (!this.commonRender(ctx, noTransform)) {
  14665. return;
  14666. }
  14667. ctx.closePath();
  14668. this._renderFill(ctx);
  14669. this._renderStroke(ctx);
  14670. },
  14671. /**
  14672. * @private
  14673. * @param {CanvasRenderingContext2D} ctx Context to render on
  14674. */
  14675. _renderDashedStroke: function(ctx) {
  14676. this.callSuper('_renderDashedStroke', ctx);
  14677. ctx.closePath();
  14678. },
  14679. });
  14680. /* _FROM_SVG_START_ */
  14681. /**
  14682. * List of attribute names to account for when parsing SVG element (used by `fabric.Polygon.fromElement`)
  14683. * @static
  14684. * @memberOf fabric.Polygon
  14685. * @see: http://www.w3.org/TR/SVG/shapes.html#PolygonElement
  14686. */
  14687. fabric.Polygon.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat();
  14688. /**
  14689. * Returns {@link fabric.Polygon} instance from an SVG element
  14690. * @static
  14691. * @memberOf fabric.Polygon
  14692. * @param {SVGElement} element Element to parse
  14693. * @param {Object} [options] Options object
  14694. * @return {fabric.Polygon} Instance of fabric.Polygon
  14695. */
  14696. fabric.Polygon.fromElement = function(element, options) {
  14697. if (!element) {
  14698. return null;
  14699. }
  14700. options || (options = { });
  14701. var points = fabric.parsePointsAttribute(element.getAttribute('points')),
  14702. parsedAttributes = fabric.parseAttributes(element, fabric.Polygon.ATTRIBUTE_NAMES);
  14703. return new fabric.Polygon(points, extend(parsedAttributes, options));
  14704. };
  14705. /* _FROM_SVG_END_ */
  14706. /**
  14707. * Returns fabric.Polygon instance from an object representation
  14708. * @static
  14709. * @memberOf fabric.Polygon
  14710. * @param {Object} object Object to create an instance from
  14711. * @param {Function} [callback] Callback to invoke when an fabric.Path instance is created
  14712. * @param {Boolean} [forceAsync] Force an async behaviour trying to create pattern first
  14713. * @return {fabric.Polygon} Instance of fabric.Polygon
  14714. */
  14715. fabric.Polygon.fromObject = function(object, callback, forceAsync) {
  14716. return fabric.Object._fromObject('Polygon', object, callback, forceAsync, 'points');
  14717. };
  14718. })(typeof exports !== 'undefined' ? exports : this);
  14719. (function(global) {
  14720. 'use strict';
  14721. var fabric = global.fabric || (global.fabric = { }),
  14722. min = fabric.util.array.min,
  14723. max = fabric.util.array.max,
  14724. extend = fabric.util.object.extend,
  14725. _toString = Object.prototype.toString,
  14726. drawArc = fabric.util.drawArc,
  14727. commandLengths = {
  14728. m: 2,
  14729. l: 2,
  14730. h: 1,
  14731. v: 1,
  14732. c: 6,
  14733. s: 4,
  14734. q: 4,
  14735. t: 2,
  14736. a: 7
  14737. },
  14738. repeatedCommands = {
  14739. m: 'l',
  14740. M: 'L'
  14741. };
  14742. if (fabric.Path) {
  14743. fabric.warn('fabric.Path is already defined');
  14744. return;
  14745. }
  14746. var cacheProperties = fabric.Object.prototype.cacheProperties.concat();
  14747. cacheProperties.push('path');
  14748. /**
  14749. * Path class
  14750. * @class fabric.Path
  14751. * @extends fabric.Object
  14752. * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#path_and_pathgroup}
  14753. * @see {@link fabric.Path#initialize} for constructor definition
  14754. */
  14755. fabric.Path = fabric.util.createClass(fabric.Object, /** @lends fabric.Path.prototype */ {
  14756. /**
  14757. * Type of an object
  14758. * @type String
  14759. * @default
  14760. */
  14761. type: 'path',
  14762. /**
  14763. * Array of path points
  14764. * @type Array
  14765. * @default
  14766. */
  14767. path: null,
  14768. /**
  14769. * Minimum X from points values, necessary to offset points
  14770. * @type Number
  14771. * @default
  14772. */
  14773. minX: 0,
  14774. /**
  14775. * Minimum Y from points values, necessary to offset points
  14776. * @type Number
  14777. * @default
  14778. */
  14779. minY: 0,
  14780. cacheProperties: cacheProperties,
  14781. /**
  14782. * Constructor
  14783. * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens)
  14784. * @param {Object} [options] Options object
  14785. * @return {fabric.Path} thisArg
  14786. */
  14787. initialize: function(path, options) {
  14788. options = options || { };
  14789. if (options) {
  14790. this.setOptions(options);
  14791. }
  14792. if (!path) {
  14793. path = [];
  14794. }
  14795. var fromArray = _toString.call(path) === '[object Array]';
  14796. this.path = fromArray
  14797. ? path
  14798. // one of commands (m,M,l,L,q,Q,c,C,etc.) followed by non-command characters (i.e. command values)
  14799. : path.match && path.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi);
  14800. if (!this.path) {
  14801. return;
  14802. }
  14803. if (!fromArray) {
  14804. this.path = this._parsePath();
  14805. }
  14806. this._setPositionDimensions(options);
  14807. if (options.sourcePath) {
  14808. this.setSourcePath(options.sourcePath);
  14809. }
  14810. if (this.objectCaching) {
  14811. this._createCacheCanvas();
  14812. this.setupState({ propertySet: 'cacheProperties' });
  14813. }
  14814. },
  14815. /**
  14816. * @private
  14817. * @param {Object} options Options object
  14818. */
  14819. _setPositionDimensions: function(options) {
  14820. var calcDim = this._parseDimensions();
  14821. this.minX = calcDim.left;
  14822. this.minY = calcDim.top;
  14823. this.width = calcDim.width;
  14824. this.height = calcDim.height;
  14825. if (typeof options.left === 'undefined') {
  14826. this.left = calcDim.left + (this.originX === 'center'
  14827. ? this.width / 2
  14828. : this.originX === 'right'
  14829. ? this.width
  14830. : 0);
  14831. }
  14832. if (typeof options.top === 'undefined') {
  14833. this.top = calcDim.top + (this.originY === 'center'
  14834. ? this.height / 2
  14835. : this.originY === 'bottom'
  14836. ? this.height
  14837. : 0);
  14838. }
  14839. this.pathOffset = this.pathOffset || {
  14840. x: this.minX + this.width / 2,
  14841. y: this.minY + this.height / 2
  14842. };
  14843. },
  14844. /**
  14845. * @private
  14846. * @param {CanvasRenderingContext2D} ctx context to render path on
  14847. */
  14848. _renderPathCommands: function(ctx) {
  14849. var current, // current instruction
  14850. previous = null,
  14851. subpathStartX = 0,
  14852. subpathStartY = 0,
  14853. x = 0, // current x
  14854. y = 0, // current y
  14855. controlX = 0, // current control point x
  14856. controlY = 0, // current control point y
  14857. tempX,
  14858. tempY,
  14859. l = -this.pathOffset.x,
  14860. t = -this.pathOffset.y;
  14861. if (this.group && this.group.type === 'path-group') {
  14862. l = 0;
  14863. t = 0;
  14864. }
  14865. ctx.beginPath();
  14866. for (var i = 0, len = this.path.length; i < len; ++i) {
  14867. current = this.path[i];
  14868. switch (current[0]) { // first letter
  14869. case 'l': // lineto, relative
  14870. x += current[1];
  14871. y += current[2];
  14872. ctx.lineTo(x + l, y + t);
  14873. break;
  14874. case 'L': // lineto, absolute
  14875. x = current[1];
  14876. y = current[2];
  14877. ctx.lineTo(x + l, y + t);
  14878. break;
  14879. case 'h': // horizontal lineto, relative
  14880. x += current[1];
  14881. ctx.lineTo(x + l, y + t);
  14882. break;
  14883. case 'H': // horizontal lineto, absolute
  14884. x = current[1];
  14885. ctx.lineTo(x + l, y + t);
  14886. break;
  14887. case 'v': // vertical lineto, relative
  14888. y += current[1];
  14889. ctx.lineTo(x + l, y + t);
  14890. break;
  14891. case 'V': // verical lineto, absolute
  14892. y = current[1];
  14893. ctx.lineTo(x + l, y + t);
  14894. break;
  14895. case 'm': // moveTo, relative
  14896. x += current[1];
  14897. y += current[2];
  14898. subpathStartX = x;
  14899. subpathStartY = y;
  14900. ctx.moveTo(x + l, y + t);
  14901. break;
  14902. case 'M': // moveTo, absolute
  14903. x = current[1];
  14904. y = current[2];
  14905. subpathStartX = x;
  14906. subpathStartY = y;
  14907. ctx.moveTo(x + l, y + t);
  14908. break;
  14909. case 'c': // bezierCurveTo, relative
  14910. tempX = x + current[5];
  14911. tempY = y + current[6];
  14912. controlX = x + current[3];
  14913. controlY = y + current[4];
  14914. ctx.bezierCurveTo(
  14915. x + current[1] + l, // x1
  14916. y + current[2] + t, // y1
  14917. controlX + l, // x2
  14918. controlY + t, // y2
  14919. tempX + l,
  14920. tempY + t
  14921. );
  14922. x = tempX;
  14923. y = tempY;
  14924. break;
  14925. case 'C': // bezierCurveTo, absolute
  14926. x = current[5];
  14927. y = current[6];
  14928. controlX = current[3];
  14929. controlY = current[4];
  14930. ctx.bezierCurveTo(
  14931. current[1] + l,
  14932. current[2] + t,
  14933. controlX + l,
  14934. controlY + t,
  14935. x + l,
  14936. y + t
  14937. );
  14938. break;
  14939. case 's': // shorthand cubic bezierCurveTo, relative
  14940. // transform to absolute x,y
  14941. tempX = x + current[3];
  14942. tempY = y + current[4];
  14943. if (previous[0].match(/[CcSs]/) === null) {
  14944. // If there is no previous command or if the previous command was not a C, c, S, or s,
  14945. // the control point is coincident with the current point
  14946. controlX = x;
  14947. controlY = y;
  14948. }
  14949. else {
  14950. // calculate reflection of previous control points
  14951. controlX = 2 * x - controlX;
  14952. controlY = 2 * y - controlY;
  14953. }
  14954. ctx.bezierCurveTo(
  14955. controlX + l,
  14956. controlY + t,
  14957. x + current[1] + l,
  14958. y + current[2] + t,
  14959. tempX + l,
  14960. tempY + t
  14961. );
  14962. // set control point to 2nd one of this command
  14963. // "... the first control point is assumed to be
  14964. // the reflection of the second control point on
  14965. // the previous command relative to the current point."
  14966. controlX = x + current[1];
  14967. controlY = y + current[2];
  14968. x = tempX;
  14969. y = tempY;
  14970. break;
  14971. case 'S': // shorthand cubic bezierCurveTo, absolute
  14972. tempX = current[3];
  14973. tempY = current[4];
  14974. if (previous[0].match(/[CcSs]/) === null) {
  14975. // If there is no previous command or if the previous command was not a C, c, S, or s,
  14976. // the control point is coincident with the current point
  14977. controlX = x;
  14978. controlY = y;
  14979. }
  14980. else {
  14981. // calculate reflection of previous control points
  14982. controlX = 2 * x - controlX;
  14983. controlY = 2 * y - controlY;
  14984. }
  14985. ctx.bezierCurveTo(
  14986. controlX + l,
  14987. controlY + t,
  14988. current[1] + l,
  14989. current[2] + t,
  14990. tempX + l,
  14991. tempY + t
  14992. );
  14993. x = tempX;
  14994. y = tempY;
  14995. // set control point to 2nd one of this command
  14996. // "... the first control point is assumed to be
  14997. // the reflection of the second control point on
  14998. // the previous command relative to the current point."
  14999. controlX = current[1];
  15000. controlY = current[2];
  15001. break;
  15002. case 'q': // quadraticCurveTo, relative
  15003. // transform to absolute x,y
  15004. tempX = x + current[3];
  15005. tempY = y + current[4];
  15006. controlX = x + current[1];
  15007. controlY = y + current[2];
  15008. ctx.quadraticCurveTo(
  15009. controlX + l,
  15010. controlY + t,
  15011. tempX + l,
  15012. tempY + t
  15013. );
  15014. x = tempX;
  15015. y = tempY;
  15016. break;
  15017. case 'Q': // quadraticCurveTo, absolute
  15018. tempX = current[3];
  15019. tempY = current[4];
  15020. ctx.quadraticCurveTo(
  15021. current[1] + l,
  15022. current[2] + t,
  15023. tempX + l,
  15024. tempY + t
  15025. );
  15026. x = tempX;
  15027. y = tempY;
  15028. controlX = current[1];
  15029. controlY = current[2];
  15030. break;
  15031. case 't': // shorthand quadraticCurveTo, relative
  15032. // transform to absolute x,y
  15033. tempX = x + current[1];
  15034. tempY = y + current[2];
  15035. if (previous[0].match(/[QqTt]/) === null) {
  15036. // If there is no previous command or if the previous command was not a Q, q, T or t,
  15037. // assume the control point is coincident with the current point
  15038. controlX = x;
  15039. controlY = y;
  15040. }
  15041. else {
  15042. // calculate reflection of previous control point
  15043. controlX = 2 * x - controlX;
  15044. controlY = 2 * y - controlY;
  15045. }
  15046. ctx.quadraticCurveTo(
  15047. controlX + l,
  15048. controlY + t,
  15049. tempX + l,
  15050. tempY + t
  15051. );
  15052. x = tempX;
  15053. y = tempY;
  15054. break;
  15055. case 'T':
  15056. tempX = current[1];
  15057. tempY = current[2];
  15058. if (previous[0].match(/[QqTt]/) === null) {
  15059. // If there is no previous command or if the previous command was not a Q, q, T or t,
  15060. // assume the control point is coincident with the current point
  15061. controlX = x;
  15062. controlY = y;
  15063. }
  15064. else {
  15065. // calculate reflection of previous control point
  15066. controlX = 2 * x - controlX;
  15067. controlY = 2 * y - controlY;
  15068. }
  15069. ctx.quadraticCurveTo(
  15070. controlX + l,
  15071. controlY + t,
  15072. tempX + l,
  15073. tempY + t
  15074. );
  15075. x = tempX;
  15076. y = tempY;
  15077. break;
  15078. case 'a':
  15079. // TODO: optimize this
  15080. drawArc(ctx, x + l, y + t, [
  15081. current[1],
  15082. current[2],
  15083. current[3],
  15084. current[4],
  15085. current[5],
  15086. current[6] + x + l,
  15087. current[7] + y + t
  15088. ]);
  15089. x += current[6];
  15090. y += current[7];
  15091. break;
  15092. case 'A':
  15093. // TODO: optimize this
  15094. drawArc(ctx, x + l, y + t, [
  15095. current[1],
  15096. current[2],
  15097. current[3],
  15098. current[4],
  15099. current[5],
  15100. current[6] + l,
  15101. current[7] + t
  15102. ]);
  15103. x = current[6];
  15104. y = current[7];
  15105. break;
  15106. case 'z':
  15107. case 'Z':
  15108. x = subpathStartX;
  15109. y = subpathStartY;
  15110. ctx.closePath();
  15111. break;
  15112. }
  15113. previous = current;
  15114. }
  15115. },
  15116. /**
  15117. * @private
  15118. * @param {CanvasRenderingContext2D} ctx context to render path on
  15119. */
  15120. _render: function(ctx) {
  15121. this._renderPathCommands(ctx);
  15122. this._renderFill(ctx);
  15123. this._renderStroke(ctx);
  15124. },
  15125. /**
  15126. * Returns string representation of an instance
  15127. * @return {String} string representation of an instance
  15128. */
  15129. toString: function() {
  15130. return '#<fabric.Path (' + this.complexity() +
  15131. '): { "top": ' + this.top + ', "left": ' + this.left + ' }>';
  15132. },
  15133. /**
  15134. * Returns object representation of an instance
  15135. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  15136. * @return {Object} object representation of an instance
  15137. */
  15138. toObject: function(propertiesToInclude) {
  15139. var o = extend(this.callSuper('toObject', ['sourcePath', 'pathOffset'].concat(propertiesToInclude)), {
  15140. path: this.path.map(function(item) { return item.slice(); }),
  15141. top: this.top,
  15142. left: this.left,
  15143. });
  15144. return o;
  15145. },
  15146. /**
  15147. * Returns dataless object representation of an instance
  15148. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  15149. * @return {Object} object representation of an instance
  15150. */
  15151. toDatalessObject: function(propertiesToInclude) {
  15152. var o = this.toObject(propertiesToInclude);
  15153. if (this.sourcePath) {
  15154. o.path = this.sourcePath;
  15155. }
  15156. delete o.sourcePath;
  15157. return o;
  15158. },
  15159. /* _TO_SVG_START_ */
  15160. /**
  15161. * Returns svg representation of an instance
  15162. * @param {Function} [reviver] Method for further parsing of svg representation.
  15163. * @return {String} svg representation of an instance
  15164. */
  15165. toSVG: function(reviver) {
  15166. var chunks = [],
  15167. markup = this._createBaseSVGMarkup(), addTransform = '';
  15168. for (var i = 0, len = this.path.length; i < len; i++) {
  15169. chunks.push(this.path[i].join(' '));
  15170. }
  15171. var path = chunks.join(' ');
  15172. if (!(this.group && this.group.type === 'path-group')) {
  15173. addTransform = ' translate(' + (-this.pathOffset.x) + ', ' + (-this.pathOffset.y) + ') ';
  15174. }
  15175. markup.push(
  15176. '<path ', this.getSvgId(),
  15177. 'd="', path,
  15178. '" style="', this.getSvgStyles(),
  15179. '" transform="', this.getSvgTransform(), addTransform,
  15180. this.getSvgTransformMatrix(), '" stroke-linecap="round" ',
  15181. '/>\n'
  15182. );
  15183. return reviver ? reviver(markup.join('')) : markup.join('');
  15184. },
  15185. /* _TO_SVG_END_ */
  15186. /**
  15187. * Returns number representation of an instance complexity
  15188. * @return {Number} complexity of this instance
  15189. */
  15190. complexity: function() {
  15191. return this.path.length;
  15192. },
  15193. /**
  15194. * @private
  15195. */
  15196. _parsePath: function() {
  15197. var result = [],
  15198. coords = [],
  15199. currentPath,
  15200. parsed,
  15201. re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/ig,
  15202. match,
  15203. coordsStr;
  15204. for (var i = 0, coordsParsed, len = this.path.length; i < len; i++) {
  15205. currentPath = this.path[i];
  15206. coordsStr = currentPath.slice(1).trim();
  15207. coords.length = 0;
  15208. while ((match = re.exec(coordsStr))) {
  15209. coords.push(match[0]);
  15210. }
  15211. coordsParsed = [currentPath.charAt(0)];
  15212. for (var j = 0, jlen = coords.length; j < jlen; j++) {
  15213. parsed = parseFloat(coords[j]);
  15214. if (!isNaN(parsed)) {
  15215. coordsParsed.push(parsed);
  15216. }
  15217. }
  15218. var command = coordsParsed[0],
  15219. commandLength = commandLengths[command.toLowerCase()],
  15220. repeatedCommand = repeatedCommands[command] || command;
  15221. if (coordsParsed.length - 1 > commandLength) {
  15222. for (var k = 1, klen = coordsParsed.length; k < klen; k += commandLength) {
  15223. result.push([command].concat(coordsParsed.slice(k, k + commandLength)));
  15224. command = repeatedCommand;
  15225. }
  15226. }
  15227. else {
  15228. result.push(coordsParsed);
  15229. }
  15230. }
  15231. return result;
  15232. },
  15233. /**
  15234. * @private
  15235. */
  15236. _parseDimensions: function() {
  15237. var aX = [],
  15238. aY = [],
  15239. current, // current instruction
  15240. previous = null,
  15241. subpathStartX = 0,
  15242. subpathStartY = 0,
  15243. x = 0, // current x
  15244. y = 0, // current y
  15245. controlX = 0, // current control point x
  15246. controlY = 0, // current control point y
  15247. tempX,
  15248. tempY,
  15249. bounds;
  15250. for (var i = 0, len = this.path.length; i < len; ++i) {
  15251. current = this.path[i];
  15252. switch (current[0]) { // first letter
  15253. case 'l': // lineto, relative
  15254. x += current[1];
  15255. y += current[2];
  15256. bounds = [];
  15257. break;
  15258. case 'L': // lineto, absolute
  15259. x = current[1];
  15260. y = current[2];
  15261. bounds = [];
  15262. break;
  15263. case 'h': // horizontal lineto, relative
  15264. x += current[1];
  15265. bounds = [];
  15266. break;
  15267. case 'H': // horizontal lineto, absolute
  15268. x = current[1];
  15269. bounds = [];
  15270. break;
  15271. case 'v': // vertical lineto, relative
  15272. y += current[1];
  15273. bounds = [];
  15274. break;
  15275. case 'V': // verical lineto, absolute
  15276. y = current[1];
  15277. bounds = [];
  15278. break;
  15279. case 'm': // moveTo, relative
  15280. x += current[1];
  15281. y += current[2];
  15282. subpathStartX = x;
  15283. subpathStartY = y;
  15284. bounds = [];
  15285. break;
  15286. case 'M': // moveTo, absolute
  15287. x = current[1];
  15288. y = current[2];
  15289. subpathStartX = x;
  15290. subpathStartY = y;
  15291. bounds = [];
  15292. break;
  15293. case 'c': // bezierCurveTo, relative
  15294. tempX = x + current[5];
  15295. tempY = y + current[6];
  15296. controlX = x + current[3];
  15297. controlY = y + current[4];
  15298. bounds = fabric.util.getBoundsOfCurve(x, y,
  15299. x + current[1], // x1
  15300. y + current[2], // y1
  15301. controlX, // x2
  15302. controlY, // y2
  15303. tempX,
  15304. tempY
  15305. );
  15306. x = tempX;
  15307. y = tempY;
  15308. break;
  15309. case 'C': // bezierCurveTo, absolute
  15310. controlX = current[3];
  15311. controlY = current[4];
  15312. bounds = fabric.util.getBoundsOfCurve(x, y,
  15313. current[1],
  15314. current[2],
  15315. controlX,
  15316. controlY,
  15317. current[5],
  15318. current[6]
  15319. );
  15320. x = current[5];
  15321. y = current[6];
  15322. break;
  15323. case 's': // shorthand cubic bezierCurveTo, relative
  15324. // transform to absolute x,y
  15325. tempX = x + current[3];
  15326. tempY = y + current[4];
  15327. if (previous[0].match(/[CcSs]/) === null) {
  15328. // If there is no previous command or if the previous command was not a C, c, S, or s,
  15329. // the control point is coincident with the current point
  15330. controlX = x;
  15331. controlY = y;
  15332. }
  15333. else {
  15334. // calculate reflection of previous control points
  15335. controlX = 2 * x - controlX;
  15336. controlY = 2 * y - controlY;
  15337. }
  15338. bounds = fabric.util.getBoundsOfCurve(x, y,
  15339. controlX,
  15340. controlY,
  15341. x + current[1],
  15342. y + current[2],
  15343. tempX,
  15344. tempY
  15345. );
  15346. // set control point to 2nd one of this command
  15347. // "... the first control point is assumed to be
  15348. // the reflection of the second control point on
  15349. // the previous command relative to the current point."
  15350. controlX = x + current[1];
  15351. controlY = y + current[2];
  15352. x = tempX;
  15353. y = tempY;
  15354. break;
  15355. case 'S': // shorthand cubic bezierCurveTo, absolute
  15356. tempX = current[3];
  15357. tempY = current[4];
  15358. if (previous[0].match(/[CcSs]/) === null) {
  15359. // If there is no previous command or if the previous command was not a C, c, S, or s,
  15360. // the control point is coincident with the current point
  15361. controlX = x;
  15362. controlY = y;
  15363. }
  15364. else {
  15365. // calculate reflection of previous control points
  15366. controlX = 2 * x - controlX;
  15367. controlY = 2 * y - controlY;
  15368. }
  15369. bounds = fabric.util.getBoundsOfCurve(x, y,
  15370. controlX,
  15371. controlY,
  15372. current[1],
  15373. current[2],
  15374. tempX,
  15375. tempY
  15376. );
  15377. x = tempX;
  15378. y = tempY;
  15379. // set control point to 2nd one of this command
  15380. // "... the first control point is assumed to be
  15381. // the reflection of the second control point on
  15382. // the previous command relative to the current point."
  15383. controlX = current[1];
  15384. controlY = current[2];
  15385. break;
  15386. case 'q': // quadraticCurveTo, relative
  15387. // transform to absolute x,y
  15388. tempX = x + current[3];
  15389. tempY = y + current[4];
  15390. controlX = x + current[1];
  15391. controlY = y + current[2];
  15392. bounds = fabric.util.getBoundsOfCurve(x, y,
  15393. controlX,
  15394. controlY,
  15395. controlX,
  15396. controlY,
  15397. tempX,
  15398. tempY
  15399. );
  15400. x = tempX;
  15401. y = tempY;
  15402. break;
  15403. case 'Q': // quadraticCurveTo, absolute
  15404. controlX = current[1];
  15405. controlY = current[2];
  15406. bounds = fabric.util.getBoundsOfCurve(x, y,
  15407. controlX,
  15408. controlY,
  15409. controlX,
  15410. controlY,
  15411. current[3],
  15412. current[4]
  15413. );
  15414. x = current[3];
  15415. y = current[4];
  15416. break;
  15417. case 't': // shorthand quadraticCurveTo, relative
  15418. // transform to absolute x,y
  15419. tempX = x + current[1];
  15420. tempY = y + current[2];
  15421. if (previous[0].match(/[QqTt]/) === null) {
  15422. // If there is no previous command or if the previous command was not a Q, q, T or t,
  15423. // assume the control point is coincident with the current point
  15424. controlX = x;
  15425. controlY = y;
  15426. }
  15427. else {
  15428. // calculate reflection of previous control point
  15429. controlX = 2 * x - controlX;
  15430. controlY = 2 * y - controlY;
  15431. }
  15432. bounds = fabric.util.getBoundsOfCurve(x, y,
  15433. controlX,
  15434. controlY,
  15435. controlX,
  15436. controlY,
  15437. tempX,
  15438. tempY
  15439. );
  15440. x = tempX;
  15441. y = tempY;
  15442. break;
  15443. case 'T':
  15444. tempX = current[1];
  15445. tempY = current[2];
  15446. if (previous[0].match(/[QqTt]/) === null) {
  15447. // If there is no previous command or if the previous command was not a Q, q, T or t,
  15448. // assume the control point is coincident with the current point
  15449. controlX = x;
  15450. controlY = y;
  15451. }
  15452. else {
  15453. // calculate reflection of previous control point
  15454. controlX = 2 * x - controlX;
  15455. controlY = 2 * y - controlY;
  15456. }
  15457. bounds = fabric.util.getBoundsOfCurve(x, y,
  15458. controlX,
  15459. controlY,
  15460. controlX,
  15461. controlY,
  15462. tempX,
  15463. tempY
  15464. );
  15465. x = tempX;
  15466. y = tempY;
  15467. break;
  15468. case 'a':
  15469. // TODO: optimize this
  15470. bounds = fabric.util.getBoundsOfArc(x, y,
  15471. current[1],
  15472. current[2],
  15473. current[3],
  15474. current[4],
  15475. current[5],
  15476. current[6] + x,
  15477. current[7] + y
  15478. );
  15479. x += current[6];
  15480. y += current[7];
  15481. break;
  15482. case 'A':
  15483. // TODO: optimize this
  15484. bounds = fabric.util.getBoundsOfArc(x, y,
  15485. current[1],
  15486. current[2],
  15487. current[3],
  15488. current[4],
  15489. current[5],
  15490. current[6],
  15491. current[7]
  15492. );
  15493. x = current[6];
  15494. y = current[7];
  15495. break;
  15496. case 'z':
  15497. case 'Z':
  15498. x = subpathStartX;
  15499. y = subpathStartY;
  15500. break;
  15501. }
  15502. previous = current;
  15503. bounds.forEach(function (point) {
  15504. aX.push(point.x);
  15505. aY.push(point.y);
  15506. });
  15507. aX.push(x);
  15508. aY.push(y);
  15509. }
  15510. var minX = min(aX) || 0,
  15511. minY = min(aY) || 0,
  15512. maxX = max(aX) || 0,
  15513. maxY = max(aY) || 0,
  15514. deltaX = maxX - minX,
  15515. deltaY = maxY - minY,
  15516. o = {
  15517. left: minX,
  15518. top: minY,
  15519. width: deltaX,
  15520. height: deltaY
  15521. };
  15522. return o;
  15523. }
  15524. });
  15525. /**
  15526. * Creates an instance of fabric.Path from an object
  15527. * @static
  15528. * @memberOf fabric.Path
  15529. * @param {Object} object
  15530. * @param {Function} [callback] Callback to invoke when an fabric.Path instance is created
  15531. * @param {Boolean} [forceAsync] Force an async behaviour trying to create pattern first
  15532. */
  15533. fabric.Path.fromObject = function(object, callback, forceAsync) {
  15534. // remove this pattern rom 2.0, accept just object.
  15535. var path;
  15536. if (typeof object.path === 'string') {
  15537. fabric.loadSVGFromURL(object.path, function (elements) {
  15538. var pathUrl = object.path;
  15539. path = elements[0];
  15540. delete object.path;
  15541. fabric.util.object.extend(path, object);
  15542. path.setSourcePath(pathUrl);
  15543. callback && callback(path);
  15544. });
  15545. }
  15546. else {
  15547. return fabric.Object._fromObject('Path', object, callback, forceAsync, 'path');
  15548. }
  15549. };
  15550. /* _FROM_SVG_START_ */
  15551. /**
  15552. * List of attribute names to account for when parsing SVG element (used by `fabric.Path.fromElement`)
  15553. * @static
  15554. * @memberOf fabric.Path
  15555. * @see http://www.w3.org/TR/SVG/paths.html#PathElement
  15556. */
  15557. fabric.Path.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(['d']);
  15558. /**
  15559. * Creates an instance of fabric.Path from an SVG <path> element
  15560. * @static
  15561. * @memberOf fabric.Path
  15562. * @param {SVGElement} element to parse
  15563. * @param {Function} callback Callback to invoke when an fabric.Path instance is created
  15564. * @param {Object} [options] Options object
  15565. */
  15566. fabric.Path.fromElement = function(element, callback, options) {
  15567. var parsedAttributes = fabric.parseAttributes(element, fabric.Path.ATTRIBUTE_NAMES);
  15568. callback && callback(new fabric.Path(parsedAttributes.d, extend(parsedAttributes, options)));
  15569. };
  15570. /* _FROM_SVG_END_ */
  15571. /**
  15572. * Indicates that instances of this type are async
  15573. * @static
  15574. * @memberOf fabric.Path
  15575. * @type Boolean
  15576. * @default
  15577. */
  15578. fabric.Path.async = true;
  15579. })(typeof exports !== 'undefined' ? exports : this);
  15580. (function(global) {
  15581. 'use strict';
  15582. var fabric = global.fabric || (global.fabric = { }),
  15583. extend = fabric.util.object.extend;
  15584. if (fabric.PathGroup) {
  15585. fabric.warn('fabric.PathGroup is already defined');
  15586. return;
  15587. }
  15588. /**
  15589. * Path group class
  15590. * @class fabric.PathGroup
  15591. * @extends fabric.Path
  15592. * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#path_and_pathgroup}
  15593. * @see {@link fabric.PathGroup#initialize} for constructor definition
  15594. */
  15595. fabric.PathGroup = fabric.util.createClass(fabric.Object, /** @lends fabric.PathGroup.prototype */ {
  15596. /**
  15597. * Type of an object
  15598. * @type String
  15599. * @default
  15600. */
  15601. type: 'path-group',
  15602. /**
  15603. * Fill value
  15604. * @type String
  15605. * @default
  15606. */
  15607. fill: '',
  15608. /**
  15609. * Constructor
  15610. * @param {Array} paths
  15611. * @param {Object} [options] Options object
  15612. * @return {fabric.PathGroup} thisArg
  15613. */
  15614. initialize: function(paths, options) {
  15615. options = options || { };
  15616. this.paths = paths || [];
  15617. for (var i = this.paths.length; i--;) {
  15618. this.paths[i].group = this;
  15619. }
  15620. if (options.toBeParsed) {
  15621. this.parseDimensionsFromPaths(options);
  15622. delete options.toBeParsed;
  15623. }
  15624. this.setOptions(options);
  15625. this.setCoords();
  15626. if (options.sourcePath) {
  15627. this.setSourcePath(options.sourcePath);
  15628. }
  15629. if (this.objectCaching) {
  15630. this._createCacheCanvas();
  15631. this.setupState({ propertySet: 'cacheProperties' });
  15632. }
  15633. },
  15634. /**
  15635. * Calculate width and height based on paths contained
  15636. */
  15637. parseDimensionsFromPaths: function(options) {
  15638. var points, p, xC = [], yC = [], path, height, width,
  15639. m;
  15640. for (var j = this.paths.length; j--;) {
  15641. path = this.paths[j];
  15642. height = path.height + path.strokeWidth;
  15643. width = path.width + path.strokeWidth;
  15644. points = [
  15645. { x: path.left, y: path.top },
  15646. { x: path.left + width, y: path.top },
  15647. { x: path.left, y: path.top + height },
  15648. { x: path.left + width, y: path.top + height }
  15649. ];
  15650. m = this.paths[j].transformMatrix;
  15651. for (var i = 0; i < points.length; i++) {
  15652. p = points[i];
  15653. if (m) {
  15654. p = fabric.util.transformPoint(p, m, false);
  15655. }
  15656. xC.push(p.x);
  15657. yC.push(p.y);
  15658. }
  15659. }
  15660. options.width = Math.max.apply(null, xC);
  15661. options.height = Math.max.apply(null, yC);
  15662. },
  15663. /**
  15664. * Execute the drawing operation for an object on a specified context
  15665. * @param {CanvasRenderingContext2D} ctx Context to render on
  15666. * @param {Boolean} [noTransform] When true, context is not transformed
  15667. */
  15668. drawObject: function(ctx) {
  15669. ctx.save();
  15670. ctx.translate(-this.width / 2, -this.height / 2);
  15671. for (var i = 0, l = this.paths.length; i < l; ++i) {
  15672. this.paths[i].render(ctx, true);
  15673. }
  15674. ctx.restore();
  15675. },
  15676. /**
  15677. * Check if cache is dirty
  15678. */
  15679. isCacheDirty: function() {
  15680. if (this.callSuper('isCacheDirty')) {
  15681. return true;
  15682. }
  15683. if (!this.statefullCache) {
  15684. return false;
  15685. }
  15686. for (var i = 0, len = this.paths.length; i < len; i++) {
  15687. if (this.paths[i].isCacheDirty(true)) {
  15688. var dim = this._getNonTransformedDimensions();
  15689. this._cacheContext.clearRect(-dim.x / 2, -dim.y / 2, dim.x, dim.y);
  15690. return true;
  15691. }
  15692. }
  15693. return false;
  15694. },
  15695. /**
  15696. * Sets certain property to a certain value
  15697. * @param {String} prop
  15698. * @param {*} value
  15699. * @return {fabric.PathGroup} thisArg
  15700. */
  15701. _set: function(prop, value) {
  15702. if (prop === 'fill' && value && this.isSameColor()) {
  15703. var i = this.paths.length;
  15704. while (i--) {
  15705. this.paths[i]._set(prop, value);
  15706. }
  15707. }
  15708. return this.callSuper('_set', prop, value);
  15709. },
  15710. /**
  15711. * Returns object representation of this path group
  15712. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  15713. * @return {Object} object representation of an instance
  15714. */
  15715. toObject: function(propertiesToInclude) {
  15716. var pathsToObject = this.paths.map(function(path) {
  15717. var originalDefaults = path.includeDefaultValues;
  15718. path.includeDefaultValues = path.group.includeDefaultValues;
  15719. var obj = path.toObject(propertiesToInclude);
  15720. path.includeDefaultValues = originalDefaults;
  15721. return obj;
  15722. });
  15723. var o = extend(this.callSuper('toObject', ['sourcePath'].concat(propertiesToInclude)), {
  15724. paths: pathsToObject
  15725. });
  15726. return o;
  15727. },
  15728. /**
  15729. * Returns dataless object representation of this path group
  15730. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  15731. * @return {Object} dataless object representation of an instance
  15732. */
  15733. toDatalessObject: function(propertiesToInclude) {
  15734. var o = this.toObject(propertiesToInclude);
  15735. if (this.sourcePath) {
  15736. o.paths = this.sourcePath;
  15737. }
  15738. return o;
  15739. },
  15740. /* _TO_SVG_START_ */
  15741. /**
  15742. * Returns svg representation of an instance
  15743. * @param {Function} [reviver] Method for further parsing of svg representation.
  15744. * @return {String} svg representation of an instance
  15745. */
  15746. toSVG: function(reviver) {
  15747. var objects = this.getObjects(),
  15748. p = this.getPointByOrigin('left', 'top'),
  15749. translatePart = 'translate(' + p.x + ' ' + p.y + ')',
  15750. markup = this._createBaseSVGMarkup();
  15751. markup.push(
  15752. '<g ', this.getSvgId(),
  15753. 'style="', this.getSvgStyles(), '" ',
  15754. 'transform="', this.getSvgTransformMatrix(), translatePart, this.getSvgTransform(), '" ',
  15755. '>\n'
  15756. );
  15757. for (var i = 0, len = objects.length; i < len; i++) {
  15758. markup.push('\t', objects[i].toSVG(reviver));
  15759. }
  15760. markup.push('</g>\n');
  15761. return reviver ? reviver(markup.join('')) : markup.join('');
  15762. },
  15763. /* _TO_SVG_END_ */
  15764. /**
  15765. * Returns a string representation of this path group
  15766. * @return {String} string representation of an object
  15767. */
  15768. toString: function() {
  15769. return '#<fabric.PathGroup (' + this.complexity() +
  15770. '): { top: ' + this.top + ', left: ' + this.left + ' }>';
  15771. },
  15772. /**
  15773. * Returns true if all paths in this group are of same color
  15774. * @return {Boolean} true if all paths are of the same color (`fill`)
  15775. */
  15776. isSameColor: function() {
  15777. var firstPathFill = this.getObjects()[0].get('fill') || '';
  15778. if (typeof firstPathFill !== 'string') {
  15779. return false;
  15780. }
  15781. firstPathFill = firstPathFill.toLowerCase();
  15782. return this.getObjects().every(function(path) {
  15783. var pathFill = path.get('fill') || '';
  15784. return typeof pathFill === 'string' && (pathFill).toLowerCase() === firstPathFill;
  15785. });
  15786. },
  15787. /**
  15788. * Returns number representation of object's complexity
  15789. * @return {Number} complexity
  15790. */
  15791. complexity: function() {
  15792. return this.paths.reduce(function(total, path) {
  15793. return total + ((path && path.complexity) ? path.complexity() : 0);
  15794. }, 0);
  15795. },
  15796. /**
  15797. * Returns all paths in this path group
  15798. * @return {Array} array of path objects included in this path group
  15799. */
  15800. getObjects: function() {
  15801. return this.paths;
  15802. }
  15803. });
  15804. /**
  15805. * Creates fabric.PathGroup instance from an object representation
  15806. * @static
  15807. * @memberOf fabric.PathGroup
  15808. * @param {Object} object Object to create an instance from
  15809. * @param {Function} [callback] Callback to invoke when an fabric.PathGroup instance is created
  15810. */
  15811. fabric.PathGroup.fromObject = function(object, callback) {
  15812. var originalPaths = object.paths;
  15813. delete object.paths;
  15814. // remove this pattern from 2.0 accepts only object
  15815. if (typeof originalPaths === 'string') {
  15816. fabric.loadSVGFromURL(originalPaths, function (elements) {
  15817. var pathUrl = originalPaths;
  15818. var pathGroup = fabric.util.groupSVGElements(elements, object, pathUrl);
  15819. object.paths = originalPaths;
  15820. callback(pathGroup);
  15821. });
  15822. }
  15823. else {
  15824. fabric.util.enlivenObjects(originalPaths, function(enlivenedObjects) {
  15825. var pathGroup = new fabric.PathGroup(enlivenedObjects, object);
  15826. object.paths = originalPaths;
  15827. callback(pathGroup);
  15828. });
  15829. }
  15830. };
  15831. /**
  15832. * Indicates that instances of this type are async
  15833. * @static
  15834. * @memberOf fabric.PathGroup
  15835. * @type Boolean
  15836. * @default
  15837. */
  15838. fabric.PathGroup.async = true;
  15839. })(typeof exports !== 'undefined' ? exports : this);
  15840. (function(global) {
  15841. 'use strict';
  15842. var fabric = global.fabric || (global.fabric = { }),
  15843. extend = fabric.util.object.extend,
  15844. min = fabric.util.array.min,
  15845. max = fabric.util.array.max;
  15846. if (fabric.Group) {
  15847. return;
  15848. }
  15849. // lock-related properties, for use in fabric.Group#get
  15850. // to enable locking behavior on group
  15851. // when one of its objects has lock-related properties set
  15852. var _lockProperties = {
  15853. lockMovementX: true,
  15854. lockMovementY: true,
  15855. lockRotation: true,
  15856. lockScalingX: true,
  15857. lockScalingY: true,
  15858. lockUniScaling: true
  15859. };
  15860. /**
  15861. * Group class
  15862. * @class fabric.Group
  15863. * @extends fabric.Object
  15864. * @mixes fabric.Collection
  15865. * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#groups}
  15866. * @see {@link fabric.Group#initialize} for constructor definition
  15867. */
  15868. fabric.Group = fabric.util.createClass(fabric.Object, fabric.Collection, /** @lends fabric.Group.prototype */ {
  15869. /**
  15870. * Type of an object
  15871. * @type String
  15872. * @default
  15873. */
  15874. type: 'group',
  15875. /**
  15876. * Width of stroke
  15877. * @type Number
  15878. * @default
  15879. */
  15880. strokeWidth: 0,
  15881. /**
  15882. * Indicates if click events should also check for subtargets
  15883. * @type Boolean
  15884. * @default
  15885. */
  15886. subTargetCheck: false,
  15887. /**
  15888. * Constructor
  15889. * @param {Object} objects Group objects
  15890. * @param {Object} [options] Options object
  15891. * @param {Boolean} [isAlreadyGrouped] if true, objects have been grouped already.
  15892. * @return {Object} thisArg
  15893. */
  15894. initialize: function(objects, options, isAlreadyGrouped) {
  15895. options = options || { };
  15896. this._objects = [];
  15897. // if objects enclosed in a group have been grouped already,
  15898. // we cannot change properties of objects.
  15899. // Thus we need to set options to group without objects,
  15900. // because delegatedProperties propagate to objects.
  15901. isAlreadyGrouped && this.callSuper('initialize', options);
  15902. this._objects = objects || [];
  15903. for (var i = this._objects.length; i--; ) {
  15904. this._objects[i].group = this;
  15905. }
  15906. this.originalState = { };
  15907. if (options.originX) {
  15908. this.originX = options.originX;
  15909. }
  15910. if (options.originY) {
  15911. this.originY = options.originY;
  15912. }
  15913. if (isAlreadyGrouped) {
  15914. // do not change coordinate of objects enclosed in a group,
  15915. // because objects coordinate system have been group coodinate system already.
  15916. this._updateObjectsCoords(true);
  15917. }
  15918. else {
  15919. this._calcBounds();
  15920. this._updateObjectsCoords();
  15921. this.callSuper('initialize', options);
  15922. }
  15923. this.setCoords();
  15924. this.saveCoords();
  15925. },
  15926. /**
  15927. * @private
  15928. * @param {Boolean} [skipCoordsChange] if true, coordinates of objects enclosed in a group do not change
  15929. */
  15930. _updateObjectsCoords: function(skipCoordsChange) {
  15931. var center = this.getCenterPoint();
  15932. for (var i = this._objects.length; i--; ){
  15933. this._updateObjectCoords(this._objects[i], center, skipCoordsChange);
  15934. }
  15935. },
  15936. /**
  15937. * @private
  15938. * @param {Object} object
  15939. * @param {fabric.Point} center, current center of group.
  15940. * @param {Boolean} [skipCoordsChange] if true, coordinates of object dose not change
  15941. */
  15942. _updateObjectCoords: function(object, center, skipCoordsChange) {
  15943. // do not display corners of objects enclosed in a group
  15944. object.__origHasControls = object.hasControls;
  15945. object.hasControls = false;
  15946. if (skipCoordsChange) {
  15947. return;
  15948. }
  15949. var objectLeft = object.getLeft(),
  15950. objectTop = object.getTop(),
  15951. ignoreZoom = true, skipAbsolute = true;
  15952. object.set({
  15953. left: objectLeft - center.x,
  15954. top: objectTop - center.y
  15955. });
  15956. object.setCoords(ignoreZoom, skipAbsolute);
  15957. },
  15958. /**
  15959. * Returns string represenation of a group
  15960. * @return {String}
  15961. */
  15962. toString: function() {
  15963. return '#<fabric.Group: (' + this.complexity() + ')>';
  15964. },
  15965. /**
  15966. * Adds an object to a group; Then recalculates group's dimension, position.
  15967. * @param {Object} object
  15968. * @return {fabric.Group} thisArg
  15969. * @chainable
  15970. */
  15971. addWithUpdate: function(object) {
  15972. this._restoreObjectsState();
  15973. fabric.util.resetObjectTransform(this);
  15974. if (object) {
  15975. this._objects.push(object);
  15976. object.group = this;
  15977. object._set('canvas', this.canvas);
  15978. }
  15979. // since _restoreObjectsState set objects inactive
  15980. this.forEachObject(this._setObjectActive, this);
  15981. this._calcBounds();
  15982. this._updateObjectsCoords();
  15983. this.dirty = true;
  15984. return this;
  15985. },
  15986. /**
  15987. * @private
  15988. */
  15989. _setObjectActive: function(object) {
  15990. object.set('active', true);
  15991. object.group = this;
  15992. },
  15993. /**
  15994. * Removes an object from a group; Then recalculates group's dimension, position.
  15995. * @param {Object} object
  15996. * @return {fabric.Group} thisArg
  15997. * @chainable
  15998. */
  15999. removeWithUpdate: function(object) {
  16000. this._restoreObjectsState();
  16001. fabric.util.resetObjectTransform(this);
  16002. // since _restoreObjectsState set objects inactive
  16003. this.forEachObject(this._setObjectActive, this);
  16004. this.remove(object);
  16005. this._calcBounds();
  16006. this._updateObjectsCoords();
  16007. this.dirty = true;
  16008. return this;
  16009. },
  16010. /**
  16011. * @private
  16012. */
  16013. _onObjectAdded: function(object) {
  16014. this.dirty = true;
  16015. object.group = this;
  16016. object._set('canvas', this.canvas);
  16017. },
  16018. /**
  16019. * @private
  16020. */
  16021. _onObjectRemoved: function(object) {
  16022. this.dirty = true;
  16023. delete object.group;
  16024. object.set('active', false);
  16025. },
  16026. /**
  16027. * Properties that are delegated to group objects when reading/writing
  16028. * @param {Object} delegatedProperties
  16029. */
  16030. delegatedProperties: {
  16031. fill: true,
  16032. stroke: true,
  16033. strokeWidth: true,
  16034. fontFamily: true,
  16035. fontWeight: true,
  16036. fontSize: true,
  16037. fontStyle: true,
  16038. lineHeight: true,
  16039. textDecoration: true,
  16040. textAlign: true,
  16041. backgroundColor: true
  16042. },
  16043. /**
  16044. * @private
  16045. */
  16046. _set: function(key, value) {
  16047. var i = this._objects.length;
  16048. if (this.delegatedProperties[key] || key === 'canvas') {
  16049. while (i--) {
  16050. this._objects[i].set(key, value);
  16051. }
  16052. }
  16053. else {
  16054. while (i--) {
  16055. this._objects[i].setOnGroup(key, value);
  16056. }
  16057. }
  16058. this.callSuper('_set', key, value);
  16059. },
  16060. /**
  16061. * Returns object representation of an instance
  16062. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  16063. * @return {Object} object representation of an instance
  16064. */
  16065. toObject: function(propertiesToInclude) {
  16066. var objsToObject = this.getObjects().map(function(obj) {
  16067. var originalDefaults = obj.includeDefaultValues;
  16068. obj.includeDefaultValues = obj.group.includeDefaultValues;
  16069. var _obj = obj.toObject(propertiesToInclude);
  16070. obj.includeDefaultValues = originalDefaults;
  16071. return _obj;
  16072. });
  16073. return extend(this.callSuper('toObject', propertiesToInclude), {
  16074. objects: objsToObject
  16075. });
  16076. },
  16077. /**
  16078. * Renders instance on a given context
  16079. * @param {CanvasRenderingContext2D} ctx context to render instance on
  16080. */
  16081. render: function(ctx) {
  16082. this._transformDone = true;
  16083. this.callSuper('render', ctx);
  16084. this._transformDone = false;
  16085. },
  16086. /**
  16087. * Execute the drawing operation for an object on a specified context
  16088. * @param {CanvasRenderingContext2D} ctx Context to render on
  16089. * @param {Boolean} [noTransform] When true, context is not transformed
  16090. */
  16091. drawObject: function(ctx) {
  16092. for (var i = 0, len = this._objects.length; i < len; i++) {
  16093. this._renderObject(this._objects[i], ctx);
  16094. }
  16095. },
  16096. /**
  16097. * Check if cache is dirty
  16098. */
  16099. isCacheDirty: function() {
  16100. if (this.callSuper('isCacheDirty')) {
  16101. return true;
  16102. }
  16103. if (!this.statefullCache) {
  16104. return false;
  16105. }
  16106. for (var i = 0, len = this._objects.length; i < len; i++) {
  16107. if (this._objects[i].isCacheDirty(true)) {
  16108. var dim = this._getNonTransformedDimensions();
  16109. this._cacheContext.clearRect(-dim.x / 2, -dim.y / 2, dim.x, dim.y);
  16110. return true;
  16111. }
  16112. }
  16113. return false;
  16114. },
  16115. /**
  16116. * Renders controls and borders for the object
  16117. * @param {CanvasRenderingContext2D} ctx Context to render on
  16118. * @param {Boolean} [noTransform] When true, context is not transformed
  16119. */
  16120. _renderControls: function(ctx, noTransform) {
  16121. ctx.save();
  16122. ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
  16123. this.callSuper('_renderControls', ctx, noTransform);
  16124. for (var i = 0, len = this._objects.length; i < len; i++) {
  16125. this._objects[i]._renderControls(ctx);
  16126. }
  16127. ctx.restore();
  16128. },
  16129. /**
  16130. * @private
  16131. */
  16132. _renderObject: function(object, ctx) {
  16133. // do not render if object is not visible
  16134. if (!object.visible) {
  16135. return;
  16136. }
  16137. var originalHasRotatingPoint = object.hasRotatingPoint;
  16138. object.hasRotatingPoint = false;
  16139. object.render(ctx);
  16140. object.hasRotatingPoint = originalHasRotatingPoint;
  16141. },
  16142. /**
  16143. * Retores original state of each of group objects (original state is that which was before group was created).
  16144. * @private
  16145. * @return {fabric.Group} thisArg
  16146. * @chainable
  16147. */
  16148. _restoreObjectsState: function() {
  16149. this._objects.forEach(this._restoreObjectState, this);
  16150. return this;
  16151. },
  16152. /**
  16153. * Realises the transform from this group onto the supplied object
  16154. * i.e. it tells you what would happen if the supplied object was in
  16155. * the group, and then the group was destroyed. It mutates the supplied
  16156. * object.
  16157. * @param {fabric.Object} object
  16158. * @return {fabric.Object} transformedObject
  16159. */
  16160. realizeTransform: function(object) {
  16161. var matrix = object.calcTransformMatrix(),
  16162. options = fabric.util.qrDecompose(matrix),
  16163. center = new fabric.Point(options.translateX, options.translateY);
  16164. object.flipX = false;
  16165. object.flipY = false;
  16166. object.set('scaleX', options.scaleX);
  16167. object.set('scaleY', options.scaleY);
  16168. object.skewX = options.skewX;
  16169. object.skewY = options.skewY;
  16170. object.angle = options.angle;
  16171. object.setPositionByOrigin(center, 'center', 'center');
  16172. return object;
  16173. },
  16174. /**
  16175. * Restores original state of a specified object in group
  16176. * @private
  16177. * @param {fabric.Object} object
  16178. * @return {fabric.Group} thisArg
  16179. */
  16180. _restoreObjectState: function(object) {
  16181. this.realizeTransform(object);
  16182. object.setCoords();
  16183. object.hasControls = object.__origHasControls;
  16184. delete object.__origHasControls;
  16185. object.set('active', false);
  16186. delete object.group;
  16187. return this;
  16188. },
  16189. /**
  16190. * Destroys a group (restoring state of its objects)
  16191. * @return {fabric.Group} thisArg
  16192. * @chainable
  16193. */
  16194. destroy: function() {
  16195. return this._restoreObjectsState();
  16196. },
  16197. /**
  16198. * Saves coordinates of this instance (to be used together with `hasMoved`)
  16199. * @saveCoords
  16200. * @return {fabric.Group} thisArg
  16201. * @chainable
  16202. */
  16203. saveCoords: function() {
  16204. this._originalLeft = this.get('left');
  16205. this._originalTop = this.get('top');
  16206. return this;
  16207. },
  16208. /**
  16209. * Checks whether this group was moved (since `saveCoords` was called last)
  16210. * @return {Boolean} true if an object was moved (since fabric.Group#saveCoords was called)
  16211. */
  16212. hasMoved: function() {
  16213. return this._originalLeft !== this.get('left') ||
  16214. this._originalTop !== this.get('top');
  16215. },
  16216. /**
  16217. * Sets coordinates of all objects inside group
  16218. * @return {fabric.Group} thisArg
  16219. * @chainable
  16220. */
  16221. setObjectsCoords: function() {
  16222. var ignoreZoom = true, skipAbsolute = true;
  16223. this.forEachObject(function(object) {
  16224. object.setCoords(ignoreZoom, skipAbsolute);
  16225. });
  16226. return this;
  16227. },
  16228. /**
  16229. * @private
  16230. */
  16231. _calcBounds: function(onlyWidthHeight) {
  16232. var aX = [],
  16233. aY = [],
  16234. o, prop,
  16235. props = ['tr', 'br', 'bl', 'tl'],
  16236. i = 0, iLen = this._objects.length,
  16237. j, jLen = props.length,
  16238. ignoreZoom = true;
  16239. for ( ; i < iLen; ++i) {
  16240. o = this._objects[i];
  16241. o.setCoords(ignoreZoom);
  16242. for (j = 0; j < jLen; j++) {
  16243. prop = props[j];
  16244. aX.push(o.oCoords[prop].x);
  16245. aY.push(o.oCoords[prop].y);
  16246. }
  16247. }
  16248. this.set(this._getBounds(aX, aY, onlyWidthHeight));
  16249. },
  16250. /**
  16251. * @private
  16252. */
  16253. _getBounds: function(aX, aY, onlyWidthHeight) {
  16254. var minXY = new fabric.Point(min(aX), min(aY)),
  16255. maxXY = new fabric.Point(max(aX), max(aY)),
  16256. obj = {
  16257. width: (maxXY.x - minXY.x) || 0,
  16258. height: (maxXY.y - minXY.y) || 0
  16259. };
  16260. if (!onlyWidthHeight) {
  16261. obj.left = minXY.x || 0;
  16262. obj.top = minXY.y || 0;
  16263. if (this.originX === 'center') {
  16264. obj.left += obj.width / 2;
  16265. }
  16266. if (this.originX === 'right') {
  16267. obj.left += obj.width;
  16268. }
  16269. if (this.originY === 'center') {
  16270. obj.top += obj.height / 2;
  16271. }
  16272. if (this.originY === 'bottom') {
  16273. obj.top += obj.height;
  16274. }
  16275. }
  16276. return obj;
  16277. },
  16278. /* _TO_SVG_START_ */
  16279. /**
  16280. * Returns svg representation of an instance
  16281. * @param {Function} [reviver] Method for further parsing of svg representation.
  16282. * @return {String} svg representation of an instance
  16283. */
  16284. toSVG: function(reviver) {
  16285. var markup = this._createBaseSVGMarkup();
  16286. markup.push(
  16287. '<g ', this.getSvgId(), 'transform="',
  16288. /* avoiding styles intentionally */
  16289. this.getSvgTransform(),
  16290. this.getSvgTransformMatrix(),
  16291. '" style="',
  16292. this.getSvgFilter(),
  16293. '">\n'
  16294. );
  16295. for (var i = 0, len = this._objects.length; i < len; i++) {
  16296. markup.push('\t', this._objects[i].toSVG(reviver));
  16297. }
  16298. markup.push('</g>\n');
  16299. return reviver ? reviver(markup.join('')) : markup.join('');
  16300. },
  16301. /* _TO_SVG_END_ */
  16302. /**
  16303. * Returns requested property
  16304. * @param {String} prop Property to get
  16305. * @return {*}
  16306. */
  16307. get: function(prop) {
  16308. if (prop in _lockProperties) {
  16309. if (this[prop]) {
  16310. return this[prop];
  16311. }
  16312. else {
  16313. for (var i = 0, len = this._objects.length; i < len; i++) {
  16314. if (this._objects[i][prop]) {
  16315. return true;
  16316. }
  16317. }
  16318. return false;
  16319. }
  16320. }
  16321. else {
  16322. if (prop in this.delegatedProperties) {
  16323. return this._objects[0] && this._objects[0].get(prop);
  16324. }
  16325. return this[prop];
  16326. }
  16327. }
  16328. });
  16329. /**
  16330. * Returns {@link fabric.Group} instance from an object representation
  16331. * @static
  16332. * @memberOf fabric.Group
  16333. * @param {Object} object Object to create a group from
  16334. * @param {Function} [callback] Callback to invoke when an group instance is created
  16335. */
  16336. fabric.Group.fromObject = function(object, callback) {
  16337. fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) {
  16338. delete object.objects;
  16339. callback && callback(new fabric.Group(enlivenedObjects, object, true));
  16340. });
  16341. };
  16342. /**
  16343. * Indicates that instances of this type are async
  16344. * @static
  16345. * @memberOf fabric.Group
  16346. * @type Boolean
  16347. * @default
  16348. */
  16349. fabric.Group.async = true;
  16350. })(typeof exports !== 'undefined' ? exports : this);
  16351. (function(global) {
  16352. 'use strict';
  16353. var extend = fabric.util.object.extend;
  16354. if (!global.fabric) {
  16355. global.fabric = { };
  16356. }
  16357. if (global.fabric.Image) {
  16358. fabric.warn('fabric.Image is already defined.');
  16359. return;
  16360. }
  16361. var stateProperties = fabric.Object.prototype.stateProperties.concat();
  16362. stateProperties.push(
  16363. 'alignX',
  16364. 'alignY',
  16365. 'meetOrSlice'
  16366. );
  16367. /**
  16368. * Image class
  16369. * @class fabric.Image
  16370. * @extends fabric.Object
  16371. * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#images}
  16372. * @see {@link fabric.Image#initialize} for constructor definition
  16373. */
  16374. fabric.Image = fabric.util.createClass(fabric.Object, /** @lends fabric.Image.prototype */ {
  16375. /**
  16376. * Type of an object
  16377. * @type String
  16378. * @default
  16379. */
  16380. type: 'image',
  16381. /**
  16382. * crossOrigin value (one of "", "anonymous", "use-credentials")
  16383. * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes
  16384. * @type String
  16385. * @default
  16386. */
  16387. crossOrigin: '',
  16388. /**
  16389. * AlignX value, part of preserveAspectRatio (one of "none", "mid", "min", "max")
  16390. * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
  16391. * This parameter defines how the picture is aligned to its viewport when image element width differs from image width.
  16392. * @type String
  16393. * @default
  16394. */
  16395. alignX: 'none',
  16396. /**
  16397. * AlignY value, part of preserveAspectRatio (one of "none", "mid", "min", "max")
  16398. * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
  16399. * This parameter defines how the picture is aligned to its viewport when image element height differs from image height.
  16400. * @type String
  16401. * @default
  16402. */
  16403. alignY: 'none',
  16404. /**
  16405. * meetOrSlice value, part of preserveAspectRatio (one of "meet", "slice").
  16406. * if meet the image is always fully visibile, if slice the viewport is always filled with image.
  16407. * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
  16408. * @type String
  16409. * @default
  16410. */
  16411. meetOrSlice: 'meet',
  16412. /**
  16413. * Width of a stroke.
  16414. * For image quality a stroke multiple of 2 gives better results.
  16415. * @type Number
  16416. * @default
  16417. */
  16418. strokeWidth: 0,
  16419. /**
  16420. * private
  16421. * contains last value of scaleX to detect
  16422. * if the Image got resized after the last Render
  16423. * @type Number
  16424. */
  16425. _lastScaleX: 1,
  16426. /**
  16427. * private
  16428. * contains last value of scaleY to detect
  16429. * if the Image got resized after the last Render
  16430. * @type Number
  16431. */
  16432. _lastScaleY: 1,
  16433. /**
  16434. * minimum scale factor under which any resizeFilter is triggered to resize the image
  16435. * 0 will disable the automatic resize. 1 will trigger automatically always.
  16436. * number bigger than 1 can be used in case we want to scale with some filter above
  16437. * the natural image dimensions
  16438. * @type Number
  16439. */
  16440. minimumScaleTrigger: 0.5,
  16441. /**
  16442. * List of properties to consider when checking if
  16443. * state of an object is changed ({@link fabric.Object#hasStateChanged})
  16444. * as well as for history (undo/redo) purposes
  16445. * @type Array
  16446. */
  16447. stateProperties: stateProperties,
  16448. /**
  16449. * When `true`, object is cached on an additional canvas.
  16450. * default to false for images
  16451. * since 1.7.0
  16452. * @type Boolean
  16453. * @default
  16454. */
  16455. objectCaching: false,
  16456. /**
  16457. * Constructor
  16458. * @param {HTMLImageElement | String} element Image element
  16459. * @param {Object} [options] Options object
  16460. * @param {function} [callback] callback function to call after eventual filters applied.
  16461. * @return {fabric.Image} thisArg
  16462. */
  16463. initialize: function(element, options, callback) {
  16464. options || (options = { });
  16465. this.filters = [];
  16466. this.resizeFilters = [];
  16467. this.callSuper('initialize', options);
  16468. this._initElement(element, options, callback);
  16469. },
  16470. /**
  16471. * Returns image element which this instance if based on
  16472. * @return {HTMLImageElement} Image element
  16473. */
  16474. getElement: function() {
  16475. return this._element;
  16476. },
  16477. /**
  16478. * Sets image element for this instance to a specified one.
  16479. * If filters defined they are applied to new image.
  16480. * You might need to call `canvas.renderAll` and `object.setCoords` after replacing, to render new image and update controls area.
  16481. * @param {HTMLImageElement} element
  16482. * @param {Function} [callback] Callback is invoked when all filters have been applied and new image is generated
  16483. * @param {Object} [options] Options object
  16484. * @return {fabric.Image} thisArg
  16485. * @chainable
  16486. */
  16487. setElement: function(element, callback, options) {
  16488. var _callback, _this;
  16489. this._element = element;
  16490. this._originalElement = element;
  16491. this._initConfig(options);
  16492. if (this.resizeFilters.length === 0) {
  16493. _callback = callback;
  16494. }
  16495. else {
  16496. _this = this;
  16497. _callback = function() {
  16498. _this.applyFilters(callback, _this.resizeFilters, _this._filteredEl || _this._originalElement, true);
  16499. };
  16500. }
  16501. if (this.filters.length !== 0) {
  16502. this.applyFilters(_callback);
  16503. }
  16504. else if (_callback) {
  16505. _callback(this);
  16506. }
  16507. return this;
  16508. },
  16509. /**
  16510. * Sets crossOrigin value (on an instance and corresponding image element)
  16511. * @return {fabric.Image} thisArg
  16512. * @chainable
  16513. */
  16514. setCrossOrigin: function(value) {
  16515. this.crossOrigin = value;
  16516. this._element.crossOrigin = value;
  16517. return this;
  16518. },
  16519. /**
  16520. * Returns original size of an image
  16521. * @return {Object} Object with "width" and "height" properties
  16522. */
  16523. getOriginalSize: function() {
  16524. var element = this.getElement();
  16525. return {
  16526. width: element.width,
  16527. height: element.height
  16528. };
  16529. },
  16530. /**
  16531. * @private
  16532. * @param {CanvasRenderingContext2D} ctx Context to render on
  16533. */
  16534. _stroke: function(ctx) {
  16535. if (!this.stroke || this.strokeWidth === 0) {
  16536. return;
  16537. }
  16538. var w = this.width / 2, h = this.height / 2;
  16539. ctx.beginPath();
  16540. ctx.moveTo(-w, -h);
  16541. ctx.lineTo(w, -h);
  16542. ctx.lineTo(w, h);
  16543. ctx.lineTo(-w, h);
  16544. ctx.lineTo(-w, -h);
  16545. ctx.closePath();
  16546. },
  16547. /**
  16548. * @private
  16549. * @param {CanvasRenderingContext2D} ctx Context to render on
  16550. */
  16551. _renderDashedStroke: function(ctx) {
  16552. var x = -this.width / 2,
  16553. y = -this.height / 2,
  16554. w = this.width,
  16555. h = this.height;
  16556. ctx.save();
  16557. this._setStrokeStyles(ctx);
  16558. ctx.beginPath();
  16559. fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray);
  16560. fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray);
  16561. fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray);
  16562. fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray);
  16563. ctx.closePath();
  16564. ctx.restore();
  16565. },
  16566. /**
  16567. * Returns object representation of an instance
  16568. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  16569. * @return {Object} Object representation of an instance
  16570. */
  16571. toObject: function(propertiesToInclude) {
  16572. var filters = [], resizeFilters = [],
  16573. scaleX = 1, scaleY = 1;
  16574. this.filters.forEach(function(filterObj) {
  16575. if (filterObj) {
  16576. if (filterObj.type === 'Resize') {
  16577. scaleX *= filterObj.scaleX;
  16578. scaleY *= filterObj.scaleY;
  16579. }
  16580. filters.push(filterObj.toObject());
  16581. }
  16582. });
  16583. this.resizeFilters.forEach(function(filterObj) {
  16584. filterObj && resizeFilters.push(filterObj.toObject());
  16585. });
  16586. var object = extend(
  16587. this.callSuper(
  16588. 'toObject',
  16589. ['crossOrigin', 'alignX', 'alignY', 'meetOrSlice'].concat(propertiesToInclude)
  16590. ), {
  16591. src: this.getSrc(),
  16592. filters: filters,
  16593. resizeFilters: resizeFilters,
  16594. });
  16595. object.width /= scaleX;
  16596. object.height /= scaleY;
  16597. return object;
  16598. },
  16599. /* _TO_SVG_START_ */
  16600. /**
  16601. * Returns SVG representation of an instance
  16602. * @param {Function} [reviver] Method for further parsing of svg representation.
  16603. * @return {String} svg representation of an instance
  16604. */
  16605. toSVG: function(reviver) {
  16606. var markup = this._createBaseSVGMarkup(), x = -this.width / 2, y = -this.height / 2,
  16607. preserveAspectRatio = 'none', filtered = true;
  16608. if (this.group && this.group.type === 'path-group') {
  16609. x = this.left;
  16610. y = this.top;
  16611. }
  16612. if (this.alignX !== 'none' && this.alignY !== 'none') {
  16613. preserveAspectRatio = 'x' + this.alignX + 'Y' + this.alignY + ' ' + this.meetOrSlice;
  16614. }
  16615. markup.push(
  16616. '<g transform="', this.getSvgTransform(), this.getSvgTransformMatrix(), '">\n',
  16617. '<image ', this.getSvgId(), 'xlink:href="', this.getSvgSrc(filtered),
  16618. '" x="', x, '" y="', y,
  16619. '" style="', this.getSvgStyles(),
  16620. // we're essentially moving origin of transformation from top/left corner to the center of the shape
  16621. // by wrapping it in container <g> element with actual transformation, then offsetting object to the top/left
  16622. // so that object's center aligns with container's left/top
  16623. '" width="', this.width,
  16624. '" height="', this.height,
  16625. '" preserveAspectRatio="', preserveAspectRatio, '"',
  16626. '></image>\n'
  16627. );
  16628. if (this.stroke || this.strokeDashArray) {
  16629. var origFill = this.fill;
  16630. this.fill = null;
  16631. markup.push(
  16632. '<rect ',
  16633. 'x="', x, '" y="', y,
  16634. '" width="', this.width, '" height="', this.height,
  16635. '" style="', this.getSvgStyles(),
  16636. '"/>\n'
  16637. );
  16638. this.fill = origFill;
  16639. }
  16640. markup.push('</g>\n');
  16641. return reviver ? reviver(markup.join('')) : markup.join('');
  16642. },
  16643. /* _TO_SVG_END_ */
  16644. /**
  16645. * Returns source of an image
  16646. * @param {Boolean} filtered indicates if the src is needed for svg
  16647. * @return {String} Source of an image
  16648. */
  16649. getSrc: function(filtered) {
  16650. var element = filtered ? this._element : this._originalElement;
  16651. if (element) {
  16652. return fabric.isLikelyNode ? element._src : element.src;
  16653. }
  16654. else {
  16655. return this.src || '';
  16656. }
  16657. },
  16658. /**
  16659. * Sets source of an image
  16660. * @param {String} src Source string (URL)
  16661. * @param {Function} [callback] Callback is invoked when image has been loaded (and all filters have been applied)
  16662. * @param {Object} [options] Options object
  16663. * @return {fabric.Image} thisArg
  16664. * @chainable
  16665. */
  16666. setSrc: function(src, callback, options) {
  16667. fabric.util.loadImage(src, function(img) {
  16668. return this.setElement(img, callback, options);
  16669. }, this, options && options.crossOrigin);
  16670. },
  16671. /**
  16672. * Returns string representation of an instance
  16673. * @return {String} String representation of an instance
  16674. */
  16675. toString: function() {
  16676. return '#<fabric.Image: { src: "' + this.getSrc() + '" }>';
  16677. },
  16678. /**
  16679. * Applies filters assigned to this image (from "filters" array)
  16680. * @method applyFilters
  16681. * @param {Function} callback Callback is invoked when all filters have been applied and new image is generated
  16682. * @param {Array} filters to be applied
  16683. * @param {fabric.Image} imgElement image to filter ( default to this._element )
  16684. * @param {Boolean} forResizing
  16685. * @return {CanvasElement} canvasEl to be drawn immediately
  16686. * @chainable
  16687. */
  16688. applyFilters: function(callback, filters, imgElement, forResizing) {
  16689. filters = filters || this.filters;
  16690. imgElement = imgElement || this._originalElement;
  16691. if (!imgElement) {
  16692. return;
  16693. }
  16694. var replacement = fabric.util.createImage(),
  16695. retinaScaling = this.canvas ? this.canvas.getRetinaScaling() : fabric.devicePixelRatio,
  16696. minimumScale = this.minimumScaleTrigger / retinaScaling,
  16697. _this = this, scaleX, scaleY;
  16698. if (filters.length === 0) {
  16699. this._element = imgElement;
  16700. callback && callback(this);
  16701. return imgElement;
  16702. }
  16703. var canvasEl = fabric.util.createCanvasElement();
  16704. canvasEl.width = imgElement.width;
  16705. canvasEl.height = imgElement.height;
  16706. canvasEl.getContext('2d').drawImage(imgElement, 0, 0, imgElement.width, imgElement.height);
  16707. filters.forEach(function(filter) {
  16708. if (!filter) {
  16709. return;
  16710. }
  16711. if (forResizing) {
  16712. scaleX = _this.scaleX < minimumScale ? _this.scaleX : 1;
  16713. scaleY = _this.scaleY < minimumScale ? _this.scaleY : 1;
  16714. if (scaleX * retinaScaling < 1) {
  16715. scaleX *= retinaScaling;
  16716. }
  16717. if (scaleY * retinaScaling < 1) {
  16718. scaleY *= retinaScaling;
  16719. }
  16720. }
  16721. else {
  16722. scaleX = filter.scaleX;
  16723. scaleY = filter.scaleY;
  16724. }
  16725. filter.applyTo(canvasEl, scaleX, scaleY);
  16726. if (!forResizing && filter.type === 'Resize') {
  16727. _this.width *= filter.scaleX;
  16728. _this.height *= filter.scaleY;
  16729. }
  16730. });
  16731. /** @ignore */
  16732. replacement.width = canvasEl.width;
  16733. replacement.height = canvasEl.height;
  16734. if (fabric.isLikelyNode) {
  16735. replacement.src = canvasEl.toBuffer(undefined, fabric.Image.pngCompression);
  16736. // onload doesn't fire in some node versions, so we invoke callback manually
  16737. _this._element = replacement;
  16738. !forResizing && (_this._filteredEl = replacement);
  16739. callback && callback(_this);
  16740. }
  16741. else {
  16742. replacement.onload = function() {
  16743. _this._element = replacement;
  16744. !forResizing && (_this._filteredEl = replacement);
  16745. callback && callback(_this);
  16746. replacement.onload = canvasEl = null;
  16747. };
  16748. replacement.src = canvasEl.toDataURL('image/png');
  16749. }
  16750. return canvasEl;
  16751. },
  16752. /**
  16753. * @private
  16754. * @param {CanvasRenderingContext2D} ctx Context to render on
  16755. * @param {Boolean} noTransform
  16756. */
  16757. _render: function(ctx, noTransform) {
  16758. var x, y, imageMargins = this._findMargins(), elementToDraw;
  16759. x = (noTransform ? this.left : -this.width / 2);
  16760. y = (noTransform ? this.top : -this.height / 2);
  16761. if (this.meetOrSlice === 'slice') {
  16762. ctx.beginPath();
  16763. ctx.rect(x, y, this.width, this.height);
  16764. ctx.clip();
  16765. }
  16766. if (this.isMoving === false && this.resizeFilters.length && this._needsResize()) {
  16767. this._lastScaleX = this.scaleX;
  16768. this._lastScaleY = this.scaleY;
  16769. elementToDraw = this.applyFilters(null, this.resizeFilters, this._filteredEl || this._originalElement, true);
  16770. }
  16771. else {
  16772. elementToDraw = this._element;
  16773. }
  16774. elementToDraw && ctx.drawImage(elementToDraw,
  16775. x + imageMargins.marginX,
  16776. y + imageMargins.marginY,
  16777. imageMargins.width,
  16778. imageMargins.height
  16779. );
  16780. this._stroke(ctx);
  16781. this._renderStroke(ctx);
  16782. },
  16783. /**
  16784. * @private, needed to check if image needs resize
  16785. */
  16786. _needsResize: function() {
  16787. return (this.scaleX !== this._lastScaleX || this.scaleY !== this._lastScaleY);
  16788. },
  16789. /**
  16790. * @private
  16791. */
  16792. _findMargins: function() {
  16793. var width = this.width, height = this.height, scales,
  16794. scale, marginX = 0, marginY = 0;
  16795. if (this.alignX !== 'none' || this.alignY !== 'none') {
  16796. scales = [this.width / this._element.width, this.height / this._element.height];
  16797. scale = this.meetOrSlice === 'meet'
  16798. ? Math.min.apply(null, scales) : Math.max.apply(null, scales);
  16799. width = this._element.width * scale;
  16800. height = this._element.height * scale;
  16801. if (this.alignX === 'Mid') {
  16802. marginX = (this.width - width) / 2;
  16803. }
  16804. if (this.alignX === 'Max') {
  16805. marginX = this.width - width;
  16806. }
  16807. if (this.alignY === 'Mid') {
  16808. marginY = (this.height - height) / 2;
  16809. }
  16810. if (this.alignY === 'Max') {
  16811. marginY = this.height - height;
  16812. }
  16813. }
  16814. return {
  16815. width: width,
  16816. height: height,
  16817. marginX: marginX,
  16818. marginY: marginY
  16819. };
  16820. },
  16821. /**
  16822. * @private
  16823. */
  16824. _resetWidthHeight: function() {
  16825. var element = this.getElement();
  16826. this.set('width', element.width);
  16827. this.set('height', element.height);
  16828. },
  16829. /**
  16830. * The Image class's initialization method. This method is automatically
  16831. * called by the constructor.
  16832. * @private
  16833. * @param {HTMLImageElement|String} element The element representing the image
  16834. * @param {Object} [options] Options object
  16835. */
  16836. _initElement: function(element, options, callback) {
  16837. this.setElement(fabric.util.getById(element), callback, options);
  16838. fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS);
  16839. },
  16840. /**
  16841. * @private
  16842. * @param {Object} [options] Options object
  16843. */
  16844. _initConfig: function(options) {
  16845. options || (options = { });
  16846. this.setOptions(options);
  16847. this._setWidthHeight(options);
  16848. if (this._element && this.crossOrigin) {
  16849. this._element.crossOrigin = this.crossOrigin;
  16850. }
  16851. },
  16852. /**
  16853. * @private
  16854. * @param {Array} filters to be initialized
  16855. * @param {Function} callback Callback to invoke when all fabric.Image.filters instances are created
  16856. */
  16857. _initFilters: function(filters, callback) {
  16858. if (filters && filters.length) {
  16859. fabric.util.enlivenObjects(filters, function(enlivenedObjects) {
  16860. callback && callback(enlivenedObjects);
  16861. }, 'fabric.Image.filters');
  16862. }
  16863. else {
  16864. callback && callback();
  16865. }
  16866. },
  16867. /**
  16868. * @private
  16869. * @param {Object} [options] Object with width/height properties
  16870. */
  16871. _setWidthHeight: function(options) {
  16872. this.width = 'width' in options
  16873. ? options.width
  16874. : (this.getElement()
  16875. ? this.getElement().width || 0
  16876. : 0);
  16877. this.height = 'height' in options
  16878. ? options.height
  16879. : (this.getElement()
  16880. ? this.getElement().height || 0
  16881. : 0);
  16882. },
  16883. });
  16884. /**
  16885. * Default CSS class name for canvas
  16886. * @static
  16887. * @type String
  16888. * @default
  16889. */
  16890. fabric.Image.CSS_CANVAS = 'canvas-img';
  16891. /**
  16892. * Alias for getSrc
  16893. * @static
  16894. */
  16895. fabric.Image.prototype.getSvgSrc = fabric.Image.prototype.getSrc;
  16896. /**
  16897. * Creates an instance of fabric.Image from its object representation
  16898. * @static
  16899. * @param {Object} object Object to create an instance from
  16900. * @param {Function} callback Callback to invoke when an image instance is created
  16901. */
  16902. fabric.Image.fromObject = function(object, callback) {
  16903. fabric.util.loadImage(object.src, function(img, error) {
  16904. if (error) {
  16905. callback && callback(null, error);
  16906. return;
  16907. }
  16908. fabric.Image.prototype._initFilters.call(object, object.filters, function(filters) {
  16909. object.filters = filters || [];
  16910. fabric.Image.prototype._initFilters.call(object, object.resizeFilters, function(resizeFilters) {
  16911. object.resizeFilters = resizeFilters || [];
  16912. return new fabric.Image(img, object, callback);
  16913. });
  16914. });
  16915. }, null, object.crossOrigin);
  16916. };
  16917. /**
  16918. * Creates an instance of fabric.Image from an URL string
  16919. * @static
  16920. * @param {String} url URL to create an image from
  16921. * @param {Function} [callback] Callback to invoke when image is created (newly created image is passed as a first argument)
  16922. * @param {Object} [imgOptions] Options object
  16923. */
  16924. fabric.Image.fromURL = function(url, callback, imgOptions) {
  16925. fabric.util.loadImage(url, function(img) {
  16926. callback && callback(new fabric.Image(img, imgOptions));
  16927. }, null, imgOptions && imgOptions.crossOrigin);
  16928. };
  16929. /* _FROM_SVG_START_ */
  16930. /**
  16931. * List of attribute names to account for when parsing SVG element (used by {@link fabric.Image.fromElement})
  16932. * @static
  16933. * @see {@link http://www.w3.org/TR/SVG/struct.html#ImageElement}
  16934. */
  16935. fabric.Image.ATTRIBUTE_NAMES =
  16936. fabric.SHARED_ATTRIBUTES.concat('x y width height preserveAspectRatio xlink:href crossOrigin'.split(' '));
  16937. /**
  16938. * Returns {@link fabric.Image} instance from an SVG element
  16939. * @static
  16940. * @param {SVGElement} element Element to parse
  16941. * @param {Function} callback Callback to execute when fabric.Image object is created
  16942. * @param {Object} [options] Options object
  16943. * @return {fabric.Image} Instance of fabric.Image
  16944. */
  16945. fabric.Image.fromElement = function(element, callback, options) {
  16946. var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES),
  16947. preserveAR;
  16948. if (parsedAttributes.preserveAspectRatio) {
  16949. preserveAR = fabric.util.parsePreserveAspectRatioAttribute(parsedAttributes.preserveAspectRatio);
  16950. extend(parsedAttributes, preserveAR);
  16951. }
  16952. fabric.Image.fromURL(parsedAttributes['xlink:href'], callback,
  16953. extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes));
  16954. };
  16955. /* _FROM_SVG_END_ */
  16956. /**
  16957. * Indicates that instances of this type are async
  16958. * @static
  16959. * @type Boolean
  16960. * @default
  16961. */
  16962. fabric.Image.async = true;
  16963. /**
  16964. * Indicates compression level used when generating PNG under Node (in applyFilters). Any of 0-9
  16965. * @static
  16966. * @type Number
  16967. * @default
  16968. */
  16969. fabric.Image.pngCompression = 1;
  16970. })(typeof exports !== 'undefined' ? exports : this);
  16971. fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
  16972. /**
  16973. * @private
  16974. * @return {Number} angle value
  16975. */
  16976. _getAngleValueForStraighten: function() {
  16977. var angle = this.getAngle() % 360;
  16978. if (angle > 0) {
  16979. return Math.round((angle - 1) / 90) * 90;
  16980. }
  16981. return Math.round(angle / 90) * 90;
  16982. },
  16983. /**
  16984. * Straightens an object (rotating it from current angle to one of 0, 90, 180, 270, etc. depending on which is closer)
  16985. * @return {fabric.Object} thisArg
  16986. * @chainable
  16987. */
  16988. straighten: function() {
  16989. this.setAngle(this._getAngleValueForStraighten());
  16990. return this;
  16991. },
  16992. /**
  16993. * Same as {@link fabric.Object.prototype.straighten} but with animation
  16994. * @param {Object} callbacks Object with callback functions
  16995. * @param {Function} [callbacks.onComplete] Invoked on completion
  16996. * @param {Function} [callbacks.onChange] Invoked on every step of animation
  16997. * @return {fabric.Object} thisArg
  16998. * @chainable
  16999. */
  17000. fxStraighten: function(callbacks) {
  17001. callbacks = callbacks || { };
  17002. var empty = function() { },
  17003. onComplete = callbacks.onComplete || empty,
  17004. onChange = callbacks.onChange || empty,
  17005. _this = this;
  17006. fabric.util.animate({
  17007. startValue: this.get('angle'),
  17008. endValue: this._getAngleValueForStraighten(),
  17009. duration: this.FX_DURATION,
  17010. onChange: function(value) {
  17011. _this.setAngle(value);
  17012. onChange();
  17013. },
  17014. onComplete: function() {
  17015. _this.setCoords();
  17016. onComplete();
  17017. },
  17018. onStart: function() {
  17019. _this.set('active', false);
  17020. }
  17021. });
  17022. return this;
  17023. }
  17024. });
  17025. fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ {
  17026. /**
  17027. * Straightens object, then rerenders canvas
  17028. * @param {fabric.Object} object Object to straighten
  17029. * @return {fabric.Canvas} thisArg
  17030. * @chainable
  17031. */
  17032. straightenObject: function (object) {
  17033. object.straighten();
  17034. this.renderAll();
  17035. return this;
  17036. },
  17037. /**
  17038. * Same as {@link fabric.Canvas.prototype.straightenObject}, but animated
  17039. * @param {fabric.Object} object Object to straighten
  17040. * @return {fabric.Canvas} thisArg
  17041. * @chainable
  17042. */
  17043. fxStraightenObject: function (object) {
  17044. object.fxStraighten({
  17045. onChange: this.renderAll.bind(this)
  17046. });
  17047. return this;
  17048. }
  17049. });
  17050. /**
  17051. * @namespace fabric.Image.filters
  17052. * @memberOf fabric.Image
  17053. * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#image_filters}
  17054. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  17055. */
  17056. fabric.Image.filters = fabric.Image.filters || { };
  17057. /**
  17058. * Root filter class from which all filter classes inherit from
  17059. * @class fabric.Image.filters.BaseFilter
  17060. * @memberOf fabric.Image.filters
  17061. */
  17062. fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Image.filters.BaseFilter.prototype */ {
  17063. /**
  17064. * Filter type
  17065. * @param {String} type
  17066. * @default
  17067. */
  17068. type: 'BaseFilter',
  17069. /**
  17070. * Constructor
  17071. * @param {Object} [options] Options object
  17072. */
  17073. initialize: function(options) {
  17074. if (options) {
  17075. this.setOptions(options);
  17076. }
  17077. },
  17078. /**
  17079. * Sets filter's properties from options
  17080. * @param {Object} [options] Options object
  17081. */
  17082. setOptions: function(options) {
  17083. for (var prop in options) {
  17084. this[prop] = options[prop];
  17085. }
  17086. },
  17087. /**
  17088. * Returns object representation of an instance
  17089. * @return {Object} Object representation of an instance
  17090. */
  17091. toObject: function() {
  17092. return { type: this.type };
  17093. },
  17094. /**
  17095. * Returns a JSON representation of an instance
  17096. * @return {Object} JSON
  17097. */
  17098. toJSON: function() {
  17099. // delegate, not alias
  17100. return this.toObject();
  17101. }
  17102. });
  17103. fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
  17104. var filter = new fabric.Image.filters[object.type](object);
  17105. callback && callback(filter);
  17106. return filter;
  17107. };
  17108. (function(global) {
  17109. 'use strict';
  17110. var fabric = global.fabric || (global.fabric = { }),
  17111. extend = fabric.util.object.extend,
  17112. filters = fabric.Image.filters,
  17113. createClass = fabric.util.createClass;
  17114. /**
  17115. * Brightness filter class
  17116. * @class fabric.Image.filters.Brightness
  17117. * @memberOf fabric.Image.filters
  17118. * @extends fabric.Image.filters.BaseFilter
  17119. * @see {@link fabric.Image.filters.Brightness#initialize} for constructor definition
  17120. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  17121. * @example
  17122. * var filter = new fabric.Image.filters.Brightness({
  17123. * brightness: 200
  17124. * });
  17125. * object.filters.push(filter);
  17126. * object.applyFilters(canvas.renderAll.bind(canvas));
  17127. */
  17128. filters.Brightness = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Brightness.prototype */ {
  17129. /**
  17130. * Filter type
  17131. * @param {String} type
  17132. * @default
  17133. */
  17134. type: 'Brightness',
  17135. /**
  17136. * Constructor
  17137. * @memberOf fabric.Image.filters.Brightness.prototype
  17138. * @param {Object} [options] Options object
  17139. * @param {Number} [options.brightness=0] Value to brighten the image up (-255..255)
  17140. */
  17141. initialize: function(options) {
  17142. options = options || { };
  17143. this.brightness = options.brightness || 0;
  17144. },
  17145. /**
  17146. * Applies filter to canvas element
  17147. * @param {Object} canvasEl Canvas element to apply filter to
  17148. */
  17149. applyTo: function(canvasEl) {
  17150. var context = canvasEl.getContext('2d'),
  17151. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  17152. data = imageData.data,
  17153. brightness = this.brightness;
  17154. for (var i = 0, len = data.length; i < len; i += 4) {
  17155. data[i] += brightness;
  17156. data[i + 1] += brightness;
  17157. data[i + 2] += brightness;
  17158. }
  17159. context.putImageData(imageData, 0, 0);
  17160. },
  17161. /**
  17162. * Returns object representation of an instance
  17163. * @return {Object} Object representation of an instance
  17164. */
  17165. toObject: function() {
  17166. return extend(this.callSuper('toObject'), {
  17167. brightness: this.brightness
  17168. });
  17169. }
  17170. });
  17171. /**
  17172. * Returns filter instance from an object representation
  17173. * @static
  17174. * @param {Object} object Object to create an instance from
  17175. * @param {function} [callback] to be invoked after filter creation
  17176. * @return {fabric.Image.filters.Brightness} Instance of fabric.Image.filters.Brightness
  17177. */
  17178. fabric.Image.filters.Brightness.fromObject = fabric.Image.filters.BaseFilter.fromObject;
  17179. })(typeof exports !== 'undefined' ? exports : this);
  17180. (function(global) {
  17181. 'use strict';
  17182. var fabric = global.fabric || (global.fabric = { }),
  17183. extend = fabric.util.object.extend,
  17184. filters = fabric.Image.filters,
  17185. createClass = fabric.util.createClass;
  17186. /**
  17187. * Adapted from <a href="http://www.html5rocks.com/en/tutorials/canvas/imagefilters/">html5rocks article</a>
  17188. * @class fabric.Image.filters.Convolute
  17189. * @memberOf fabric.Image.filters
  17190. * @extends fabric.Image.filters.BaseFilter
  17191. * @see {@link fabric.Image.filters.Convolute#initialize} for constructor definition
  17192. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  17193. * @example <caption>Sharpen filter</caption>
  17194. * var filter = new fabric.Image.filters.Convolute({
  17195. * matrix: [ 0, -1, 0,
  17196. * -1, 5, -1,
  17197. * 0, -1, 0 ]
  17198. * });
  17199. * object.filters.push(filter);
  17200. * object.applyFilters(canvas.renderAll.bind(canvas));
  17201. * @example <caption>Blur filter</caption>
  17202. * var filter = new fabric.Image.filters.Convolute({
  17203. * matrix: [ 1/9, 1/9, 1/9,
  17204. * 1/9, 1/9, 1/9,
  17205. * 1/9, 1/9, 1/9 ]
  17206. * });
  17207. * object.filters.push(filter);
  17208. * object.applyFilters(canvas.renderAll.bind(canvas));
  17209. * @example <caption>Emboss filter</caption>
  17210. * var filter = new fabric.Image.filters.Convolute({
  17211. * matrix: [ 1, 1, 1,
  17212. * 1, 0.7, -1,
  17213. * -1, -1, -1 ]
  17214. * });
  17215. * object.filters.push(filter);
  17216. * object.applyFilters(canvas.renderAll.bind(canvas));
  17217. * @example <caption>Emboss filter with opaqueness</caption>
  17218. * var filter = new fabric.Image.filters.Convolute({
  17219. * opaque: true,
  17220. * matrix: [ 1, 1, 1,
  17221. * 1, 0.7, -1,
  17222. * -1, -1, -1 ]
  17223. * });
  17224. * object.filters.push(filter);
  17225. * object.applyFilters(canvas.renderAll.bind(canvas));
  17226. */
  17227. filters.Convolute = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Convolute.prototype */ {
  17228. /**
  17229. * Filter type
  17230. * @param {String} type
  17231. * @default
  17232. */
  17233. type: 'Convolute',
  17234. /**
  17235. * Constructor
  17236. * @memberOf fabric.Image.filters.Convolute.prototype
  17237. * @param {Object} [options] Options object
  17238. * @param {Boolean} [options.opaque=false] Opaque value (true/false)
  17239. * @param {Array} [options.matrix] Filter matrix
  17240. */
  17241. initialize: function(options) {
  17242. options = options || { };
  17243. this.opaque = options.opaque;
  17244. this.matrix = options.matrix || [
  17245. 0, 0, 0,
  17246. 0, 1, 0,
  17247. 0, 0, 0
  17248. ];
  17249. },
  17250. /**
  17251. * Applies filter to canvas element
  17252. * @param {Object} canvasEl Canvas element to apply filter to
  17253. */
  17254. applyTo: function(canvasEl) {
  17255. var weights = this.matrix,
  17256. context = canvasEl.getContext('2d'),
  17257. pixels = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  17258. side = Math.round(Math.sqrt(weights.length)),
  17259. halfSide = Math.floor(side / 2),
  17260. src = pixels.data,
  17261. sw = pixels.width,
  17262. sh = pixels.height,
  17263. output = context.createImageData(sw, sh),
  17264. dst = output.data,
  17265. // go through the destination image pixels
  17266. alphaFac = this.opaque ? 1 : 0,
  17267. r, g, b, a, dstOff,
  17268. scx, scy, srcOff, wt;
  17269. for (var y = 0; y < sh; y++) {
  17270. for (var x = 0; x < sw; x++) {
  17271. dstOff = (y * sw + x) * 4;
  17272. // calculate the weighed sum of the source image pixels that
  17273. // fall under the convolution matrix
  17274. r = 0; g = 0; b = 0; a = 0;
  17275. for (var cy = 0; cy < side; cy++) {
  17276. for (var cx = 0; cx < side; cx++) {
  17277. scy = y + cy - halfSide;
  17278. scx = x + cx - halfSide;
  17279. // eslint-disable-next-line max-depth
  17280. if (scy < 0 || scy > sh || scx < 0 || scx > sw) {
  17281. continue;
  17282. }
  17283. srcOff = (scy * sw + scx) * 4;
  17284. wt = weights[cy * side + cx];
  17285. r += src[srcOff] * wt;
  17286. g += src[srcOff + 1] * wt;
  17287. b += src[srcOff + 2] * wt;
  17288. a += src[srcOff + 3] * wt;
  17289. }
  17290. }
  17291. dst[dstOff] = r;
  17292. dst[dstOff + 1] = g;
  17293. dst[dstOff + 2] = b;
  17294. dst[dstOff + 3] = a + alphaFac * (255 - a);
  17295. }
  17296. }
  17297. context.putImageData(output, 0, 0);
  17298. },
  17299. /**
  17300. * Returns object representation of an instance
  17301. * @return {Object} Object representation of an instance
  17302. */
  17303. toObject: function() {
  17304. return extend(this.callSuper('toObject'), {
  17305. opaque: this.opaque,
  17306. matrix: this.matrix
  17307. });
  17308. }
  17309. });
  17310. /**
  17311. * Returns filter instance from an object representation
  17312. * @static
  17313. * @param {Object} object Object to create an instance from
  17314. * @param {function} [callback] to be invoked after filter creation
  17315. * @return {fabric.Image.filters.Convolute} Instance of fabric.Image.filters.Convolute
  17316. */
  17317. fabric.Image.filters.Convolute.fromObject = fabric.Image.filters.BaseFilter.fromObject;
  17318. })(typeof exports !== 'undefined' ? exports : this);
  17319. (function(global) {
  17320. 'use strict';
  17321. var fabric = global.fabric || (global.fabric = { }),
  17322. extend = fabric.util.object.extend,
  17323. filters = fabric.Image.filters,
  17324. createClass = fabric.util.createClass;
  17325. /**
  17326. * GradientTransparency filter class
  17327. * @class fabric.Image.filters.GradientTransparency
  17328. * @memberOf fabric.Image.filters
  17329. * @extends fabric.Image.filters.BaseFilter
  17330. * @see {@link fabric.Image.filters.GradientTransparency#initialize} for constructor definition
  17331. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  17332. * @example
  17333. * var filter = new fabric.Image.filters.GradientTransparency({
  17334. * threshold: 200
  17335. * });
  17336. * object.filters.push(filter);
  17337. * object.applyFilters(canvas.renderAll.bind(canvas));
  17338. */
  17339. // eslint-disable-next-line max-len
  17340. filters.GradientTransparency = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.GradientTransparency.prototype */ {
  17341. /**
  17342. * Filter type
  17343. * @param {String} type
  17344. * @default
  17345. */
  17346. type: 'GradientTransparency',
  17347. /**
  17348. * Constructor
  17349. * @memberOf fabric.Image.filters.GradientTransparency.prototype
  17350. * @param {Object} [options] Options object
  17351. * @param {Number} [options.threshold=100] Threshold value
  17352. */
  17353. initialize: function(options) {
  17354. options = options || { };
  17355. this.threshold = options.threshold || 100;
  17356. },
  17357. /**
  17358. * Applies filter to canvas element
  17359. * @param {Object} canvasEl Canvas element to apply filter to
  17360. */
  17361. applyTo: function(canvasEl) {
  17362. var context = canvasEl.getContext('2d'),
  17363. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  17364. data = imageData.data,
  17365. threshold = this.threshold,
  17366. total = data.length;
  17367. for (var i = 0, len = data.length; i < len; i += 4) {
  17368. data[i + 3] = threshold + 255 * (total - i) / total;
  17369. }
  17370. context.putImageData(imageData, 0, 0);
  17371. },
  17372. /**
  17373. * Returns object representation of an instance
  17374. * @return {Object} Object representation of an instance
  17375. */
  17376. toObject: function() {
  17377. return extend(this.callSuper('toObject'), {
  17378. threshold: this.threshold
  17379. });
  17380. }
  17381. });
  17382. /**
  17383. * Returns filter instance from an object representation
  17384. * @static
  17385. * @param {Object} object Object to create an instance from
  17386. * @param {function} [callback] to be invoked after filter creation
  17387. * @return {fabric.Image.filters.GradientTransparency} Instance of fabric.Image.filters.GradientTransparency
  17388. */
  17389. fabric.Image.filters.GradientTransparency.fromObject = fabric.Image.filters.BaseFilter.fromObject;
  17390. })(typeof exports !== 'undefined' ? exports : this);
  17391. (function(global) {
  17392. 'use strict';
  17393. var fabric = global.fabric || (global.fabric = { }),
  17394. filters = fabric.Image.filters,
  17395. createClass = fabric.util.createClass;
  17396. /**
  17397. * Grayscale image filter class
  17398. * @class fabric.Image.filters.Grayscale
  17399. * @memberOf fabric.Image.filters
  17400. * @extends fabric.Image.filters.BaseFilter
  17401. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  17402. * @example
  17403. * var filter = new fabric.Image.filters.Grayscale();
  17404. * object.filters.push(filter);
  17405. * object.applyFilters(canvas.renderAll.bind(canvas));
  17406. */
  17407. filters.Grayscale = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Grayscale.prototype */ {
  17408. /**
  17409. * Filter type
  17410. * @param {String} type
  17411. * @default
  17412. */
  17413. type: 'Grayscale',
  17414. /**
  17415. * Applies filter to canvas element
  17416. * @memberOf fabric.Image.filters.Grayscale.prototype
  17417. * @param {Object} canvasEl Canvas element to apply filter to
  17418. */
  17419. applyTo: function(canvasEl) {
  17420. var context = canvasEl.getContext('2d'),
  17421. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  17422. data = imageData.data,
  17423. len = imageData.width * imageData.height * 4,
  17424. index = 0,
  17425. average;
  17426. while (index < len) {
  17427. average = (data[index] + data[index + 1] + data[index + 2]) / 3;
  17428. data[index] = average;
  17429. data[index + 1] = average;
  17430. data[index + 2] = average;
  17431. index += 4;
  17432. }
  17433. context.putImageData(imageData, 0, 0);
  17434. }
  17435. });
  17436. /**
  17437. * Returns filter instance from an object representation
  17438. * @static
  17439. * @param {Object} object Object to create an instance from
  17440. * @param {function} [callback] to be invoked after filter creation
  17441. * @return {fabric.Image.filters.Grayscale} Instance of fabric.Image.filters.Grayscale
  17442. */
  17443. fabric.Image.filters.Grayscale.fromObject = function(object, callback) {
  17444. object = object || { };
  17445. object.type = 'Grayscale';
  17446. return fabric.Image.filters.BaseFilter.fromObject(object, callback);
  17447. };
  17448. })(typeof exports !== 'undefined' ? exports : this);
  17449. (function(global) {
  17450. 'use strict';
  17451. var fabric = global.fabric || (global.fabric = { }),
  17452. filters = fabric.Image.filters,
  17453. createClass = fabric.util.createClass;
  17454. /**
  17455. * Invert filter class
  17456. * @class fabric.Image.filters.Invert
  17457. * @memberOf fabric.Image.filters
  17458. * @extends fabric.Image.filters.BaseFilter
  17459. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  17460. * @example
  17461. * var filter = new fabric.Image.filters.Invert();
  17462. * object.filters.push(filter);
  17463. * object.applyFilters(canvas.renderAll.bind(canvas));
  17464. */
  17465. filters.Invert = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Invert.prototype */ {
  17466. /**
  17467. * Filter type
  17468. * @param {String} type
  17469. * @default
  17470. */
  17471. type: 'Invert',
  17472. /**
  17473. * Applies filter to canvas element
  17474. * @memberOf fabric.Image.filters.Invert.prototype
  17475. * @param {Object} canvasEl Canvas element to apply filter to
  17476. */
  17477. applyTo: function(canvasEl) {
  17478. var context = canvasEl.getContext('2d'),
  17479. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  17480. data = imageData.data,
  17481. iLen = data.length, i;
  17482. for (i = 0; i < iLen; i += 4) {
  17483. data[i] = 255 - data[i];
  17484. data[i + 1] = 255 - data[i + 1];
  17485. data[i + 2] = 255 - data[i + 2];
  17486. }
  17487. context.putImageData(imageData, 0, 0);
  17488. }
  17489. });
  17490. /**
  17491. * Returns filter instance from an object representation
  17492. * @static
  17493. * @param {Object} object Object to create an instance from
  17494. * @param {function} [callback] to be invoked after filter creation
  17495. * @return {fabric.Image.filters.Invert} Instance of fabric.Image.filters.Invert
  17496. */
  17497. fabric.Image.filters.Invert.fromObject = function(object, callback) {
  17498. object = object || { };
  17499. object.type = 'Invert';
  17500. return fabric.Image.filters.BaseFilter.fromObject(object, callback);
  17501. };
  17502. })(typeof exports !== 'undefined' ? exports : this);
  17503. (function(global) {
  17504. 'use strict';
  17505. var fabric = global.fabric || (global.fabric = { }),
  17506. extend = fabric.util.object.extend,
  17507. filters = fabric.Image.filters,
  17508. createClass = fabric.util.createClass;
  17509. /**
  17510. * Mask filter class
  17511. * See http://resources.aleph-1.com/mask/
  17512. * @class fabric.Image.filters.Mask
  17513. * @memberOf fabric.Image.filters
  17514. * @extends fabric.Image.filters.BaseFilter
  17515. * @see {@link fabric.Image.filters.Mask#initialize} for constructor definition
  17516. */
  17517. filters.Mask = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Mask.prototype */ {
  17518. /**
  17519. * Filter type
  17520. * @param {String} type
  17521. * @default
  17522. */
  17523. type: 'Mask',
  17524. /**
  17525. * Constructor
  17526. * @memberOf fabric.Image.filters.Mask.prototype
  17527. * @param {Object} [options] Options object
  17528. * @param {fabric.Image} [options.mask] Mask image object
  17529. * @param {Number} [options.channel=0] Rgb channel (0, 1, 2 or 3)
  17530. */
  17531. initialize: function(options) {
  17532. options = options || { };
  17533. this.mask = options.mask;
  17534. this.channel = [0, 1, 2, 3].indexOf(options.channel) > -1 ? options.channel : 0;
  17535. },
  17536. /**
  17537. * Applies filter to canvas element
  17538. * @param {Object} canvasEl Canvas element to apply filter to
  17539. */
  17540. applyTo: function(canvasEl) {
  17541. if (!this.mask) {
  17542. return;
  17543. }
  17544. var context = canvasEl.getContext('2d'),
  17545. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  17546. data = imageData.data,
  17547. maskEl = this.mask.getElement(),
  17548. maskCanvasEl = fabric.util.createCanvasElement(),
  17549. channel = this.channel,
  17550. i,
  17551. iLen = imageData.width * imageData.height * 4;
  17552. maskCanvasEl.width = canvasEl.width;
  17553. maskCanvasEl.height = canvasEl.height;
  17554. maskCanvasEl.getContext('2d').drawImage(maskEl, 0, 0, canvasEl.width, canvasEl.height);
  17555. var maskImageData = maskCanvasEl.getContext('2d').getImageData(0, 0, canvasEl.width, canvasEl.height),
  17556. maskData = maskImageData.data;
  17557. for (i = 0; i < iLen; i += 4) {
  17558. data[i + 3] = maskData[i + channel];
  17559. }
  17560. context.putImageData(imageData, 0, 0);
  17561. },
  17562. /**
  17563. * Returns object representation of an instance
  17564. * @return {Object} Object representation of an instance
  17565. */
  17566. toObject: function() {
  17567. return extend(this.callSuper('toObject'), {
  17568. mask: this.mask.toObject(),
  17569. channel: this.channel
  17570. });
  17571. }
  17572. });
  17573. /**
  17574. * Returns filter instance from an object representation
  17575. * @static
  17576. * @param {Object} object Object to create an instance from
  17577. * @param {Function} [callback] Callback to invoke when a mask filter instance is created
  17578. */
  17579. fabric.Image.filters.Mask.fromObject = function(object, callback) {
  17580. fabric.util.loadImage(object.mask.src, function(img) {
  17581. object.mask = new fabric.Image(img, object.mask);
  17582. return fabric.Image.filters.BaseFilter.fromObject(object, callback);
  17583. });
  17584. };
  17585. /**
  17586. * Indicates that instances of this type are async
  17587. * @static
  17588. * @type Boolean
  17589. * @default
  17590. */
  17591. fabric.Image.filters.Mask.async = true;
  17592. })(typeof exports !== 'undefined' ? exports : this);
  17593. (function(global) {
  17594. 'use strict';
  17595. var fabric = global.fabric || (global.fabric = { }),
  17596. extend = fabric.util.object.extend,
  17597. filters = fabric.Image.filters,
  17598. createClass = fabric.util.createClass;
  17599. /**
  17600. * Noise filter class
  17601. * @class fabric.Image.filters.Noise
  17602. * @memberOf fabric.Image.filters
  17603. * @extends fabric.Image.filters.BaseFilter
  17604. * @see {@link fabric.Image.filters.Noise#initialize} for constructor definition
  17605. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  17606. * @example
  17607. * var filter = new fabric.Image.filters.Noise({
  17608. * noise: 700
  17609. * });
  17610. * object.filters.push(filter);
  17611. * object.applyFilters(canvas.renderAll.bind(canvas));
  17612. */
  17613. filters.Noise = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Noise.prototype */ {
  17614. /**
  17615. * Filter type
  17616. * @param {String} type
  17617. * @default
  17618. */
  17619. type: 'Noise',
  17620. /**
  17621. * Constructor
  17622. * @memberOf fabric.Image.filters.Noise.prototype
  17623. * @param {Object} [options] Options object
  17624. * @param {Number} [options.noise=0] Noise value
  17625. */
  17626. initialize: function(options) {
  17627. options = options || { };
  17628. this.noise = options.noise || 0;
  17629. },
  17630. /**
  17631. * Applies filter to canvas element
  17632. * @param {Object} canvasEl Canvas element to apply filter to
  17633. */
  17634. applyTo: function(canvasEl) {
  17635. var context = canvasEl.getContext('2d'),
  17636. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  17637. data = imageData.data,
  17638. noise = this.noise, rand;
  17639. for (var i = 0, len = data.length; i < len; i += 4) {
  17640. rand = (0.5 - Math.random()) * noise;
  17641. data[i] += rand;
  17642. data[i + 1] += rand;
  17643. data[i + 2] += rand;
  17644. }
  17645. context.putImageData(imageData, 0, 0);
  17646. },
  17647. /**
  17648. * Returns object representation of an instance
  17649. * @return {Object} Object representation of an instance
  17650. */
  17651. toObject: function() {
  17652. return extend(this.callSuper('toObject'), {
  17653. noise: this.noise
  17654. });
  17655. }
  17656. });
  17657. /**
  17658. * Returns filter instance from an object representation
  17659. * @static
  17660. * @param {Object} object Object to create an instance from
  17661. * @param {Function} [callback] to be invoked after filter creation
  17662. * @return {fabric.Image.filters.Noise} Instance of fabric.Image.filters.Noise
  17663. */
  17664. fabric.Image.filters.Noise.fromObject = fabric.Image.filters.BaseFilter.fromObject;
  17665. })(typeof exports !== 'undefined' ? exports : this);
  17666. (function(global) {
  17667. 'use strict';
  17668. var fabric = global.fabric || (global.fabric = { }),
  17669. extend = fabric.util.object.extend,
  17670. filters = fabric.Image.filters,
  17671. createClass = fabric.util.createClass;
  17672. /**
  17673. * Pixelate filter class
  17674. * @class fabric.Image.filters.Pixelate
  17675. * @memberOf fabric.Image.filters
  17676. * @extends fabric.Image.filters.BaseFilter
  17677. * @see {@link fabric.Image.filters.Pixelate#initialize} for constructor definition
  17678. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  17679. * @example
  17680. * var filter = new fabric.Image.filters.Pixelate({
  17681. * blocksize: 8
  17682. * });
  17683. * object.filters.push(filter);
  17684. * object.applyFilters(canvas.renderAll.bind(canvas));
  17685. */
  17686. filters.Pixelate = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Pixelate.prototype */ {
  17687. /**
  17688. * Filter type
  17689. * @param {String} type
  17690. * @default
  17691. */
  17692. type: 'Pixelate',
  17693. /**
  17694. * Constructor
  17695. * @memberOf fabric.Image.filters.Pixelate.prototype
  17696. * @param {Object} [options] Options object
  17697. * @param {Number} [options.blocksize=4] Blocksize for pixelate
  17698. */
  17699. initialize: function(options) {
  17700. options = options || { };
  17701. this.blocksize = options.blocksize || 4;
  17702. },
  17703. /**
  17704. * Applies filter to canvas element
  17705. * @param {Object} canvasEl Canvas element to apply filter to
  17706. */
  17707. applyTo: function(canvasEl) {
  17708. var context = canvasEl.getContext('2d'),
  17709. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  17710. data = imageData.data,
  17711. iLen = imageData.height,
  17712. jLen = imageData.width,
  17713. index, i, j, r, g, b, a;
  17714. for (i = 0; i < iLen; i += this.blocksize) {
  17715. for (j = 0; j < jLen; j += this.blocksize) {
  17716. index = (i * 4) * jLen + (j * 4);
  17717. r = data[index];
  17718. g = data[index + 1];
  17719. b = data[index + 2];
  17720. a = data[index + 3];
  17721. /*
  17722. blocksize: 4
  17723. [1,x,x,x,1]
  17724. [x,x,x,x,1]
  17725. [x,x,x,x,1]
  17726. [x,x,x,x,1]
  17727. [1,1,1,1,1]
  17728. */
  17729. for (var _i = i, _ilen = i + this.blocksize; _i < _ilen; _i++) {
  17730. for (var _j = j, _jlen = j + this.blocksize; _j < _jlen; _j++) {
  17731. index = (_i * 4) * jLen + (_j * 4);
  17732. data[index] = r;
  17733. data[index + 1] = g;
  17734. data[index + 2] = b;
  17735. data[index + 3] = a;
  17736. }
  17737. }
  17738. }
  17739. }
  17740. context.putImageData(imageData, 0, 0);
  17741. },
  17742. /**
  17743. * Returns object representation of an instance
  17744. * @return {Object} Object representation of an instance
  17745. */
  17746. toObject: function() {
  17747. return extend(this.callSuper('toObject'), {
  17748. blocksize: this.blocksize
  17749. });
  17750. }
  17751. });
  17752. /**
  17753. * Returns filter instance from an object representation
  17754. * @static
  17755. * @param {Object} object Object to create an instance from
  17756. * @param {Function} [callback] to be invoked after filter creation
  17757. * @return {fabric.Image.filters.Pixelate} Instance of fabric.Image.filters.Pixelate
  17758. */
  17759. fabric.Image.filters.Pixelate.fromObject = fabric.Image.filters.BaseFilter.fromObject;
  17760. })(typeof exports !== 'undefined' ? exports : this);
  17761. (function(global) {
  17762. 'use strict';
  17763. var fabric = global.fabric || (global.fabric = { }),
  17764. extend = fabric.util.object.extend,
  17765. filters = fabric.Image.filters,
  17766. createClass = fabric.util.createClass;
  17767. /**
  17768. * Remove white filter class
  17769. * @class fabric.Image.filters.RemoveWhite
  17770. * @memberOf fabric.Image.filters
  17771. * @extends fabric.Image.filters.BaseFilter
  17772. * @see {@link fabric.Image.filters.RemoveWhite#initialize} for constructor definition
  17773. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  17774. * @example
  17775. * var filter = new fabric.Image.filters.RemoveWhite({
  17776. * threshold: 40,
  17777. * distance: 140
  17778. * });
  17779. * object.filters.push(filter);
  17780. * object.applyFilters(canvas.renderAll.bind(canvas));
  17781. */
  17782. filters.RemoveWhite = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.RemoveWhite.prototype */ {
  17783. /**
  17784. * Filter type
  17785. * @param {String} type
  17786. * @default
  17787. */
  17788. type: 'RemoveWhite',
  17789. /**
  17790. * Constructor
  17791. * @memberOf fabric.Image.filters.RemoveWhite.prototype
  17792. * @param {Object} [options] Options object
  17793. * @param {Number} [options.threshold=30] Threshold value
  17794. * @param {Number} [options.distance=20] Distance value
  17795. */
  17796. initialize: function(options) {
  17797. options = options || { };
  17798. this.threshold = options.threshold || 30;
  17799. this.distance = options.distance || 20;
  17800. },
  17801. /**
  17802. * Applies filter to canvas element
  17803. * @param {Object} canvasEl Canvas element to apply filter to
  17804. */
  17805. applyTo: function(canvasEl) {
  17806. var context = canvasEl.getContext('2d'),
  17807. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  17808. data = imageData.data,
  17809. threshold = this.threshold,
  17810. distance = this.distance,
  17811. limit = 255 - threshold,
  17812. abs = Math.abs,
  17813. r, g, b;
  17814. for (var i = 0, len = data.length; i < len; i += 4) {
  17815. r = data[i];
  17816. g = data[i + 1];
  17817. b = data[i + 2];
  17818. if (r > limit &&
  17819. g > limit &&
  17820. b > limit &&
  17821. abs(r - g) < distance &&
  17822. abs(r - b) < distance &&
  17823. abs(g - b) < distance
  17824. ) {
  17825. data[i + 3] = 0;
  17826. }
  17827. }
  17828. context.putImageData(imageData, 0, 0);
  17829. },
  17830. /**
  17831. * Returns object representation of an instance
  17832. * @return {Object} Object representation of an instance
  17833. */
  17834. toObject: function() {
  17835. return extend(this.callSuper('toObject'), {
  17836. threshold: this.threshold,
  17837. distance: this.distance
  17838. });
  17839. }
  17840. });
  17841. /**
  17842. * Returns filter instance from an object representation
  17843. * @static
  17844. * @param {Object} object Object to create an instance from
  17845. * @param {Function} [callback] to be invoked after filter creation
  17846. * @return {fabric.Image.filters.RemoveWhite} Instance of fabric.Image.filters.RemoveWhite
  17847. */
  17848. fabric.Image.filters.RemoveWhite.fromObject = fabric.Image.filters.BaseFilter.fromObject;
  17849. })(typeof exports !== 'undefined' ? exports : this);
  17850. (function(global) {
  17851. 'use strict';
  17852. var fabric = global.fabric || (global.fabric = { }),
  17853. filters = fabric.Image.filters,
  17854. createClass = fabric.util.createClass;
  17855. /**
  17856. * Sepia filter class
  17857. * @class fabric.Image.filters.Sepia
  17858. * @memberOf fabric.Image.filters
  17859. * @extends fabric.Image.filters.BaseFilter
  17860. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  17861. * @example
  17862. * var filter = new fabric.Image.filters.Sepia();
  17863. * object.filters.push(filter);
  17864. * object.applyFilters(canvas.renderAll.bind(canvas));
  17865. */
  17866. filters.Sepia = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Sepia.prototype */ {
  17867. /**
  17868. * Filter type
  17869. * @param {String} type
  17870. * @default
  17871. */
  17872. type: 'Sepia',
  17873. /**
  17874. * Applies filter to canvas element
  17875. * @memberOf fabric.Image.filters.Sepia.prototype
  17876. * @param {Object} canvasEl Canvas element to apply filter to
  17877. */
  17878. applyTo: function(canvasEl) {
  17879. var context = canvasEl.getContext('2d'),
  17880. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  17881. data = imageData.data,
  17882. iLen = data.length, i, avg;
  17883. for (i = 0; i < iLen; i += 4) {
  17884. avg = 0.3 * data[i] + 0.59 * data[i + 1] + 0.11 * data[i + 2];
  17885. data[i] = avg + 100;
  17886. data[i + 1] = avg + 50;
  17887. data[i + 2] = avg + 255;
  17888. }
  17889. context.putImageData(imageData, 0, 0);
  17890. }
  17891. });
  17892. /**
  17893. * Returns filter instance from an object representation
  17894. * @static
  17895. * @param {Object} object Object to create an instance from
  17896. * @param {Function} [callback] to be invoked after filter creation
  17897. * @return {fabric.Image.filters.Sepia} Instance of fabric.Image.filters.Sepia
  17898. */
  17899. fabric.Image.filters.Sepia.fromObject = function(object, callback) {
  17900. object = object || { };
  17901. object.type = 'Sepia';
  17902. return new fabric.Image.filters.BaseFilter.fromObject(object, callback);
  17903. };
  17904. })(typeof exports !== 'undefined' ? exports : this);
  17905. (function(global) {
  17906. 'use strict';
  17907. var fabric = global.fabric || (global.fabric = { }),
  17908. filters = fabric.Image.filters,
  17909. createClass = fabric.util.createClass;
  17910. /**
  17911. * Sepia2 filter class
  17912. * @class fabric.Image.filters.Sepia2
  17913. * @memberOf fabric.Image.filters
  17914. * @extends fabric.Image.filters.BaseFilter
  17915. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  17916. * @example
  17917. * var filter = new fabric.Image.filters.Sepia2();
  17918. * object.filters.push(filter);
  17919. * object.applyFilters(canvas.renderAll.bind(canvas));
  17920. */
  17921. filters.Sepia2 = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Sepia2.prototype */ {
  17922. /**
  17923. * Filter type
  17924. * @param {String} type
  17925. * @default
  17926. */
  17927. type: 'Sepia2',
  17928. /**
  17929. * Applies filter to canvas element
  17930. * @memberOf fabric.Image.filters.Sepia.prototype
  17931. * @param {Object} canvasEl Canvas element to apply filter to
  17932. */
  17933. applyTo: function(canvasEl) {
  17934. var context = canvasEl.getContext('2d'),
  17935. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  17936. data = imageData.data,
  17937. iLen = data.length, i, r, g, b;
  17938. for (i = 0; i < iLen; i += 4) {
  17939. r = data[i];
  17940. g = data[i + 1];
  17941. b = data[i + 2];
  17942. data[i] = (r * 0.393 + g * 0.769 + b * 0.189 ) / 1.351;
  17943. data[i + 1] = (r * 0.349 + g * 0.686 + b * 0.168 ) / 1.203;
  17944. data[i + 2] = (r * 0.272 + g * 0.534 + b * 0.131 ) / 2.140;
  17945. }
  17946. context.putImageData(imageData, 0, 0);
  17947. }
  17948. });
  17949. /**
  17950. * Returns filter instance from an object representation
  17951. * @static
  17952. * @param {Object} object Object to create an instance from
  17953. * @param {Function} [callback] to be invoked after filter creation
  17954. * @return {fabric.Image.filters.Sepia2} Instance of fabric.Image.filters.Sepia2
  17955. */
  17956. fabric.Image.filters.Sepia2.fromObject = function(object, callback) {
  17957. object = object || { };
  17958. object.type = 'Sepia2';
  17959. return new fabric.Image.filters.BaseFilter.fromObject(object, callback);
  17960. };
  17961. })(typeof exports !== 'undefined' ? exports : this);
  17962. (function(global) {
  17963. 'use strict';
  17964. var fabric = global.fabric || (global.fabric = { }),
  17965. extend = fabric.util.object.extend,
  17966. filters = fabric.Image.filters,
  17967. createClass = fabric.util.createClass;
  17968. /**
  17969. * Tint filter class
  17970. * Adapted from <a href="https://github.com/mezzoblue/PaintbrushJS">https://github.com/mezzoblue/PaintbrushJS</a>
  17971. * @class fabric.Image.filters.Tint
  17972. * @memberOf fabric.Image.filters
  17973. * @extends fabric.Image.filters.BaseFilter
  17974. * @see {@link fabric.Image.filters.Tint#initialize} for constructor definition
  17975. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  17976. * @example <caption>Tint filter with hex color and opacity</caption>
  17977. * var filter = new fabric.Image.filters.Tint({
  17978. * color: '#3513B0',
  17979. * opacity: 0.5
  17980. * });
  17981. * object.filters.push(filter);
  17982. * object.applyFilters(canvas.renderAll.bind(canvas));
  17983. * @example <caption>Tint filter with rgba color</caption>
  17984. * var filter = new fabric.Image.filters.Tint({
  17985. * color: 'rgba(53, 21, 176, 0.5)'
  17986. * });
  17987. * object.filters.push(filter);
  17988. * object.applyFilters(canvas.renderAll.bind(canvas));
  17989. */
  17990. filters.Tint = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Tint.prototype */ {
  17991. /**
  17992. * Filter type
  17993. * @param {String} type
  17994. * @default
  17995. */
  17996. type: 'Tint',
  17997. /**
  17998. * Constructor
  17999. * @memberOf fabric.Image.filters.Tint.prototype
  18000. * @param {Object} [options] Options object
  18001. * @param {String} [options.color=#000000] Color to tint the image with
  18002. * @param {Number} [options.opacity] Opacity value that controls the tint effect's transparency (0..1)
  18003. */
  18004. initialize: function(options) {
  18005. options = options || { };
  18006. this.color = options.color || '#000000';
  18007. this.opacity = typeof options.opacity !== 'undefined'
  18008. ? options.opacity
  18009. : new fabric.Color(this.color).getAlpha();
  18010. },
  18011. /**
  18012. * Applies filter to canvas element
  18013. * @param {Object} canvasEl Canvas element to apply filter to
  18014. */
  18015. applyTo: function(canvasEl) {
  18016. var context = canvasEl.getContext('2d'),
  18017. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  18018. data = imageData.data,
  18019. iLen = data.length, i,
  18020. tintR, tintG, tintB,
  18021. r, g, b, alpha1,
  18022. source;
  18023. source = new fabric.Color(this.color).getSource();
  18024. tintR = source[0] * this.opacity;
  18025. tintG = source[1] * this.opacity;
  18026. tintB = source[2] * this.opacity;
  18027. alpha1 = 1 - this.opacity;
  18028. for (i = 0; i < iLen; i += 4) {
  18029. r = data[i];
  18030. g = data[i + 1];
  18031. b = data[i + 2];
  18032. // alpha compositing
  18033. data[i] = tintR + r * alpha1;
  18034. data[i + 1] = tintG + g * alpha1;
  18035. data[i + 2] = tintB + b * alpha1;
  18036. }
  18037. context.putImageData(imageData, 0, 0);
  18038. },
  18039. /**
  18040. * Returns object representation of an instance
  18041. * @return {Object} Object representation of an instance
  18042. */
  18043. toObject: function() {
  18044. return extend(this.callSuper('toObject'), {
  18045. color: this.color,
  18046. opacity: this.opacity
  18047. });
  18048. }
  18049. });
  18050. /**
  18051. * Returns filter instance from an object representation
  18052. * @static
  18053. * @param {Object} object Object to create an instance from
  18054. * @param {Function} [callback] to be invoked after filter creation
  18055. * @return {fabric.Image.filters.Tint} Instance of fabric.Image.filters.Tint
  18056. */
  18057. fabric.Image.filters.Tint.fromObject = fabric.Image.filters.BaseFilter.fromObject;
  18058. })(typeof exports !== 'undefined' ? exports : this);
  18059. (function(global) {
  18060. 'use strict';
  18061. var fabric = global.fabric || (global.fabric = { }),
  18062. extend = fabric.util.object.extend,
  18063. filters = fabric.Image.filters,
  18064. createClass = fabric.util.createClass;
  18065. /**
  18066. * Multiply filter class
  18067. * Adapted from <a href="http://www.laurenscorijn.com/articles/colormath-basics">http://www.laurenscorijn.com/articles/colormath-basics</a>
  18068. * @class fabric.Image.filters.Multiply
  18069. * @memberOf fabric.Image.filters
  18070. * @extends fabric.Image.filters.BaseFilter
  18071. * @example <caption>Multiply filter with hex color</caption>
  18072. * var filter = new fabric.Image.filters.Multiply({
  18073. * color: '#F0F'
  18074. * });
  18075. * object.filters.push(filter);
  18076. * object.applyFilters(canvas.renderAll.bind(canvas));
  18077. * @example <caption>Multiply filter with rgb color</caption>
  18078. * var filter = new fabric.Image.filters.Multiply({
  18079. * color: 'rgb(53, 21, 176)'
  18080. * });
  18081. * object.filters.push(filter);
  18082. * object.applyFilters(canvas.renderAll.bind(canvas));
  18083. */
  18084. filters.Multiply = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Multiply.prototype */ {
  18085. /**
  18086. * Filter type
  18087. * @param {String} type
  18088. * @default
  18089. */
  18090. type: 'Multiply',
  18091. /**
  18092. * Constructor
  18093. * @memberOf fabric.Image.filters.Multiply.prototype
  18094. * @param {Object} [options] Options object
  18095. * @param {String} [options.color=#000000] Color to multiply the image pixels with
  18096. */
  18097. initialize: function(options) {
  18098. options = options || { };
  18099. this.color = options.color || '#000000';
  18100. },
  18101. /**
  18102. * Applies filter to canvas element
  18103. * @param {Object} canvasEl Canvas element to apply filter to
  18104. */
  18105. applyTo: function(canvasEl) {
  18106. var context = canvasEl.getContext('2d'),
  18107. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  18108. data = imageData.data,
  18109. iLen = data.length, i,
  18110. source;
  18111. source = new fabric.Color(this.color).getSource();
  18112. for (i = 0; i < iLen; i += 4) {
  18113. data[i] *= source[0] / 255;
  18114. data[i + 1] *= source[1] / 255;
  18115. data[i + 2] *= source[2] / 255;
  18116. }
  18117. context.putImageData(imageData, 0, 0);
  18118. },
  18119. /**
  18120. * Returns object representation of an instance
  18121. * @return {Object} Object representation of an instance
  18122. */
  18123. toObject: function() {
  18124. return extend(this.callSuper('toObject'), {
  18125. color: this.color
  18126. });
  18127. }
  18128. });
  18129. /**
  18130. * Returns filter instance from an object representation
  18131. * @static
  18132. * @param {Object} object Object to create an instance from
  18133. * @param {Function} [callback] to be invoked after filter creation
  18134. * @return {fabric.Image.filters.Multiply} Instance of fabric.Image.filters.Multiply
  18135. */
  18136. fabric.Image.filters.Multiply.fromObject = fabric.Image.filters.BaseFilter.fromObject;
  18137. })(typeof exports !== 'undefined' ? exports : this);
  18138. (function(global) {
  18139. 'use strict';
  18140. var fabric = global.fabric,
  18141. filters = fabric.Image.filters,
  18142. createClass = fabric.util.createClass;
  18143. /**
  18144. * Color Blend filter class
  18145. * @class fabric.Image.filter.Blend
  18146. * @memberOf fabric.Image.filters
  18147. * @extends fabric.Image.filters.BaseFilter
  18148. * @example
  18149. * var filter = new fabric.Image.filters.Blend({
  18150. * color: '#000',
  18151. * mode: 'multiply'
  18152. * });
  18153. *
  18154. * var filter = new fabric.Image.filters.Blend({
  18155. * image: fabricImageObject,
  18156. * mode: 'multiply',
  18157. * alpha: 0.5
  18158. * });
  18159. * object.filters.push(filter);
  18160. * object.applyFilters(canvas.renderAll.bind(canvas));
  18161. */
  18162. filters.Blend = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Blend.prototype */ {
  18163. type: 'Blend',
  18164. initialize: function(options) {
  18165. options = options || {};
  18166. this.color = options.color || '#000';
  18167. this.image = options.image || false;
  18168. this.mode = options.mode || 'multiply';
  18169. this.alpha = options.alpha || 1;
  18170. },
  18171. applyTo: function(canvasEl) {
  18172. var context = canvasEl.getContext('2d'),
  18173. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  18174. data = imageData.data,
  18175. tr, tg, tb,
  18176. r, g, b,
  18177. _r, _g, _b,
  18178. source,
  18179. isImage = false;
  18180. if (this.image) {
  18181. // Blend images
  18182. isImage = true;
  18183. var _el = fabric.util.createCanvasElement();
  18184. _el.width = this.image.width;
  18185. _el.height = this.image.height;
  18186. var tmpCanvas = new fabric.StaticCanvas(_el);
  18187. tmpCanvas.add(this.image);
  18188. var context2 = tmpCanvas.getContext('2d');
  18189. source = context2.getImageData(0, 0, tmpCanvas.width, tmpCanvas.height).data;
  18190. }
  18191. else {
  18192. // Blend color
  18193. source = new fabric.Color(this.color).getSource();
  18194. tr = source[0] * this.alpha;
  18195. tg = source[1] * this.alpha;
  18196. tb = source[2] * this.alpha;
  18197. }
  18198. for (var i = 0, len = data.length; i < len; i += 4) {
  18199. r = data[i];
  18200. g = data[i + 1];
  18201. b = data[i + 2];
  18202. if (isImage) {
  18203. tr = source[i] * this.alpha;
  18204. tg = source[i + 1] * this.alpha;
  18205. tb = source[i + 2] * this.alpha;
  18206. }
  18207. switch (this.mode) {
  18208. case 'multiply':
  18209. data[i] = r * tr / 255;
  18210. data[i + 1] = g * tg / 255;
  18211. data[i + 2] = b * tb / 255;
  18212. break;
  18213. case 'screen':
  18214. data[i] = 1 - (1 - r) * (1 - tr);
  18215. data[i + 1] = 1 - (1 - g) * (1 - tg);
  18216. data[i + 2] = 1 - (1 - b) * (1 - tb);
  18217. break;
  18218. case 'add':
  18219. data[i] = Math.min(255, r + tr);
  18220. data[i + 1] = Math.min(255, g + tg);
  18221. data[i + 2] = Math.min(255, b + tb);
  18222. break;
  18223. case 'diff':
  18224. case 'difference':
  18225. data[i] = Math.abs(r - tr);
  18226. data[i + 1] = Math.abs(g - tg);
  18227. data[i + 2] = Math.abs(b - tb);
  18228. break;
  18229. case 'subtract':
  18230. _r = r - tr;
  18231. _g = g - tg;
  18232. _b = b - tb;
  18233. data[i] = (_r < 0) ? 0 : _r;
  18234. data[i + 1] = (_g < 0) ? 0 : _g;
  18235. data[i + 2] = (_b < 0) ? 0 : _b;
  18236. break;
  18237. case 'darken':
  18238. data[i] = Math.min(r, tr);
  18239. data[i + 1] = Math.min(g, tg);
  18240. data[i + 2] = Math.min(b, tb);
  18241. break;
  18242. case 'lighten':
  18243. data[i] = Math.max(r, tr);
  18244. data[i + 1] = Math.max(g, tg);
  18245. data[i + 2] = Math.max(b, tb);
  18246. break;
  18247. }
  18248. }
  18249. context.putImageData(imageData, 0, 0);
  18250. },
  18251. /**
  18252. * Returns object representation of an instance
  18253. * @return {Object} Object representation of an instance
  18254. */
  18255. toObject: function() {
  18256. return {
  18257. color: this.color,
  18258. image: this.image,
  18259. mode: this.mode,
  18260. alpha: this.alpha
  18261. };
  18262. }
  18263. });
  18264. /**
  18265. * Returns filter instance from an object representation
  18266. * @static
  18267. * @param {Object} object Object to create an instance from
  18268. * @param {function} [callback] to be invoked after filter creation
  18269. * @return {fabric.Image.filters.Blend} Instance of fabric.Image.filters.Blend
  18270. */
  18271. fabric.Image.filters.Blend.fromObject = fabric.Image.filters.BaseFilter.fromObject;
  18272. })(typeof exports !== 'undefined' ? exports : this);
  18273. (function(global) {
  18274. 'use strict';
  18275. var fabric = global.fabric || (global.fabric = { }), pow = Math.pow, floor = Math.floor,
  18276. sqrt = Math.sqrt, abs = Math.abs, max = Math.max, round = Math.round, sin = Math.sin,
  18277. ceil = Math.ceil,
  18278. filters = fabric.Image.filters,
  18279. createClass = fabric.util.createClass;
  18280. /**
  18281. * Resize image filter class
  18282. * @class fabric.Image.filters.Resize
  18283. * @memberOf fabric.Image.filters
  18284. * @extends fabric.Image.filters.BaseFilter
  18285. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  18286. * @example
  18287. * var filter = new fabric.Image.filters.Resize();
  18288. * object.filters.push(filter);
  18289. * object.applyFilters(canvas.renderAll.bind(canvas));
  18290. */
  18291. filters.Resize = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Resize.prototype */ {
  18292. /**
  18293. * Filter type
  18294. * @param {String} type
  18295. * @default
  18296. */
  18297. type: 'Resize',
  18298. /**
  18299. * Resize type
  18300. * @param {String} resizeType
  18301. * @default
  18302. */
  18303. resizeType: 'hermite',
  18304. /**
  18305. * Scale factor for resizing, x axis
  18306. * @param {Number} scaleX
  18307. * @default
  18308. */
  18309. scaleX: 0,
  18310. /**
  18311. * Scale factor for resizing, y axis
  18312. * @param {Number} scaleY
  18313. * @default
  18314. */
  18315. scaleY: 0,
  18316. /**
  18317. * LanczosLobes parameter for lanczos filter
  18318. * @param {Number} lanczosLobes
  18319. * @default
  18320. */
  18321. lanczosLobes: 3,
  18322. /**
  18323. * Applies filter to canvas element
  18324. * @memberOf fabric.Image.filters.Resize.prototype
  18325. * @param {Object} canvasEl Canvas element to apply filter to
  18326. * @param {Number} scaleX
  18327. * @param {Number} scaleY
  18328. */
  18329. applyTo: function(canvasEl, scaleX, scaleY) {
  18330. if (scaleX === 1 && scaleY === 1) {
  18331. return;
  18332. }
  18333. this.rcpScaleX = 1 / scaleX;
  18334. this.rcpScaleY = 1 / scaleY;
  18335. var oW = canvasEl.width, oH = canvasEl.height,
  18336. dW = round(oW * scaleX), dH = round(oH * scaleY),
  18337. imageData;
  18338. if (this.resizeType === 'sliceHack') {
  18339. imageData = this.sliceByTwo(canvasEl, oW, oH, dW, dH);
  18340. }
  18341. if (this.resizeType === 'hermite') {
  18342. imageData = this.hermiteFastResize(canvasEl, oW, oH, dW, dH);
  18343. }
  18344. if (this.resizeType === 'bilinear') {
  18345. imageData = this.bilinearFiltering(canvasEl, oW, oH, dW, dH);
  18346. }
  18347. if (this.resizeType === 'lanczos') {
  18348. imageData = this.lanczosResize(canvasEl, oW, oH, dW, dH);
  18349. }
  18350. canvasEl.width = dW;
  18351. canvasEl.height = dH;
  18352. canvasEl.getContext('2d').putImageData(imageData, 0, 0);
  18353. },
  18354. /**
  18355. * Filter sliceByTwo
  18356. * @param {Object} canvasEl Canvas element to apply filter to
  18357. * @param {Number} oW Original Width
  18358. * @param {Number} oH Original Height
  18359. * @param {Number} dW Destination Width
  18360. * @param {Number} dH Destination Height
  18361. * @returns {ImageData}
  18362. */
  18363. sliceByTwo: function(canvasEl, oW, oH, dW, dH) {
  18364. var context = canvasEl.getContext('2d'), imageData,
  18365. multW = 0.5, multH = 0.5, signW = 1, signH = 1,
  18366. doneW = false, doneH = false, stepW = oW, stepH = oH,
  18367. tmpCanvas = fabric.util.createCanvasElement(),
  18368. tmpCtx = tmpCanvas.getContext('2d');
  18369. dW = floor(dW);
  18370. dH = floor(dH);
  18371. tmpCanvas.width = max(dW, oW);
  18372. tmpCanvas.height = max(dH, oH);
  18373. if (dW > oW) {
  18374. multW = 2;
  18375. signW = -1;
  18376. }
  18377. if (dH > oH) {
  18378. multH = 2;
  18379. signH = -1;
  18380. }
  18381. imageData = context.getImageData(0, 0, oW, oH);
  18382. canvasEl.width = max(dW, oW);
  18383. canvasEl.height = max(dH, oH);
  18384. context.putImageData(imageData, 0, 0);
  18385. while (!doneW || !doneH) {
  18386. oW = stepW;
  18387. oH = stepH;
  18388. if (dW * signW < floor(stepW * multW * signW)) {
  18389. stepW = floor(stepW * multW);
  18390. }
  18391. else {
  18392. stepW = dW;
  18393. doneW = true;
  18394. }
  18395. if (dH * signH < floor(stepH * multH * signH)) {
  18396. stepH = floor(stepH * multH);
  18397. }
  18398. else {
  18399. stepH = dH;
  18400. doneH = true;
  18401. }
  18402. imageData = context.getImageData(0, 0, oW, oH);
  18403. tmpCtx.putImageData(imageData, 0, 0);
  18404. context.clearRect(0, 0, stepW, stepH);
  18405. context.drawImage(tmpCanvas, 0, 0, oW, oH, 0, 0, stepW, stepH);
  18406. }
  18407. return context.getImageData(0, 0, dW, dH);
  18408. },
  18409. /**
  18410. * Filter lanczosResize
  18411. * @param {Object} canvasEl Canvas element to apply filter to
  18412. * @param {Number} oW Original Width
  18413. * @param {Number} oH Original Height
  18414. * @param {Number} dW Destination Width
  18415. * @param {Number} dH Destination Height
  18416. * @returns {ImageData}
  18417. */
  18418. lanczosResize: function(canvasEl, oW, oH, dW, dH) {
  18419. function lanczosCreate(lobes) {
  18420. return function(x) {
  18421. if (x > lobes) {
  18422. return 0;
  18423. }
  18424. x *= Math.PI;
  18425. if (abs(x) < 1e-16) {
  18426. return 1;
  18427. }
  18428. var xx = x / lobes;
  18429. return sin(x) * sin(xx) / x / xx;
  18430. };
  18431. }
  18432. function process(u) {
  18433. var v, i, weight, idx, a, red, green,
  18434. blue, alpha, fX, fY;
  18435. center.x = (u + 0.5) * ratioX;
  18436. icenter.x = floor(center.x);
  18437. for (v = 0; v < dH; v++) {
  18438. center.y = (v + 0.5) * ratioY;
  18439. icenter.y = floor(center.y);
  18440. a = 0; red = 0; green = 0; blue = 0; alpha = 0;
  18441. for (i = icenter.x - range2X; i <= icenter.x + range2X; i++) {
  18442. if (i < 0 || i >= oW) {
  18443. continue;
  18444. }
  18445. fX = floor(1000 * abs(i - center.x));
  18446. if (!cacheLanc[fX]) {
  18447. cacheLanc[fX] = { };
  18448. }
  18449. for (var j = icenter.y - range2Y; j <= icenter.y + range2Y; j++) {
  18450. if (j < 0 || j >= oH) {
  18451. continue;
  18452. }
  18453. fY = floor(1000 * abs(j - center.y));
  18454. if (!cacheLanc[fX][fY]) {
  18455. cacheLanc[fX][fY] = lanczos(sqrt(pow(fX * rcpRatioX, 2) + pow(fY * rcpRatioY, 2)) / 1000);
  18456. }
  18457. weight = cacheLanc[fX][fY];
  18458. if (weight > 0) {
  18459. idx = (j * oW + i) * 4;
  18460. a += weight;
  18461. red += weight * srcData[idx];
  18462. green += weight * srcData[idx + 1];
  18463. blue += weight * srcData[idx + 2];
  18464. alpha += weight * srcData[idx + 3];
  18465. }
  18466. }
  18467. }
  18468. idx = (v * dW + u) * 4;
  18469. destData[idx] = red / a;
  18470. destData[idx + 1] = green / a;
  18471. destData[idx + 2] = blue / a;
  18472. destData[idx + 3] = alpha / a;
  18473. }
  18474. if (++u < dW) {
  18475. return process(u);
  18476. }
  18477. else {
  18478. return destImg;
  18479. }
  18480. }
  18481. var context = canvasEl.getContext('2d'),
  18482. srcImg = context.getImageData(0, 0, oW, oH),
  18483. destImg = context.getImageData(0, 0, dW, dH),
  18484. srcData = srcImg.data, destData = destImg.data,
  18485. lanczos = lanczosCreate(this.lanczosLobes),
  18486. ratioX = this.rcpScaleX, ratioY = this.rcpScaleY,
  18487. rcpRatioX = 2 / this.rcpScaleX, rcpRatioY = 2 / this.rcpScaleY,
  18488. range2X = ceil(ratioX * this.lanczosLobes / 2),
  18489. range2Y = ceil(ratioY * this.lanczosLobes / 2),
  18490. cacheLanc = { }, center = { }, icenter = { };
  18491. return process(0);
  18492. },
  18493. /**
  18494. * bilinearFiltering
  18495. * @param {Object} canvasEl Canvas element to apply filter to
  18496. * @param {Number} oW Original Width
  18497. * @param {Number} oH Original Height
  18498. * @param {Number} dW Destination Width
  18499. * @param {Number} dH Destination Height
  18500. * @returns {ImageData}
  18501. */
  18502. bilinearFiltering: function(canvasEl, oW, oH, dW, dH) {
  18503. var a, b, c, d, x, y, i, j, xDiff, yDiff, chnl,
  18504. color, offset = 0, origPix, ratioX = this.rcpScaleX,
  18505. ratioY = this.rcpScaleY, context = canvasEl.getContext('2d'),
  18506. w4 = 4 * (oW - 1), img = context.getImageData(0, 0, oW, oH),
  18507. pixels = img.data, destImage = context.getImageData(0, 0, dW, dH),
  18508. destPixels = destImage.data;
  18509. for (i = 0; i < dH; i++) {
  18510. for (j = 0; j < dW; j++) {
  18511. x = floor(ratioX * j);
  18512. y = floor(ratioY * i);
  18513. xDiff = ratioX * j - x;
  18514. yDiff = ratioY * i - y;
  18515. origPix = 4 * (y * oW + x);
  18516. for (chnl = 0; chnl < 4; chnl++) {
  18517. a = pixels[origPix + chnl];
  18518. b = pixels[origPix + 4 + chnl];
  18519. c = pixels[origPix + w4 + chnl];
  18520. d = pixels[origPix + w4 + 4 + chnl];
  18521. color = a * (1 - xDiff) * (1 - yDiff) + b * xDiff * (1 - yDiff) +
  18522. c * yDiff * (1 - xDiff) + d * xDiff * yDiff;
  18523. destPixels[offset++] = color;
  18524. }
  18525. }
  18526. }
  18527. return destImage;
  18528. },
  18529. /**
  18530. * hermiteFastResize
  18531. * @param {Object} canvasEl Canvas element to apply filter to
  18532. * @param {Number} oW Original Width
  18533. * @param {Number} oH Original Height
  18534. * @param {Number} dW Destination Width
  18535. * @param {Number} dH Destination Height
  18536. * @returns {ImageData}
  18537. */
  18538. hermiteFastResize: function(canvasEl, oW, oH, dW, dH) {
  18539. var ratioW = this.rcpScaleX, ratioH = this.rcpScaleY,
  18540. ratioWHalf = ceil(ratioW / 2),
  18541. ratioHHalf = ceil(ratioH / 2),
  18542. context = canvasEl.getContext('2d'),
  18543. img = context.getImageData(0, 0, oW, oH), data = img.data,
  18544. img2 = context.getImageData(0, 0, dW, dH), data2 = img2.data;
  18545. for (var j = 0; j < dH; j++) {
  18546. for (var i = 0; i < dW; i++) {
  18547. var x2 = (i + j * dW) * 4, weight = 0, weights = 0, weightsAlpha = 0,
  18548. gxR = 0, gxG = 0, gxB = 0, gxA = 0, centerY = (j + 0.5) * ratioH;
  18549. for (var yy = floor(j * ratioH); yy < (j + 1) * ratioH; yy++) {
  18550. var dy = abs(centerY - (yy + 0.5)) / ratioHHalf,
  18551. centerX = (i + 0.5) * ratioW, w0 = dy * dy;
  18552. for (var xx = floor(i * ratioW); xx < (i + 1) * ratioW; xx++) {
  18553. var dx = abs(centerX - (xx + 0.5)) / ratioWHalf,
  18554. w = sqrt(w0 + dx * dx);
  18555. /* eslint-disable max-depth */
  18556. if (w > 1 && w < -1) {
  18557. continue;
  18558. }
  18559. //hermite filter
  18560. weight = 2 * w * w * w - 3 * w * w + 1;
  18561. if (weight > 0) {
  18562. dx = 4 * (xx + yy * oW);
  18563. //alpha
  18564. gxA += weight * data[dx + 3];
  18565. weightsAlpha += weight;
  18566. //colors
  18567. if (data[dx + 3] < 255) {
  18568. weight = weight * data[dx + 3] / 250;
  18569. }
  18570. gxR += weight * data[dx];
  18571. gxG += weight * data[dx + 1];
  18572. gxB += weight * data[dx + 2];
  18573. weights += weight;
  18574. }
  18575. /* eslint-enable max-depth */
  18576. }
  18577. }
  18578. data2[x2] = gxR / weights;
  18579. data2[x2 + 1] = gxG / weights;
  18580. data2[x2 + 2] = gxB / weights;
  18581. data2[x2 + 3] = gxA / weightsAlpha;
  18582. }
  18583. }
  18584. return img2;
  18585. },
  18586. /**
  18587. * Returns object representation of an instance
  18588. * @return {Object} Object representation of an instance
  18589. */
  18590. toObject: function() {
  18591. return {
  18592. type: this.type,
  18593. scaleX: this.scaleX,
  18594. scaleY: this.scaleY,
  18595. resizeType: this.resizeType,
  18596. lanczosLobes: this.lanczosLobes
  18597. };
  18598. }
  18599. });
  18600. /**
  18601. * Returns filter instance from an object representation
  18602. * @static
  18603. * @param {Object} object Object to create an instance from
  18604. * @param {Function} [callback] to be invoked after filter creation
  18605. * @return {fabric.Image.filters.Resize} Instance of fabric.Image.filters.Resize
  18606. */
  18607. fabric.Image.filters.Resize.fromObject = fabric.Image.filters.BaseFilter.fromObject;
  18608. })(typeof exports !== 'undefined' ? exports : this);
  18609. (function(global) {
  18610. 'use strict';
  18611. var fabric = global.fabric || (global.fabric = { }),
  18612. extend = fabric.util.object.extend,
  18613. filters = fabric.Image.filters,
  18614. createClass = fabric.util.createClass;
  18615. /**
  18616. * Color Matrix filter class
  18617. * @class fabric.Image.filters.ColorMatrix
  18618. * @memberOf fabric.Image.filters
  18619. * @extends fabric.Image.filters.BaseFilter
  18620. * @see {@link fabric.Image.filters.ColorMatrix#initialize} for constructor definition
  18621. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  18622. * @see {@Link http://www.webwasp.co.uk/tutorials/219/Color_Matrix_Filter.php}
  18623. * @see {@Link http://phoboslab.org/log/2013/11/fast-image-filters-with-webgl}
  18624. * @example <caption>Kodachrome filter</caption>
  18625. * var filter = new fabric.Image.filters.ColorMatrix({
  18626. * matrix: [
  18627. 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502,
  18628. -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203,
  18629. -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946,
  18630. 0, 0, 0, 1, 0
  18631. ]
  18632. * });
  18633. * object.filters.push(filter);
  18634. * object.applyFilters(canvas.renderAll.bind(canvas));
  18635. */
  18636. filters.ColorMatrix = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.ColorMatrix.prototype */ {
  18637. /**
  18638. * Filter type
  18639. * @param {String} type
  18640. * @default
  18641. */
  18642. type: 'ColorMatrix',
  18643. /**
  18644. * Constructor
  18645. * @memberOf fabric.Image.filters.ColorMatrix.prototype
  18646. * @param {Object} [options] Options object
  18647. * @param {Array} [options.matrix] Color Matrix to modify the image data with
  18648. */
  18649. initialize: function( options ) {
  18650. options || ( options = {} );
  18651. this.matrix = options.matrix || [
  18652. 1, 0, 0, 0, 0,
  18653. 0, 1, 0, 0, 0,
  18654. 0, 0, 1, 0, 0,
  18655. 0, 0, 0, 1, 0
  18656. ];
  18657. },
  18658. /**
  18659. * Applies filter to canvas element
  18660. * @param {Object} canvasEl Canvas element to apply filter to
  18661. */
  18662. applyTo: function( canvasEl ) {
  18663. var context = canvasEl.getContext( '2d' ),
  18664. imageData = context.getImageData( 0, 0, canvasEl.width, canvasEl.height ),
  18665. data = imageData.data,
  18666. iLen = data.length,
  18667. i,
  18668. r,
  18669. g,
  18670. b,
  18671. a,
  18672. m = this.matrix;
  18673. for ( i = 0; i < iLen; i += 4 ) {
  18674. r = data[ i ];
  18675. g = data[ i + 1 ];
  18676. b = data[ i + 2 ];
  18677. a = data[ i + 3 ];
  18678. data[ i ] = r * m[ 0 ] + g * m[ 1 ] + b * m[ 2 ] + a * m[ 3 ] + m[ 4 ];
  18679. data[ i + 1 ] = r * m[ 5 ] + g * m[ 6 ] + b * m[ 7 ] + a * m[ 8 ] + m[ 9 ];
  18680. data[ i + 2 ] = r * m[ 10 ] + g * m[ 11 ] + b * m[ 12 ] + a * m[ 13 ] + m[ 14 ];
  18681. data[ i + 3 ] = r * m[ 15 ] + g * m[ 16 ] + b * m[ 17 ] + a * m[ 18 ] + m[ 19 ];
  18682. }
  18683. context.putImageData( imageData, 0, 0 );
  18684. },
  18685. /**
  18686. * Returns object representation of an instance
  18687. * @return {Object} Object representation of an instance
  18688. */
  18689. toObject: function() {
  18690. return extend(this.callSuper('toObject'), {
  18691. type: this.type,
  18692. matrix: this.matrix
  18693. });
  18694. }
  18695. });
  18696. /**
  18697. * Returns filter instance from an object representation
  18698. * @static
  18699. * @param {Object} object Object to create an instance from
  18700. * @param {function} [callback] function to invoke after filter creation
  18701. * @return {fabric.Image.filters.ColorMatrix} Instance of fabric.Image.filters.ColorMatrix
  18702. */
  18703. fabric.Image.filters.ColorMatrix.fromObject = fabric.Image.filters.BaseFilter.fromObject;
  18704. })(typeof exports !== 'undefined' ? exports : this);
  18705. (function(global) {
  18706. 'use strict';
  18707. var fabric = global.fabric || (global.fabric = { }),
  18708. extend = fabric.util.object.extend,
  18709. filters = fabric.Image.filters,
  18710. createClass = fabric.util.createClass;
  18711. /**
  18712. * Contrast filter class
  18713. * @class fabric.Image.filters.Contrast
  18714. * @memberOf fabric.Image.filters
  18715. * @extends fabric.Image.filters.BaseFilter
  18716. * @see {@link fabric.Image.filters.Contrast#initialize} for constructor definition
  18717. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  18718. * @example
  18719. * var filter = new fabric.Image.filters.Contrast({
  18720. * contrast: 40
  18721. * });
  18722. * object.filters.push(filter);
  18723. * object.applyFilters(canvas.renderAll.bind(canvas));
  18724. */
  18725. filters.Contrast = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Contrast.prototype */ {
  18726. /**
  18727. * Filter type
  18728. * @param {String} type
  18729. * @default
  18730. */
  18731. type: 'Contrast',
  18732. /**
  18733. * Constructor
  18734. * @memberOf fabric.Image.filters.Contrast.prototype
  18735. * @param {Object} [options] Options object
  18736. * @param {Number} [options.contrast=0] Value to contrast the image up (-255...255)
  18737. */
  18738. initialize: function(options) {
  18739. options = options || { };
  18740. this.contrast = options.contrast || 0;
  18741. },
  18742. /**
  18743. * Applies filter to canvas element
  18744. * @param {Object} canvasEl Canvas element to apply filter to
  18745. */
  18746. applyTo: function(canvasEl) {
  18747. var context = canvasEl.getContext('2d'),
  18748. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  18749. data = imageData.data,
  18750. contrastF = 259 * (this.contrast + 255) / (255 * (259 - this.contrast));
  18751. for (var i = 0, len = data.length; i < len; i += 4) {
  18752. data[i] = contrastF * (data[i] - 128) + 128;
  18753. data[i + 1] = contrastF * (data[i + 1] - 128) + 128;
  18754. data[i + 2] = contrastF * (data[i + 2] - 128) + 128;
  18755. }
  18756. context.putImageData(imageData, 0, 0);
  18757. },
  18758. /**
  18759. * Returns object representation of an instance
  18760. * @return {Object} Object representation of an instance
  18761. */
  18762. toObject: function() {
  18763. return extend(this.callSuper('toObject'), {
  18764. contrast: this.contrast
  18765. });
  18766. }
  18767. });
  18768. /**
  18769. * Returns filter instance from an object representation
  18770. * @static
  18771. * @param {Object} object Object to create an instance from
  18772. * @param {function} [callback] to be invoked after filter creation
  18773. * @return {fabric.Image.filters.Contrast} Instance of fabric.Image.filters.Contrast
  18774. */
  18775. fabric.Image.filters.Contrast.fromObject = fabric.Image.filters.BaseFilter.fromObject;
  18776. })(typeof exports !== 'undefined' ? exports : this);
  18777. (function(global) {
  18778. 'use strict';
  18779. var fabric = global.fabric || (global.fabric = { }),
  18780. extend = fabric.util.object.extend,
  18781. filters = fabric.Image.filters,
  18782. createClass = fabric.util.createClass;
  18783. /**
  18784. * Saturate filter class
  18785. * @class fabric.Image.filters.Saturate
  18786. * @memberOf fabric.Image.filters
  18787. * @extends fabric.Image.filters.BaseFilter
  18788. * @see {@link fabric.Image.filters.Saturate#initialize} for constructor definition
  18789. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  18790. * @example
  18791. * var filter = new fabric.Image.filters.Saturate({
  18792. * saturate: 100
  18793. * });
  18794. * object.filters.push(filter);
  18795. * object.applyFilters(canvas.renderAll.bind(canvas));
  18796. */
  18797. filters.Saturate = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Saturate.prototype */ {
  18798. /**
  18799. * Filter type
  18800. * @param {String} type
  18801. * @default
  18802. */
  18803. type: 'Saturate',
  18804. /**
  18805. * Constructor
  18806. * @memberOf fabric.Image.filters.Saturate.prototype
  18807. * @param {Object} [options] Options object
  18808. * @param {Number} [options.saturate=0] Value to saturate the image (-100...100)
  18809. */
  18810. initialize: function(options) {
  18811. options = options || { };
  18812. this.saturate = options.saturate || 0;
  18813. },
  18814. /**
  18815. * Applies filter to canvas element
  18816. * @param {Object} canvasEl Canvas element to apply filter to
  18817. */
  18818. applyTo: function(canvasEl) {
  18819. var context = canvasEl.getContext('2d'),
  18820. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  18821. data = imageData.data,
  18822. max, adjust = -this.saturate * 0.01;
  18823. for (var i = 0, len = data.length; i < len; i += 4) {
  18824. max = Math.max(data[i], data[i + 1], data[i + 2]);
  18825. data[i] += max !== data[i] ? (max - data[i]) * adjust : 0;
  18826. data[i + 1] += max !== data[i + 1] ? (max - data[i + 1]) * adjust : 0;
  18827. data[i + 2] += max !== data[i + 2] ? (max - data[i + 2]) * adjust : 0;
  18828. }
  18829. context.putImageData(imageData, 0, 0);
  18830. },
  18831. /**
  18832. * Returns object representation of an instance
  18833. * @return {Object} Object representation of an instance
  18834. */
  18835. toObject: function() {
  18836. return extend(this.callSuper('toObject'), {
  18837. saturate: this.saturate
  18838. });
  18839. }
  18840. });
  18841. /**
  18842. * Returns filter instance from an object representation
  18843. * @static
  18844. * @param {Object} object Object to create an instance from
  18845. * @param {Function} [callback] to be invoked after filter creation
  18846. * @return {fabric.Image.filters.Saturate} Instance of fabric.Image.filters.Saturate
  18847. */
  18848. fabric.Image.filters.Saturate.fromObject = fabric.Image.filters.BaseFilter.fromObject;
  18849. })(typeof exports !== 'undefined' ? exports : this);
  18850. (function(global) {
  18851. 'use strict';
  18852. var fabric = global.fabric || (global.fabric = { }),
  18853. toFixed = fabric.util.toFixed,
  18854. NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS,
  18855. MIN_TEXT_WIDTH = 2;
  18856. if (fabric.Text) {
  18857. fabric.warn('fabric.Text is already defined');
  18858. return;
  18859. }
  18860. var stateProperties = fabric.Object.prototype.stateProperties.concat();
  18861. stateProperties.push(
  18862. 'fontFamily',
  18863. 'fontWeight',
  18864. 'fontSize',
  18865. 'text',
  18866. 'textDecoration',
  18867. 'textAlign',
  18868. 'fontStyle',
  18869. 'lineHeight',
  18870. 'textBackgroundColor',
  18871. 'charSpacing'
  18872. );
  18873. var cacheProperties = fabric.Object.prototype.cacheProperties.concat();
  18874. cacheProperties.push(
  18875. 'fontFamily',
  18876. 'fontWeight',
  18877. 'fontSize',
  18878. 'text',
  18879. 'textDecoration',
  18880. 'textAlign',
  18881. 'fontStyle',
  18882. 'lineHeight',
  18883. 'textBackgroundColor',
  18884. 'charSpacing',
  18885. 'styles'
  18886. );
  18887. /**
  18888. * Text class
  18889. * @class fabric.Text
  18890. * @extends fabric.Object
  18891. * @return {fabric.Text} thisArg
  18892. * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#text}
  18893. * @see {@link fabric.Text#initialize} for constructor definition
  18894. */
  18895. fabric.Text = fabric.util.createClass(fabric.Object, /** @lends fabric.Text.prototype */ {
  18896. /**
  18897. * Properties which when set cause object to change dimensions
  18898. * @type Object
  18899. * @private
  18900. */
  18901. _dimensionAffectingProps: [
  18902. 'fontSize',
  18903. 'fontWeight',
  18904. 'fontFamily',
  18905. 'fontStyle',
  18906. 'lineHeight',
  18907. 'text',
  18908. 'charSpacing',
  18909. 'textAlign'
  18910. ],
  18911. /**
  18912. * @private
  18913. */
  18914. _reNewline: /\r?\n/,
  18915. /**
  18916. * Use this regular expression to filter for whitespace that is not a new line.
  18917. * Mostly used when text is 'justify' aligned.
  18918. * @private
  18919. */
  18920. _reSpacesAndTabs: /[ \t\r]+/g,
  18921. /**
  18922. * Retrieves object's fontSize
  18923. * @method getFontSize
  18924. * @memberOf fabric.Text.prototype
  18925. * @return {String} Font size (in pixels)
  18926. */
  18927. /**
  18928. * Sets object's fontSize
  18929. * Does not update the object .width and .height,
  18930. * call ._initDimensions() to update the values.
  18931. * @method setFontSize
  18932. * @memberOf fabric.Text.prototype
  18933. * @param {Number} fontSize Font size (in pixels)
  18934. * @return {fabric.Text}
  18935. * @chainable
  18936. */
  18937. /**
  18938. * Retrieves object's fontWeight
  18939. * @method getFontWeight
  18940. * @memberOf fabric.Text.prototype
  18941. * @return {(String|Number)} Font weight
  18942. */
  18943. /**
  18944. * Sets object's fontWeight
  18945. * Does not update the object .width and .height,
  18946. * call ._initDimensions() to update the values.
  18947. * @method setFontWeight
  18948. * @memberOf fabric.Text.prototype
  18949. * @param {(Number|String)} fontWeight Font weight
  18950. * @return {fabric.Text}
  18951. * @chainable
  18952. */
  18953. /**
  18954. * Retrieves object's fontFamily
  18955. * @method getFontFamily
  18956. * @memberOf fabric.Text.prototype
  18957. * @return {String} Font family
  18958. */
  18959. /**
  18960. * Sets object's fontFamily
  18961. * Does not update the object .width and .height,
  18962. * call ._initDimensions() to update the values.
  18963. * @method setFontFamily
  18964. * @memberOf fabric.Text.prototype
  18965. * @param {String} fontFamily Font family
  18966. * @return {fabric.Text}
  18967. * @chainable
  18968. */
  18969. /**
  18970. * Retrieves object's text
  18971. * @method getText
  18972. * @memberOf fabric.Text.prototype
  18973. * @return {String} text
  18974. */
  18975. /**
  18976. * Sets object's text
  18977. * Does not update the object .width and .height,
  18978. * call ._initDimensions() to update the values.
  18979. * @method setText
  18980. * @memberOf fabric.Text.prototype
  18981. * @param {String} text Text
  18982. * @return {fabric.Text}
  18983. * @chainable
  18984. */
  18985. /**
  18986. * Retrieves object's textDecoration
  18987. * @method getTextDecoration
  18988. * @memberOf fabric.Text.prototype
  18989. * @return {String} Text decoration
  18990. */
  18991. /**
  18992. * Sets object's textDecoration
  18993. * @method setTextDecoration
  18994. * @memberOf fabric.Text.prototype
  18995. * @param {String} textDecoration Text decoration
  18996. * @return {fabric.Text}
  18997. * @chainable
  18998. */
  18999. /**
  19000. * Retrieves object's fontStyle
  19001. * @method getFontStyle
  19002. * @memberOf fabric.Text.prototype
  19003. * @return {String} Font style
  19004. */
  19005. /**
  19006. * Sets object's fontStyle
  19007. * Does not update the object .width and .height,
  19008. * call ._initDimensions() to update the values.
  19009. * @method setFontStyle
  19010. * @memberOf fabric.Text.prototype
  19011. * @param {String} fontStyle Font style
  19012. * @return {fabric.Text}
  19013. * @chainable
  19014. */
  19015. /**
  19016. * Retrieves object's lineHeight
  19017. * @method getLineHeight
  19018. * @memberOf fabric.Text.prototype
  19019. * @return {Number} Line height
  19020. */
  19021. /**
  19022. * Sets object's lineHeight
  19023. * @method setLineHeight
  19024. * @memberOf fabric.Text.prototype
  19025. * @param {Number} lineHeight Line height
  19026. * @return {fabric.Text}
  19027. * @chainable
  19028. */
  19029. /**
  19030. * Retrieves object's textAlign
  19031. * @method getTextAlign
  19032. * @memberOf fabric.Text.prototype
  19033. * @return {String} Text alignment
  19034. */
  19035. /**
  19036. * Sets object's textAlign
  19037. * @method setTextAlign
  19038. * @memberOf fabric.Text.prototype
  19039. * @param {String} textAlign Text alignment
  19040. * @return {fabric.Text}
  19041. * @chainable
  19042. */
  19043. /**
  19044. * Retrieves object's textBackgroundColor
  19045. * @method getTextBackgroundColor
  19046. * @memberOf fabric.Text.prototype
  19047. * @return {String} Text background color
  19048. */
  19049. /**
  19050. * Sets object's textBackgroundColor
  19051. * @method setTextBackgroundColor
  19052. * @memberOf fabric.Text.prototype
  19053. * @param {String} textBackgroundColor Text background color
  19054. * @return {fabric.Text}
  19055. * @chainable
  19056. */
  19057. /**
  19058. * Type of an object
  19059. * @type String
  19060. * @default
  19061. */
  19062. type: 'text',
  19063. /**
  19064. * Font size (in pixels)
  19065. * @type Number
  19066. * @default
  19067. */
  19068. fontSize: 40,
  19069. /**
  19070. * Font weight (e.g. bold, normal, 400, 600, 800)
  19071. * @type {(Number|String)}
  19072. * @default
  19073. */
  19074. fontWeight: 'normal',
  19075. /**
  19076. * Font family
  19077. * @type String
  19078. * @default
  19079. */
  19080. fontFamily: 'Times New Roman',
  19081. /**
  19082. * Text decoration Possible values: "", "underline", "overline" or "line-through".
  19083. * @type String
  19084. * @default
  19085. */
  19086. textDecoration: '',
  19087. /**
  19088. * Text alignment. Possible values: "left", "center", "right" or "justify".
  19089. * @type String
  19090. * @default
  19091. */
  19092. textAlign: 'left',
  19093. /**
  19094. * Font style . Possible values: "", "normal", "italic" or "oblique".
  19095. * @type String
  19096. * @default
  19097. */
  19098. fontStyle: '',
  19099. /**
  19100. * Line height
  19101. * @type Number
  19102. * @default
  19103. */
  19104. lineHeight: 1.16,
  19105. /**
  19106. * Background color of text lines
  19107. * @type String
  19108. * @default
  19109. */
  19110. textBackgroundColor: '',
  19111. /**
  19112. * List of properties to consider when checking if
  19113. * state of an object is changed ({@link fabric.Object#hasStateChanged})
  19114. * as well as for history (undo/redo) purposes
  19115. * @type Array
  19116. */
  19117. stateProperties: stateProperties,
  19118. /**
  19119. * List of properties to consider when checking if cache needs refresh
  19120. * @type Array
  19121. */
  19122. cacheProperties: cacheProperties,
  19123. /**
  19124. * When defined, an object is rendered via stroke and this property specifies its color.
  19125. * <b>Backwards incompatibility note:</b> This property was named "strokeStyle" until v1.1.6
  19126. * @type String
  19127. * @default
  19128. */
  19129. stroke: null,
  19130. /**
  19131. * Shadow object representing shadow of this shape.
  19132. * <b>Backwards incompatibility note:</b> This property was named "textShadow" (String) until v1.2.11
  19133. * @type fabric.Shadow
  19134. * @default
  19135. */
  19136. shadow: null,
  19137. /**
  19138. * @private
  19139. */
  19140. _fontSizeFraction: 0.25,
  19141. /**
  19142. * Text Line proportion to font Size (in pixels)
  19143. * @type Number
  19144. * @default
  19145. */
  19146. _fontSizeMult: 1.13,
  19147. /**
  19148. * additional space between characters
  19149. * expressed in thousands of em unit
  19150. * @type Number
  19151. * @default
  19152. */
  19153. charSpacing: 0,
  19154. /**
  19155. * Constructor
  19156. * @param {String} text Text string
  19157. * @param {Object} [options] Options object
  19158. * @return {fabric.Text} thisArg
  19159. */
  19160. initialize: function(text, options) {
  19161. options = options || { };
  19162. this.text = text;
  19163. this.__skipDimension = true;
  19164. this.callSuper('initialize', options);
  19165. this.__skipDimension = false;
  19166. this._initDimensions();
  19167. this.setCoords();
  19168. this.setupState({ propertySet: '_dimensionAffectingProps' });
  19169. },
  19170. /**
  19171. * Initialize text dimensions. Render all text on given context
  19172. * or on a offscreen canvas to get the text width with measureText.
  19173. * Updates this.width and this.height with the proper values.
  19174. * Does not return dimensions.
  19175. * @param {CanvasRenderingContext2D} [ctx] Context to render on
  19176. * @private
  19177. */
  19178. _initDimensions: function(ctx) {
  19179. if (this.__skipDimension) {
  19180. return;
  19181. }
  19182. if (!ctx) {
  19183. ctx = fabric.util.createCanvasElement().getContext('2d');
  19184. this._setTextStyles(ctx);
  19185. }
  19186. this._textLines = this._splitTextIntoLines();
  19187. this._clearCache();
  19188. this.width = this._getTextWidth(ctx) || this.cursorWidth || MIN_TEXT_WIDTH;
  19189. this.height = this._getTextHeight(ctx);
  19190. },
  19191. /**
  19192. * Returns string representation of an instance
  19193. * @return {String} String representation of text object
  19194. */
  19195. toString: function() {
  19196. return '#<fabric.Text (' + this.complexity() +
  19197. '): { "text": "' + this.text + '", "fontFamily": "' + this.fontFamily + '" }>';
  19198. },
  19199. /**
  19200. * Return the dimension and the zoom level needed to create a cache canvas
  19201. * big enough to host the object to be cached.
  19202. * @private
  19203. * @return {Object}.width width of canvas
  19204. * @return {Object}.height height of canvas
  19205. * @return {Object}.zoomX zoomX zoom value to unscale the canvas before drawing cache
  19206. * @return {Object}.zoomY zoomY zoom value to unscale the canvas before drawing cache
  19207. */
  19208. _getCacheCanvasDimensions: function() {
  19209. var dim = this.callSuper('_getCacheCanvasDimensions');
  19210. var fontSize = this.fontSize * 2;
  19211. dim.width += fontSize * dim.zoomX;
  19212. dim.height += fontSize * dim.zoomY;
  19213. return dim;
  19214. },
  19215. /**
  19216. * @private
  19217. * @param {CanvasRenderingContext2D} ctx Context to render on
  19218. */
  19219. _render: function(ctx) {
  19220. this._setTextStyles(ctx);
  19221. if (this.group && this.group.type === 'path-group') {
  19222. ctx.translate(this.left, this.top);
  19223. }
  19224. this._renderTextLinesBackground(ctx);
  19225. this._renderText(ctx);
  19226. this._renderTextDecoration(ctx);
  19227. },
  19228. /**
  19229. * @private
  19230. * @param {CanvasRenderingContext2D} ctx Context to render on
  19231. */
  19232. _renderText: function(ctx) {
  19233. this._renderTextFill(ctx);
  19234. this._renderTextStroke(ctx);
  19235. },
  19236. /**
  19237. * @private
  19238. * @param {CanvasRenderingContext2D} ctx Context to render on
  19239. */
  19240. _setTextStyles: function(ctx) {
  19241. ctx.textBaseline = 'alphabetic';
  19242. ctx.font = this._getFontDeclaration();
  19243. },
  19244. /**
  19245. * @private
  19246. * @return {Number} Height of fabric.Text object
  19247. */
  19248. _getTextHeight: function() {
  19249. return this._getHeightOfSingleLine() + (this._textLines.length - 1) * this._getHeightOfLine();
  19250. },
  19251. /**
  19252. * @private
  19253. * @param {CanvasRenderingContext2D} ctx Context to render on
  19254. * @return {Number} Maximum width of fabric.Text object
  19255. */
  19256. _getTextWidth: function(ctx) {
  19257. var maxWidth = this._getLineWidth(ctx, 0);
  19258. for (var i = 1, len = this._textLines.length; i < len; i++) {
  19259. var currentLineWidth = this._getLineWidth(ctx, i);
  19260. if (currentLineWidth > maxWidth) {
  19261. maxWidth = currentLineWidth;
  19262. }
  19263. }
  19264. return maxWidth;
  19265. },
  19266. /**
  19267. * @private
  19268. * @param {String} method Method name ("fillText" or "strokeText")
  19269. * @param {CanvasRenderingContext2D} ctx Context to render on
  19270. * @param {String} chars Chars to render
  19271. * @param {Number} left Left position of text
  19272. * @param {Number} top Top position of text
  19273. */
  19274. _renderChars: function(method, ctx, chars, left, top) {
  19275. // remove Text word from method var
  19276. var shortM = method.slice(0, -4), char, width;
  19277. if (this[shortM].toLive) {
  19278. var offsetX = -this.width / 2 + this[shortM].offsetX || 0,
  19279. offsetY = -this.height / 2 + this[shortM].offsetY || 0;
  19280. ctx.save();
  19281. ctx.translate(offsetX, offsetY);
  19282. left -= offsetX;
  19283. top -= offsetY;
  19284. }
  19285. if (this.charSpacing !== 0) {
  19286. var additionalSpace = this._getWidthOfCharSpacing();
  19287. chars = chars.split('');
  19288. for (var i = 0, len = chars.length; i < len; i++) {
  19289. char = chars[i];
  19290. width = ctx.measureText(char).width + additionalSpace;
  19291. ctx[method](char, left, top);
  19292. left += width > 0 ? width : 0;
  19293. }
  19294. }
  19295. else {
  19296. ctx[method](chars, left, top);
  19297. }
  19298. this[shortM].toLive && ctx.restore();
  19299. },
  19300. /**
  19301. * @private
  19302. * @param {String} method Method name ("fillText" or "strokeText")
  19303. * @param {CanvasRenderingContext2D} ctx Context to render on
  19304. * @param {String} line Text to render
  19305. * @param {Number} left Left position of text
  19306. * @param {Number} top Top position of text
  19307. * @param {Number} lineIndex Index of a line in a text
  19308. */
  19309. _renderTextLine: function(method, ctx, line, left, top, lineIndex) {
  19310. // lift the line by quarter of fontSize
  19311. top -= this.fontSize * this._fontSizeFraction;
  19312. // short-circuit
  19313. var lineWidth = this._getLineWidth(ctx, lineIndex);
  19314. if (this.textAlign !== 'justify' || this.width < lineWidth) {
  19315. this._renderChars(method, ctx, line, left, top, lineIndex);
  19316. return;
  19317. }
  19318. // stretch the line
  19319. var words = line.split(/\s+/),
  19320. charOffset = 0,
  19321. wordsWidth = this._getWidthOfWords(ctx, words.join(' '), lineIndex, 0),
  19322. widthDiff = this.width - wordsWidth,
  19323. numSpaces = words.length - 1,
  19324. spaceWidth = numSpaces > 0 ? widthDiff / numSpaces : 0,
  19325. leftOffset = 0, word;
  19326. for (var i = 0, len = words.length; i < len; i++) {
  19327. while (line[charOffset] === ' ' && charOffset < line.length) {
  19328. charOffset++;
  19329. }
  19330. word = words[i];
  19331. this._renderChars(method, ctx, word, left + leftOffset, top, lineIndex, charOffset);
  19332. leftOffset += this._getWidthOfWords(ctx, word, lineIndex, charOffset) + spaceWidth;
  19333. charOffset += word.length;
  19334. }
  19335. },
  19336. /**
  19337. * @private
  19338. * @param {CanvasRenderingContext2D} ctx Context to render on
  19339. * @param {String} word
  19340. */
  19341. _getWidthOfWords: function (ctx, word) {
  19342. var width = ctx.measureText(word).width, charCount, additionalSpace;
  19343. if (this.charSpacing !== 0) {
  19344. charCount = word.split('').length;
  19345. additionalSpace = charCount * this._getWidthOfCharSpacing();
  19346. width += additionalSpace;
  19347. }
  19348. return width > 0 ? width : 0;
  19349. },
  19350. /**
  19351. * @private
  19352. * @return {Number} Left offset
  19353. */
  19354. _getLeftOffset: function() {
  19355. return -this.width / 2;
  19356. },
  19357. /**
  19358. * @private
  19359. * @return {Number} Top offset
  19360. */
  19361. _getTopOffset: function() {
  19362. return -this.height / 2;
  19363. },
  19364. /**
  19365. * Returns true because text has no style
  19366. */
  19367. isEmptyStyles: function() {
  19368. return true;
  19369. },
  19370. /**
  19371. * @private
  19372. * @param {CanvasRenderingContext2D} ctx Context to render on
  19373. * @param {String} method Method name ("fillText" or "strokeText")
  19374. */
  19375. _renderTextCommon: function(ctx, method) {
  19376. var lineHeights = 0, left = this._getLeftOffset(), top = this._getTopOffset();
  19377. for (var i = 0, len = this._textLines.length; i < len; i++) {
  19378. var heightOfLine = this._getHeightOfLine(ctx, i),
  19379. maxHeight = heightOfLine / this.lineHeight,
  19380. lineWidth = this._getLineWidth(ctx, i),
  19381. leftOffset = this._getLineLeftOffset(lineWidth);
  19382. this._renderTextLine(
  19383. method,
  19384. ctx,
  19385. this._textLines[i],
  19386. left + leftOffset,
  19387. top + lineHeights + maxHeight,
  19388. i
  19389. );
  19390. lineHeights += heightOfLine;
  19391. }
  19392. },
  19393. /**
  19394. * @private
  19395. * @param {CanvasRenderingContext2D} ctx Context to render on
  19396. */
  19397. _renderTextFill: function(ctx) {
  19398. if (!this.fill && this.isEmptyStyles()) {
  19399. return;
  19400. }
  19401. this._renderTextCommon(ctx, 'fillText');
  19402. },
  19403. /**
  19404. * @private
  19405. * @param {CanvasRenderingContext2D} ctx Context to render on
  19406. */
  19407. _renderTextStroke: function(ctx) {
  19408. if ((!this.stroke || this.strokeWidth === 0) && this.isEmptyStyles()) {
  19409. return;
  19410. }
  19411. if (this.shadow && !this.shadow.affectStroke) {
  19412. this._removeShadow(ctx);
  19413. }
  19414. ctx.save();
  19415. this._setLineDash(ctx, this.strokeDashArray);
  19416. ctx.beginPath();
  19417. this._renderTextCommon(ctx, 'strokeText');
  19418. ctx.closePath();
  19419. ctx.restore();
  19420. },
  19421. /**
  19422. * @private
  19423. * @return {Number} height of line
  19424. */
  19425. _getHeightOfLine: function() {
  19426. return this._getHeightOfSingleLine() * this.lineHeight;
  19427. },
  19428. /**
  19429. * @private
  19430. * @return {Number} height of line without lineHeight
  19431. */
  19432. _getHeightOfSingleLine: function() {
  19433. return this.fontSize * this._fontSizeMult;
  19434. },
  19435. /**
  19436. * @private
  19437. * @param {CanvasRenderingContext2D} ctx Context to render on
  19438. */
  19439. _renderTextLinesBackground: function(ctx) {
  19440. if (!this.textBackgroundColor) {
  19441. return;
  19442. }
  19443. var lineTopOffset = 0, heightOfLine,
  19444. lineWidth, lineLeftOffset, originalFill = ctx.fillStyle;
  19445. ctx.fillStyle = this.textBackgroundColor;
  19446. for (var i = 0, len = this._textLines.length; i < len; i++) {
  19447. heightOfLine = this._getHeightOfLine(ctx, i);
  19448. lineWidth = this._getLineWidth(ctx, i);
  19449. if (lineWidth > 0) {
  19450. lineLeftOffset = this._getLineLeftOffset(lineWidth);
  19451. ctx.fillRect(
  19452. this._getLeftOffset() + lineLeftOffset,
  19453. this._getTopOffset() + lineTopOffset,
  19454. lineWidth,
  19455. heightOfLine / this.lineHeight
  19456. );
  19457. }
  19458. lineTopOffset += heightOfLine;
  19459. }
  19460. ctx.fillStyle = originalFill;
  19461. // if there is text background color no
  19462. // other shadows should be casted
  19463. this._removeShadow(ctx);
  19464. },
  19465. /**
  19466. * @private
  19467. * @param {Number} lineWidth Width of text line
  19468. * @return {Number} Line left offset
  19469. */
  19470. _getLineLeftOffset: function(lineWidth) {
  19471. if (this.textAlign === 'center') {
  19472. return (this.width - lineWidth) / 2;
  19473. }
  19474. if (this.textAlign === 'right') {
  19475. return this.width - lineWidth;
  19476. }
  19477. return 0;
  19478. },
  19479. /**
  19480. * @private
  19481. */
  19482. _clearCache: function() {
  19483. this.__lineWidths = [];
  19484. this.__lineHeights = [];
  19485. },
  19486. /**
  19487. * @private
  19488. */
  19489. _shouldClearDimensionCache: function() {
  19490. var shouldClear = this._forceClearCache;
  19491. shouldClear || (shouldClear = this.hasStateChanged('_dimensionAffectingProps'));
  19492. if (shouldClear) {
  19493. this.saveState({ propertySet: '_dimensionAffectingProps' });
  19494. this.dirty = true;
  19495. }
  19496. return shouldClear;
  19497. },
  19498. /**
  19499. * @private
  19500. * @param {CanvasRenderingContext2D} ctx Context to render on
  19501. * @param {Number} lineIndex line number
  19502. * @return {Number} Line width
  19503. */
  19504. _getLineWidth: function(ctx, lineIndex) {
  19505. if (this.__lineWidths[lineIndex]) {
  19506. return this.__lineWidths[lineIndex] === -1 ? this.width : this.__lineWidths[lineIndex];
  19507. }
  19508. var width, wordCount, line = this._textLines[lineIndex];
  19509. if (line === '') {
  19510. width = 0;
  19511. }
  19512. else {
  19513. width = this._measureLine(ctx, lineIndex);
  19514. }
  19515. this.__lineWidths[lineIndex] = width;
  19516. if (width && this.textAlign === 'justify') {
  19517. wordCount = line.split(/\s+/);
  19518. if (wordCount.length > 1) {
  19519. this.__lineWidths[lineIndex] = -1;
  19520. }
  19521. }
  19522. return width;
  19523. },
  19524. _getWidthOfCharSpacing: function() {
  19525. if (this.charSpacing !== 0) {
  19526. return this.fontSize * this.charSpacing / 1000;
  19527. }
  19528. return 0;
  19529. },
  19530. /**
  19531. * @private
  19532. * @param {CanvasRenderingContext2D} ctx Context to render on
  19533. * @param {Number} lineIndex line number
  19534. * @return {Number} Line width
  19535. */
  19536. _measureLine: function(ctx, lineIndex) {
  19537. var line = this._textLines[lineIndex],
  19538. width = ctx.measureText(line).width,
  19539. additionalSpace = 0, charCount, finalWidth;
  19540. if (this.charSpacing !== 0) {
  19541. charCount = line.split('').length;
  19542. additionalSpace = (charCount - 1) * this._getWidthOfCharSpacing();
  19543. }
  19544. finalWidth = width + additionalSpace;
  19545. return finalWidth > 0 ? finalWidth : 0;
  19546. },
  19547. /**
  19548. * @private
  19549. * @param {CanvasRenderingContext2D} ctx Context to render on
  19550. */
  19551. _renderTextDecoration: function(ctx) {
  19552. if (!this.textDecoration) {
  19553. return;
  19554. }
  19555. var halfOfVerticalBox = this.height / 2,
  19556. _this = this, offsets = [];
  19557. /** @ignore */
  19558. function renderLinesAtOffset(offsets) {
  19559. var i, lineHeight = 0, len, j, oLen, lineWidth,
  19560. lineLeftOffset, heightOfLine;
  19561. for (i = 0, len = _this._textLines.length; i < len; i++) {
  19562. lineWidth = _this._getLineWidth(ctx, i);
  19563. lineLeftOffset = _this._getLineLeftOffset(lineWidth);
  19564. heightOfLine = _this._getHeightOfLine(ctx, i);
  19565. for (j = 0, oLen = offsets.length; j < oLen; j++) {
  19566. ctx.fillRect(
  19567. _this._getLeftOffset() + lineLeftOffset,
  19568. lineHeight + (_this._fontSizeMult - 1 + offsets[j] ) * _this.fontSize - halfOfVerticalBox,
  19569. lineWidth,
  19570. _this.fontSize / 15);
  19571. }
  19572. lineHeight += heightOfLine;
  19573. }
  19574. }
  19575. if (this.textDecoration.indexOf('underline') > -1) {
  19576. offsets.push(0.85); // 1 - 3/16
  19577. }
  19578. if (this.textDecoration.indexOf('line-through') > -1) {
  19579. offsets.push(0.43);
  19580. }
  19581. if (this.textDecoration.indexOf('overline') > -1) {
  19582. offsets.push(-0.12);
  19583. }
  19584. if (offsets.length > 0) {
  19585. renderLinesAtOffset(offsets);
  19586. }
  19587. },
  19588. /**
  19589. * return font declaration string for canvas context
  19590. * @returns {String} font declaration formatted for canvas context.
  19591. */
  19592. _getFontDeclaration: function() {
  19593. return [
  19594. // node-canvas needs "weight style", while browsers need "style weight"
  19595. (fabric.isLikelyNode ? this.fontWeight : this.fontStyle),
  19596. (fabric.isLikelyNode ? this.fontStyle : this.fontWeight),
  19597. this.fontSize + 'px',
  19598. (fabric.isLikelyNode ? ('"' + this.fontFamily + '"') : this.fontFamily)
  19599. ].join(' ');
  19600. },
  19601. /**
  19602. * Renders text instance on a specified context
  19603. * @param {CanvasRenderingContext2D} ctx Context to render on
  19604. * @param {Boolean} noTransform
  19605. */
  19606. render: function(ctx, noTransform) {
  19607. // do not render if object is not visible
  19608. if (!this.visible) {
  19609. return;
  19610. }
  19611. if (this.canvas && this.canvas.skipOffscreen && !this.group && !this.isOnScreen()) {
  19612. return;
  19613. }
  19614. if (this._shouldClearDimensionCache()) {
  19615. this._setTextStyles(ctx);
  19616. this._initDimensions(ctx);
  19617. }
  19618. this.callSuper('render', ctx, noTransform);
  19619. },
  19620. /**
  19621. * Returns the text as an array of lines.
  19622. * @returns {Array} Lines in the text
  19623. */
  19624. _splitTextIntoLines: function() {
  19625. return this.text.split(this._reNewline);
  19626. },
  19627. /**
  19628. * Returns object representation of an instance
  19629. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  19630. * @return {Object} Object representation of an instance
  19631. */
  19632. toObject: function(propertiesToInclude) {
  19633. var additionalProperties = [
  19634. 'text',
  19635. 'fontSize',
  19636. 'fontWeight',
  19637. 'fontFamily',
  19638. 'fontStyle',
  19639. 'lineHeight',
  19640. 'textDecoration',
  19641. 'textAlign',
  19642. 'textBackgroundColor',
  19643. 'charSpacing'
  19644. ].concat(propertiesToInclude);
  19645. return this.callSuper('toObject', additionalProperties);
  19646. },
  19647. /* _TO_SVG_START_ */
  19648. /**
  19649. * Returns SVG representation of an instance
  19650. * @param {Function} [reviver] Method for further parsing of svg representation.
  19651. * @return {String} svg representation of an instance
  19652. */
  19653. toSVG: function(reviver) {
  19654. if (!this.ctx) {
  19655. this.ctx = fabric.util.createCanvasElement().getContext('2d');
  19656. }
  19657. var markup = this._createBaseSVGMarkup(),
  19658. offsets = this._getSVGLeftTopOffsets(this.ctx),
  19659. textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft);
  19660. this._wrapSVGTextAndBg(markup, textAndBg);
  19661. return reviver ? reviver(markup.join('')) : markup.join('');
  19662. },
  19663. /**
  19664. * @private
  19665. */
  19666. _getSVGLeftTopOffsets: function(ctx) {
  19667. var lineTop = this._getHeightOfLine(ctx, 0),
  19668. textLeft = -this.width / 2,
  19669. textTop = 0;
  19670. return {
  19671. textLeft: textLeft + (this.group && this.group.type === 'path-group' ? this.left : 0),
  19672. textTop: textTop + (this.group && this.group.type === 'path-group' ? -this.top : 0),
  19673. lineTop: lineTop
  19674. };
  19675. },
  19676. /**
  19677. * @private
  19678. */
  19679. _wrapSVGTextAndBg: function(markup, textAndBg) {
  19680. var noShadow = true, filter = this.getSvgFilter(),
  19681. style = filter === '' ? '' : ' style="' + filter + '"';
  19682. markup.push(
  19683. '\t<g ', this.getSvgId(), 'transform="', this.getSvgTransform(), this.getSvgTransformMatrix(), '"',
  19684. style, '>\n',
  19685. textAndBg.textBgRects.join(''),
  19686. '\t\t<text ',
  19687. (this.fontFamily ? 'font-family="' + this.fontFamily.replace(/"/g, '\'') + '" ' : ''),
  19688. (this.fontSize ? 'font-size="' + this.fontSize + '" ' : ''),
  19689. (this.fontStyle ? 'font-style="' + this.fontStyle + '" ' : ''),
  19690. (this.fontWeight ? 'font-weight="' + this.fontWeight + '" ' : ''),
  19691. (this.textDecoration ? 'text-decoration="' + this.textDecoration + '" ' : ''),
  19692. 'style="', this.getSvgStyles(noShadow), '" >\n',
  19693. textAndBg.textSpans.join(''),
  19694. '\t\t</text>\n',
  19695. '\t</g>\n'
  19696. );
  19697. },
  19698. /**
  19699. * @private
  19700. * @param {Number} textTopOffset Text top offset
  19701. * @param {Number} textLeftOffset Text left offset
  19702. * @return {Object}
  19703. */
  19704. _getSVGTextAndBg: function(textTopOffset, textLeftOffset) {
  19705. var textSpans = [],
  19706. textBgRects = [],
  19707. height = 0;
  19708. // bounding-box background
  19709. this._setSVGBg(textBgRects);
  19710. // text and text-background
  19711. for (var i = 0, len = this._textLines.length; i < len; i++) {
  19712. if (this.textBackgroundColor) {
  19713. this._setSVGTextLineBg(textBgRects, i, textLeftOffset, textTopOffset, height);
  19714. }
  19715. this._setSVGTextLineText(i, textSpans, height, textLeftOffset, textTopOffset, textBgRects);
  19716. height += this._getHeightOfLine(this.ctx, i);
  19717. }
  19718. return {
  19719. textSpans: textSpans,
  19720. textBgRects: textBgRects
  19721. };
  19722. },
  19723. _setSVGTextLineText: function(i, textSpans, height, textLeftOffset, textTopOffset) {
  19724. var yPos = this.fontSize * (this._fontSizeMult - this._fontSizeFraction)
  19725. - textTopOffset + height - this.height / 2;
  19726. if (this.textAlign === 'justify') {
  19727. // i call from here to do not intefere with IText
  19728. this._setSVGTextLineJustifed(i, textSpans, yPos, textLeftOffset);
  19729. return;
  19730. }
  19731. textSpans.push(
  19732. '\t\t\t<tspan x="',
  19733. toFixed(textLeftOffset + this._getLineLeftOffset(this._getLineWidth(this.ctx, i)), NUM_FRACTION_DIGITS), '" ',
  19734. 'y="',
  19735. toFixed(yPos, NUM_FRACTION_DIGITS),
  19736. '" ',
  19737. // doing this on <tspan> elements since setting opacity
  19738. // on containing <text> one doesn't work in Illustrator
  19739. this._getFillAttributes(this.fill), '>',
  19740. fabric.util.string.escapeXml(this._textLines[i]),
  19741. '</tspan>\n'
  19742. );
  19743. },
  19744. _setSVGTextLineJustifed: function(i, textSpans, yPos, textLeftOffset) {
  19745. var ctx = fabric.util.createCanvasElement().getContext('2d');
  19746. this._setTextStyles(ctx);
  19747. var line = this._textLines[i],
  19748. words = line.split(/\s+/),
  19749. wordsWidth = this._getWidthOfWords(ctx, words.join('')),
  19750. widthDiff = this.width - wordsWidth,
  19751. numSpaces = words.length - 1,
  19752. spaceWidth = numSpaces > 0 ? widthDiff / numSpaces : 0,
  19753. word, attributes = this._getFillAttributes(this.fill),
  19754. len;
  19755. textLeftOffset += this._getLineLeftOffset(this._getLineWidth(ctx, i));
  19756. for (i = 0, len = words.length; i < len; i++) {
  19757. word = words[i];
  19758. textSpans.push(
  19759. '\t\t\t<tspan x="',
  19760. toFixed(textLeftOffset, NUM_FRACTION_DIGITS), '" ',
  19761. 'y="',
  19762. toFixed(yPos, NUM_FRACTION_DIGITS),
  19763. '" ',
  19764. // doing this on <tspan> elements since setting opacity
  19765. // on containing <text> one doesn't work in Illustrator
  19766. attributes, '>',
  19767. fabric.util.string.escapeXml(word),
  19768. '</tspan>\n'
  19769. );
  19770. textLeftOffset += this._getWidthOfWords(ctx, word) + spaceWidth;
  19771. }
  19772. },
  19773. _setSVGTextLineBg: function(textBgRects, i, textLeftOffset, textTopOffset, height) {
  19774. textBgRects.push(
  19775. '\t\t<rect ',
  19776. this._getFillAttributes(this.textBackgroundColor),
  19777. ' x="',
  19778. toFixed(textLeftOffset + this._getLineLeftOffset(this._getLineWidth(this.ctx, i)), NUM_FRACTION_DIGITS),
  19779. '" y="',
  19780. toFixed(height - this.height / 2, NUM_FRACTION_DIGITS),
  19781. '" width="',
  19782. toFixed(this._getLineWidth(this.ctx, i), NUM_FRACTION_DIGITS),
  19783. '" height="',
  19784. toFixed(this._getHeightOfLine(this.ctx, i) / this.lineHeight, NUM_FRACTION_DIGITS),
  19785. '"></rect>\n');
  19786. },
  19787. _setSVGBg: function(textBgRects) {
  19788. if (this.backgroundColor) {
  19789. textBgRects.push(
  19790. '\t\t<rect ',
  19791. this._getFillAttributes(this.backgroundColor),
  19792. ' x="',
  19793. toFixed(-this.width / 2, NUM_FRACTION_DIGITS),
  19794. '" y="',
  19795. toFixed(-this.height / 2, NUM_FRACTION_DIGITS),
  19796. '" width="',
  19797. toFixed(this.width, NUM_FRACTION_DIGITS),
  19798. '" height="',
  19799. toFixed(this.height, NUM_FRACTION_DIGITS),
  19800. '"></rect>\n');
  19801. }
  19802. },
  19803. /**
  19804. * Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values
  19805. * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1
  19806. *
  19807. * @private
  19808. * @param {*} value
  19809. * @return {String}
  19810. */
  19811. _getFillAttributes: function(value) {
  19812. var fillColor = (value && typeof value === 'string') ? new fabric.Color(value) : '';
  19813. if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) {
  19814. return 'fill="' + value + '"';
  19815. }
  19816. return 'opacity="' + fillColor.getAlpha() + '" fill="' + fillColor.setAlpha(1).toRgb() + '"';
  19817. },
  19818. /* _TO_SVG_END_ */
  19819. /**
  19820. * Sets specified property to a specified value
  19821. * @param {String} key
  19822. * @param {*} value
  19823. * @return {fabric.Text} thisArg
  19824. * @chainable
  19825. */
  19826. _set: function(key, value) {
  19827. this.callSuper('_set', key, value);
  19828. if (this._dimensionAffectingProps.indexOf(key) > -1) {
  19829. this._initDimensions();
  19830. this.setCoords();
  19831. }
  19832. },
  19833. /**
  19834. * Returns complexity of an instance
  19835. * @return {Number} complexity
  19836. */
  19837. complexity: function() {
  19838. return 1;
  19839. }
  19840. });
  19841. /* _FROM_SVG_START_ */
  19842. /**
  19843. * List of attribute names to account for when parsing SVG element (used by {@link fabric.Text.fromElement})
  19844. * @static
  19845. * @memberOf fabric.Text
  19846. * @see: http://www.w3.org/TR/SVG/text.html#TextElement
  19847. */
  19848. fabric.Text.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(
  19849. 'x y dx dy font-family font-style font-weight font-size text-decoration text-anchor'.split(' '));
  19850. /**
  19851. * Default SVG font size
  19852. * @static
  19853. * @memberOf fabric.Text
  19854. */
  19855. fabric.Text.DEFAULT_SVG_FONT_SIZE = 16;
  19856. /**
  19857. * Returns fabric.Text instance from an SVG element (<b>not yet implemented</b>)
  19858. * @static
  19859. * @memberOf fabric.Text
  19860. * @param {SVGElement} element Element to parse
  19861. * @param {Object} [options] Options object
  19862. * @return {fabric.Text} Instance of fabric.Text
  19863. */
  19864. fabric.Text.fromElement = function(element, options) {
  19865. if (!element) {
  19866. return null;
  19867. }
  19868. var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES);
  19869. options = fabric.util.object.extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes);
  19870. options.top = options.top || 0;
  19871. options.left = options.left || 0;
  19872. if ('dx' in parsedAttributes) {
  19873. options.left += parsedAttributes.dx;
  19874. }
  19875. if ('dy' in parsedAttributes) {
  19876. options.top += parsedAttributes.dy;
  19877. }
  19878. if (!('fontSize' in options)) {
  19879. options.fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE;
  19880. }
  19881. if (!options.originX) {
  19882. options.originX = 'left';
  19883. }
  19884. var textContent = '';
  19885. // The XML is not properly parsed in IE9 so a workaround to get
  19886. // textContent is through firstChild.data. Another workaround would be
  19887. // to convert XML loaded from a file to be converted using DOMParser (same way loadSVGFromString() does)
  19888. if (!('textContent' in element)) {
  19889. if ('firstChild' in element && element.firstChild !== null) {
  19890. if ('data' in element.firstChild && element.firstChild.data !== null) {
  19891. textContent = element.firstChild.data;
  19892. }
  19893. }
  19894. }
  19895. else {
  19896. textContent = element.textContent;
  19897. }
  19898. textContent = textContent.replace(/^\s+|\s+$|\n+/g, '').replace(/\s+/g, ' ');
  19899. var text = new fabric.Text(textContent, options),
  19900. textHeightScaleFactor = text.getHeight() / text.height,
  19901. lineHeightDiff = (text.height + text.strokeWidth) * text.lineHeight - text.height,
  19902. scaledDiff = lineHeightDiff * textHeightScaleFactor,
  19903. textHeight = text.getHeight() + scaledDiff,
  19904. offX = 0;
  19905. /*
  19906. Adjust positioning:
  19907. x/y attributes in SVG correspond to the bottom-left corner of text bounding box
  19908. top/left properties in Fabric correspond to center point of text bounding box
  19909. */
  19910. if (text.originX === 'left') {
  19911. offX = text.getWidth() / 2;
  19912. }
  19913. if (text.originX === 'right') {
  19914. offX = -text.getWidth() / 2;
  19915. }
  19916. text.set({
  19917. left: text.getLeft() + offX,
  19918. top: text.getTop() - textHeight / 2 + text.fontSize * (0.18 + text._fontSizeFraction) / text.lineHeight /* 0.3 is the old lineHeight */
  19919. });
  19920. return text;
  19921. };
  19922. /* _FROM_SVG_END_ */
  19923. /**
  19924. * Returns fabric.Text instance from an object representation
  19925. * @static
  19926. * @memberOf fabric.Text
  19927. * @param {Object} object Object to create an instance from
  19928. * @param {Function} [callback] Callback to invoke when an fabric.Text instance is created
  19929. * @param {Boolean} [forceAsync] Force an async behaviour trying to create pattern first
  19930. * @return {fabric.Text} Instance of fabric.Text
  19931. */
  19932. fabric.Text.fromObject = function(object, callback, forceAsync) {
  19933. return fabric.Object._fromObject('Text', object, callback, forceAsync, 'text');
  19934. };
  19935. fabric.util.createAccessors(fabric.Text);
  19936. })(typeof exports !== 'undefined' ? exports : this);
  19937. (function() {
  19938. var clone = fabric.util.object.clone;
  19939. /**
  19940. * IText class (introduced in <b>v1.4</b>) Events are also fired with "text:"
  19941. * prefix when observing canvas.
  19942. * @class fabric.IText
  19943. * @extends fabric.Text
  19944. * @mixes fabric.Observable
  19945. *
  19946. * @fires changed
  19947. * @fires selection:changed
  19948. * @fires editing:entered
  19949. * @fires editing:exited
  19950. *
  19951. * @return {fabric.IText} thisArg
  19952. * @see {@link fabric.IText#initialize} for constructor definition
  19953. *
  19954. * <p>Supported key combinations:</p>
  19955. * <pre>
  19956. * Move cursor: left, right, up, down
  19957. * Select character: shift + left, shift + right
  19958. * Select text vertically: shift + up, shift + down
  19959. * Move cursor by word: alt + left, alt + right
  19960. * Select words: shift + alt + left, shift + alt + right
  19961. * Move cursor to line start/end: cmd + left, cmd + right or home, end
  19962. * Select till start/end of line: cmd + shift + left, cmd + shift + right or shift + home, shift + end
  19963. * Jump to start/end of text: cmd + up, cmd + down
  19964. * Select till start/end of text: cmd + shift + up, cmd + shift + down or shift + pgUp, shift + pgDown
  19965. * Delete character: backspace
  19966. * Delete word: alt + backspace
  19967. * Delete line: cmd + backspace
  19968. * Forward delete: delete
  19969. * Copy text: ctrl/cmd + c
  19970. * Paste text: ctrl/cmd + v
  19971. * Cut text: ctrl/cmd + x
  19972. * Select entire text: ctrl/cmd + a
  19973. * Quit editing tab or esc
  19974. * </pre>
  19975. *
  19976. * <p>Supported mouse/touch combination</p>
  19977. * <pre>
  19978. * Position cursor: click/touch
  19979. * Create selection: click/touch & drag
  19980. * Create selection: click & shift + click
  19981. * Select word: double click
  19982. * Select line: triple click
  19983. * </pre>
  19984. */
  19985. fabric.IText = fabric.util.createClass(fabric.Text, fabric.Observable, /** @lends fabric.IText.prototype */ {
  19986. /**
  19987. * Type of an object
  19988. * @type String
  19989. * @default
  19990. */
  19991. type: 'i-text',
  19992. /**
  19993. * Index where text selection starts (or where cursor is when there is no selection)
  19994. * @type Number
  19995. * @default
  19996. */
  19997. selectionStart: 0,
  19998. /**
  19999. * Index where text selection ends
  20000. * @type Number
  20001. * @default
  20002. */
  20003. selectionEnd: 0,
  20004. /**
  20005. * Color of text selection
  20006. * @type String
  20007. * @default
  20008. */
  20009. selectionColor: 'rgba(17,119,255,0.3)',
  20010. /**
  20011. * Indicates whether text is in editing mode
  20012. * @type Boolean
  20013. * @default
  20014. */
  20015. isEditing: false,
  20016. /**
  20017. * Indicates whether a text can be edited
  20018. * @type Boolean
  20019. * @default
  20020. */
  20021. editable: true,
  20022. /**
  20023. * Border color of text object while it's in editing mode
  20024. * @type String
  20025. * @default
  20026. */
  20027. editingBorderColor: 'rgba(102,153,255,0.25)',
  20028. /**
  20029. * Width of cursor (in px)
  20030. * @type Number
  20031. * @default
  20032. */
  20033. cursorWidth: 2,
  20034. /**
  20035. * Color of default cursor (when not overwritten by character style)
  20036. * @type String
  20037. * @default
  20038. */
  20039. cursorColor: '#333',
  20040. /**
  20041. * Delay between cursor blink (in ms)
  20042. * @type Number
  20043. * @default
  20044. */
  20045. cursorDelay: 1000,
  20046. /**
  20047. * Duration of cursor fadein (in ms)
  20048. * @type Number
  20049. * @default
  20050. */
  20051. cursorDuration: 600,
  20052. /**
  20053. * Object containing character styles
  20054. * (where top-level properties corresponds to line number and 2nd-level properties -- to char number in a line)
  20055. * @type Object
  20056. * @default
  20057. */
  20058. styles: null,
  20059. /**
  20060. * Indicates whether internal text char widths can be cached
  20061. * @type Boolean
  20062. * @default
  20063. */
  20064. caching: true,
  20065. /**
  20066. * @private
  20067. */
  20068. _reSpace: /\s|\n/,
  20069. /**
  20070. * @private
  20071. */
  20072. _currentCursorOpacity: 0,
  20073. /**
  20074. * @private
  20075. */
  20076. _selectionDirection: null,
  20077. /**
  20078. * @private
  20079. */
  20080. _abortCursorAnimation: false,
  20081. /**
  20082. * @private
  20083. */
  20084. __widthOfSpace: [],
  20085. /**
  20086. * Constructor
  20087. * @param {String} text Text string
  20088. * @param {Object} [options] Options object
  20089. * @return {fabric.IText} thisArg
  20090. */
  20091. initialize: function(text, options) {
  20092. this.styles = options ? (options.styles || { }) : { };
  20093. this.callSuper('initialize', text, options);
  20094. this.initBehavior();
  20095. },
  20096. /**
  20097. * @private
  20098. */
  20099. _clearCache: function() {
  20100. this.callSuper('_clearCache');
  20101. this.__widthOfSpace = [];
  20102. },
  20103. /**
  20104. * Returns true if object has no styling
  20105. */
  20106. isEmptyStyles: function() {
  20107. if (!this.styles) {
  20108. return true;
  20109. }
  20110. var obj = this.styles;
  20111. for (var p1 in obj) {
  20112. for (var p2 in obj[p1]) {
  20113. // eslint-disable-next-line no-unused-vars
  20114. for (var p3 in obj[p1][p2]) {
  20115. return false;
  20116. }
  20117. }
  20118. }
  20119. return true;
  20120. },
  20121. /**
  20122. * Sets selection start (left boundary of a selection)
  20123. * @param {Number} index Index to set selection start to
  20124. */
  20125. setSelectionStart: function(index) {
  20126. index = Math.max(index, 0);
  20127. this._updateAndFire('selectionStart', index);
  20128. },
  20129. /**
  20130. * Sets selection end (right boundary of a selection)
  20131. * @param {Number} index Index to set selection end to
  20132. */
  20133. setSelectionEnd: function(index) {
  20134. index = Math.min(index, this.text.length);
  20135. this._updateAndFire('selectionEnd', index);
  20136. },
  20137. /**
  20138. * @private
  20139. * @param {String} property 'selectionStart' or 'selectionEnd'
  20140. * @param {Number} index new position of property
  20141. */
  20142. _updateAndFire: function(property, index) {
  20143. if (this[property] !== index) {
  20144. this._fireSelectionChanged();
  20145. this[property] = index;
  20146. }
  20147. this._updateTextarea();
  20148. },
  20149. /**
  20150. * Fires the even of selection changed
  20151. * @private
  20152. */
  20153. _fireSelectionChanged: function() {
  20154. this.fire('selection:changed');
  20155. this.canvas && this.canvas.fire('text:selection:changed', { target: this });
  20156. },
  20157. /**
  20158. * Gets style of a current selection/cursor (at the start position)
  20159. * @param {Number} [startIndex] Start index to get styles at
  20160. * @param {Number} [endIndex] End index to get styles at
  20161. * @return {Object} styles Style object at a specified (or current) index
  20162. */
  20163. getSelectionStyles: function(startIndex, endIndex) {
  20164. if (arguments.length === 2) {
  20165. var styles = [];
  20166. for (var i = startIndex; i < endIndex; i++) {
  20167. styles.push(this.getSelectionStyles(i));
  20168. }
  20169. return styles;
  20170. }
  20171. var loc = this.get2DCursorLocation(startIndex),
  20172. style = this._getStyleDeclaration(loc.lineIndex, loc.charIndex);
  20173. return style || {};
  20174. },
  20175. /**
  20176. * Sets style of a current selection
  20177. * @param {Object} [styles] Styles object
  20178. * @return {fabric.IText} thisArg
  20179. * @chainable
  20180. */
  20181. setSelectionStyles: function(styles) {
  20182. if (this.selectionStart === this.selectionEnd) {
  20183. this._extendStyles(this.selectionStart, styles);
  20184. }
  20185. else {
  20186. for (var i = this.selectionStart; i < this.selectionEnd; i++) {
  20187. this._extendStyles(i, styles);
  20188. }
  20189. }
  20190. /* not included in _extendStyles to avoid clearing cache more than once */
  20191. this._forceClearCache = true;
  20192. return this;
  20193. },
  20194. /**
  20195. * @private
  20196. */
  20197. _extendStyles: function(index, styles) {
  20198. var loc = this.get2DCursorLocation(index);
  20199. if (!this._getLineStyle(loc.lineIndex)) {
  20200. this._setLineStyle(loc.lineIndex, {});
  20201. }
  20202. if (!this._getStyleDeclaration(loc.lineIndex, loc.charIndex)) {
  20203. this._setStyleDeclaration(loc.lineIndex, loc.charIndex, {});
  20204. }
  20205. fabric.util.object.extend(this._getStyleDeclaration(loc.lineIndex, loc.charIndex), styles);
  20206. },
  20207. /**
  20208. * Initialize text dimensions. Render all text on given context
  20209. * or on a offscreen canvas to get the text width with measureText.
  20210. * Updates this.width and this.height with the proper values.
  20211. * Does not return dimensions.
  20212. * @param {CanvasRenderingContext2D} [ctx] Context to render on
  20213. * @private
  20214. */
  20215. _initDimensions: function(ctx) {
  20216. if (!ctx) {
  20217. this.clearContextTop();
  20218. }
  20219. this.callSuper('_initDimensions', ctx);
  20220. },
  20221. /**
  20222. * @private
  20223. * @param {CanvasRenderingContext2D} ctx Context to render on
  20224. * @param {Boolean} noTransform
  20225. */
  20226. render: function(ctx, noTransform) {
  20227. this.clearContextTop();
  20228. this.callSuper('render', ctx, noTransform);
  20229. // clear the cursorOffsetCache, so we ensure to calculate once per renderCursor
  20230. // the correct position but not at every cursor animation.
  20231. this.cursorOffsetCache = { };
  20232. this.renderCursorOrSelection();
  20233. },
  20234. /**
  20235. * @private
  20236. * @param {CanvasRenderingContext2D} ctx Context to render on
  20237. */
  20238. _render: function(ctx) {
  20239. this.callSuper('_render', ctx);
  20240. this.ctx = ctx;
  20241. },
  20242. /**
  20243. * Prepare and clean the contextTop
  20244. */
  20245. clearContextTop: function() {
  20246. if (!this.active || !this.isEditing) {
  20247. return;
  20248. }
  20249. if (this.canvas && this.canvas.contextTop) {
  20250. var ctx = this.canvas.contextTop;
  20251. ctx.save();
  20252. ctx.transform.apply(ctx, this.canvas.viewportTransform);
  20253. this.transform(ctx);
  20254. this.transformMatrix && ctx.transform.apply(ctx, this.transformMatrix);
  20255. this._clearTextArea(ctx);
  20256. ctx.restore();
  20257. }
  20258. },
  20259. /**
  20260. * Renders cursor or selection (depending on what exists)
  20261. */
  20262. renderCursorOrSelection: function() {
  20263. if (!this.active || !this.isEditing) {
  20264. return;
  20265. }
  20266. var chars = this.text.split(''),
  20267. boundaries, ctx;
  20268. if (this.canvas && this.canvas.contextTop) {
  20269. ctx = this.canvas.contextTop;
  20270. ctx.save();
  20271. ctx.transform.apply(ctx, this.canvas.viewportTransform);
  20272. this.transform(ctx);
  20273. this.transformMatrix && ctx.transform.apply(ctx, this.transformMatrix);
  20274. this._clearTextArea(ctx);
  20275. }
  20276. else {
  20277. ctx = this.ctx;
  20278. ctx.save();
  20279. }
  20280. if (this.selectionStart === this.selectionEnd) {
  20281. boundaries = this._getCursorBoundaries(chars, 'cursor');
  20282. this.renderCursor(boundaries, ctx);
  20283. }
  20284. else {
  20285. boundaries = this._getCursorBoundaries(chars, 'selection');
  20286. this.renderSelection(chars, boundaries, ctx);
  20287. }
  20288. ctx.restore();
  20289. },
  20290. _clearTextArea: function(ctx) {
  20291. // we add 4 pixel, to be sure to do not leave any pixel out
  20292. var width = this.width + 4, height = this.height + 4;
  20293. ctx.clearRect(-width / 2, -height / 2, width, height);
  20294. },
  20295. /**
  20296. * Returns 2d representation (lineIndex and charIndex) of cursor (or selection start)
  20297. * @param {Number} [selectionStart] Optional index. When not given, current selectionStart is used.
  20298. */
  20299. get2DCursorLocation: function(selectionStart) {
  20300. if (typeof selectionStart === 'undefined') {
  20301. selectionStart = this.selectionStart;
  20302. }
  20303. var len = this._textLines.length;
  20304. for (var i = 0; i < len; i++) {
  20305. if (selectionStart <= this._textLines[i].length) {
  20306. return {
  20307. lineIndex: i,
  20308. charIndex: selectionStart
  20309. };
  20310. }
  20311. selectionStart -= this._textLines[i].length + 1;
  20312. }
  20313. return {
  20314. lineIndex: i - 1,
  20315. charIndex: this._textLines[i - 1].length < selectionStart ? this._textLines[i - 1].length : selectionStart
  20316. };
  20317. },
  20318. /**
  20319. * Returns complete style of char at the current cursor
  20320. * @param {Number} lineIndex Line index
  20321. * @param {Number} charIndex Char index
  20322. * @return {Object} Character style
  20323. */
  20324. getCurrentCharStyle: function(lineIndex, charIndex) {
  20325. var style = this._getStyleDeclaration(lineIndex, charIndex === 0 ? 0 : charIndex - 1);
  20326. return {
  20327. fontSize: style && style.fontSize || this.fontSize,
  20328. fill: style && style.fill || this.fill,
  20329. textBackgroundColor: style && style.textBackgroundColor || this.textBackgroundColor,
  20330. textDecoration: style && style.textDecoration || this.textDecoration,
  20331. fontFamily: style && style.fontFamily || this.fontFamily,
  20332. fontWeight: style && style.fontWeight || this.fontWeight,
  20333. fontStyle: style && style.fontStyle || this.fontStyle,
  20334. stroke: style && style.stroke || this.stroke,
  20335. strokeWidth: style && style.strokeWidth || this.strokeWidth
  20336. };
  20337. },
  20338. /**
  20339. * Returns fontSize of char at the current cursor
  20340. * @param {Number} lineIndex Line index
  20341. * @param {Number} charIndex Char index
  20342. * @return {Number} Character font size
  20343. */
  20344. getCurrentCharFontSize: function(lineIndex, charIndex) {
  20345. var style = this._getStyleDeclaration(lineIndex, charIndex === 0 ? 0 : charIndex - 1);
  20346. return style && style.fontSize ? style.fontSize : this.fontSize;
  20347. },
  20348. /**
  20349. * Returns color (fill) of char at the current cursor
  20350. * @param {Number} lineIndex Line index
  20351. * @param {Number} charIndex Char index
  20352. * @return {String} Character color (fill)
  20353. */
  20354. getCurrentCharColor: function(lineIndex, charIndex) {
  20355. var style = this._getStyleDeclaration(lineIndex, charIndex === 0 ? 0 : charIndex - 1);
  20356. return style && style.fill ? style.fill : this.cursorColor;
  20357. },
  20358. /**
  20359. * Returns cursor boundaries (left, top, leftOffset, topOffset)
  20360. * @private
  20361. * @param {Array} chars Array of characters
  20362. * @param {String} typeOfBoundaries
  20363. */
  20364. _getCursorBoundaries: function(chars, typeOfBoundaries) {
  20365. // left/top are left/top of entire text box
  20366. // leftOffset/topOffset are offset from that left/top point of a text box
  20367. var left = Math.round(this._getLeftOffset()),
  20368. top = this._getTopOffset(),
  20369. offsets = this._getCursorBoundariesOffsets(
  20370. chars, typeOfBoundaries);
  20371. return {
  20372. left: left,
  20373. top: top,
  20374. leftOffset: offsets.left + offsets.lineLeft,
  20375. topOffset: offsets.top
  20376. };
  20377. },
  20378. /**
  20379. * @private
  20380. */
  20381. _getCursorBoundariesOffsets: function(chars, typeOfBoundaries) {
  20382. if (this.cursorOffsetCache && 'top' in this.cursorOffsetCache) {
  20383. return this.cursorOffsetCache;
  20384. }
  20385. var lineLeftOffset = 0,
  20386. lineIndex = 0,
  20387. charIndex = 0,
  20388. topOffset = 0,
  20389. leftOffset = 0,
  20390. boundaries;
  20391. for (var i = 0; i < this.selectionStart; i++) {
  20392. if (chars[i] === '\n') {
  20393. leftOffset = 0;
  20394. topOffset += this._getHeightOfLine(this.ctx, lineIndex);
  20395. lineIndex++;
  20396. charIndex = 0;
  20397. }
  20398. else {
  20399. leftOffset += this._getWidthOfChar(this.ctx, chars[i], lineIndex, charIndex);
  20400. charIndex++;
  20401. }
  20402. lineLeftOffset = this._getLineLeftOffset(this._getLineWidth(this.ctx, lineIndex));
  20403. }
  20404. if (typeOfBoundaries === 'cursor') {
  20405. topOffset += (1 - this._fontSizeFraction) * this._getHeightOfLine(this.ctx, lineIndex) / this.lineHeight
  20406. - this.getCurrentCharFontSize(lineIndex, charIndex) * (1 - this._fontSizeFraction);
  20407. }
  20408. if (this.charSpacing !== 0 && charIndex === this._textLines[lineIndex].length) {
  20409. leftOffset -= this._getWidthOfCharSpacing();
  20410. }
  20411. boundaries = {
  20412. top: topOffset,
  20413. left: leftOffset > 0 ? leftOffset : 0,
  20414. lineLeft: lineLeftOffset
  20415. };
  20416. this.cursorOffsetCache = boundaries;
  20417. return this.cursorOffsetCache;
  20418. },
  20419. /**
  20420. * Renders cursor
  20421. * @param {Object} boundaries
  20422. * @param {CanvasRenderingContext2D} ctx transformed context to draw on
  20423. */
  20424. renderCursor: function(boundaries, ctx) {
  20425. var cursorLocation = this.get2DCursorLocation(),
  20426. lineIndex = cursorLocation.lineIndex,
  20427. charIndex = cursorLocation.charIndex,
  20428. charHeight = this.getCurrentCharFontSize(lineIndex, charIndex),
  20429. leftOffset = boundaries.leftOffset,
  20430. multiplier = this.scaleX * this.canvas.getZoom(),
  20431. cursorWidth = this.cursorWidth / multiplier;
  20432. ctx.fillStyle = this.getCurrentCharColor(lineIndex, charIndex);
  20433. ctx.globalAlpha = this.__isMousedown ? 1 : this._currentCursorOpacity;
  20434. ctx.fillRect(
  20435. boundaries.left + leftOffset - cursorWidth / 2,
  20436. boundaries.top + boundaries.topOffset,
  20437. cursorWidth,
  20438. charHeight);
  20439. },
  20440. /**
  20441. * Renders text selection
  20442. * @param {Array} chars Array of characters
  20443. * @param {Object} boundaries Object with left/top/leftOffset/topOffset
  20444. * @param {CanvasRenderingContext2D} ctx transformed context to draw on
  20445. */
  20446. renderSelection: function(chars, boundaries, ctx) {
  20447. ctx.fillStyle = this.selectionColor;
  20448. var start = this.get2DCursorLocation(this.selectionStart),
  20449. end = this.get2DCursorLocation(this.selectionEnd),
  20450. startLine = start.lineIndex,
  20451. endLine = end.lineIndex;
  20452. for (var i = startLine; i <= endLine; i++) {
  20453. var lineOffset = this._getLineLeftOffset(this._getLineWidth(ctx, i)) || 0,
  20454. lineHeight = this._getHeightOfLine(this.ctx, i),
  20455. realLineHeight = 0, boxWidth = 0, line = this._textLines[i];
  20456. if (i === startLine) {
  20457. for (var j = 0, len = line.length; j < len; j++) {
  20458. if (j >= start.charIndex && (i !== endLine || j < end.charIndex)) {
  20459. boxWidth += this._getWidthOfChar(ctx, line[j], i, j);
  20460. }
  20461. if (j < start.charIndex) {
  20462. lineOffset += this._getWidthOfChar(ctx, line[j], i, j);
  20463. }
  20464. }
  20465. if (j === line.length) {
  20466. boxWidth -= this._getWidthOfCharSpacing();
  20467. }
  20468. }
  20469. else if (i > startLine && i < endLine) {
  20470. boxWidth += this._getLineWidth(ctx, i) || 5;
  20471. }
  20472. else if (i === endLine) {
  20473. for (var j2 = 0, j2len = end.charIndex; j2 < j2len; j2++) {
  20474. boxWidth += this._getWidthOfChar(ctx, line[j2], i, j2);
  20475. }
  20476. if (end.charIndex === line.length) {
  20477. boxWidth -= this._getWidthOfCharSpacing();
  20478. }
  20479. }
  20480. realLineHeight = lineHeight;
  20481. if (this.lineHeight < 1 || (i === endLine && this.lineHeight > 1)) {
  20482. lineHeight /= this.lineHeight;
  20483. }
  20484. ctx.fillRect(
  20485. boundaries.left + lineOffset,
  20486. boundaries.top + boundaries.topOffset,
  20487. boxWidth > 0 ? boxWidth : 0,
  20488. lineHeight);
  20489. boundaries.topOffset += realLineHeight;
  20490. }
  20491. },
  20492. /**
  20493. * @private
  20494. * @param {String} method
  20495. * @param {CanvasRenderingContext2D} ctx Context to render on
  20496. * @param {String} line Content of the line
  20497. * @param {Number} left
  20498. * @param {Number} top
  20499. * @param {Number} lineIndex
  20500. * @param {Number} charOffset
  20501. */
  20502. _renderChars: function(method, ctx, line, left, top, lineIndex, charOffset) {
  20503. if (this.isEmptyStyles()) {
  20504. return this._renderCharsFast(method, ctx, line, left, top);
  20505. }
  20506. charOffset = charOffset || 0;
  20507. // set proper line offset
  20508. var lineHeight = this._getHeightOfLine(ctx, lineIndex),
  20509. prevStyle,
  20510. thisStyle,
  20511. charsToRender = '';
  20512. ctx.save();
  20513. top -= lineHeight / this.lineHeight * this._fontSizeFraction;
  20514. for (var i = charOffset, len = line.length + charOffset; i <= len; i++) {
  20515. prevStyle = prevStyle || this.getCurrentCharStyle(lineIndex, i);
  20516. thisStyle = this.getCurrentCharStyle(lineIndex, i + 1);
  20517. if (this._hasStyleChanged(prevStyle, thisStyle) || i === len) {
  20518. this._renderChar(method, ctx, lineIndex, i - 1, charsToRender, left, top, lineHeight);
  20519. charsToRender = '';
  20520. prevStyle = thisStyle;
  20521. }
  20522. charsToRender += line[i - charOffset];
  20523. }
  20524. ctx.restore();
  20525. },
  20526. /**
  20527. * @private
  20528. * @param {String} method
  20529. * @param {CanvasRenderingContext2D} ctx Context to render on
  20530. * @param {String} line Content of the line
  20531. * @param {Number} left Left coordinate
  20532. * @param {Number} top Top coordinate
  20533. */
  20534. _renderCharsFast: function(method, ctx, line, left, top) {
  20535. if (method === 'fillText' && this.fill) {
  20536. this.callSuper('_renderChars', method, ctx, line, left, top);
  20537. }
  20538. if (method === 'strokeText' && ((this.stroke && this.strokeWidth > 0) || this.skipFillStrokeCheck)) {
  20539. this.callSuper('_renderChars', method, ctx, line, left, top);
  20540. }
  20541. },
  20542. /**
  20543. * @private
  20544. * @param {String} method
  20545. * @param {CanvasRenderingContext2D} ctx Context to render on
  20546. * @param {Number} lineIndex
  20547. * @param {Number} i
  20548. * @param {String} _char
  20549. * @param {Number} left Left coordinate
  20550. * @param {Number} top Top coordinate
  20551. * @param {Number} lineHeight Height of the line
  20552. */
  20553. _renderChar: function(method, ctx, lineIndex, i, _char, left, top, lineHeight) {
  20554. var charWidth, charHeight, shouldFill, shouldStroke,
  20555. decl = this._getStyleDeclaration(lineIndex, i),
  20556. offset, textDecoration, chars, additionalSpace, _charWidth;
  20557. if (decl) {
  20558. charHeight = this._getHeightOfChar(ctx, _char, lineIndex, i);
  20559. shouldStroke = decl.stroke;
  20560. shouldFill = decl.fill;
  20561. textDecoration = decl.textDecoration;
  20562. }
  20563. else {
  20564. charHeight = this.fontSize;
  20565. }
  20566. shouldStroke = (shouldStroke || this.stroke) && method === 'strokeText';
  20567. shouldFill = (shouldFill || this.fill) && method === 'fillText';
  20568. decl && ctx.save();
  20569. charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i, decl || null);
  20570. textDecoration = textDecoration || this.textDecoration;
  20571. if (decl && decl.textBackgroundColor) {
  20572. this._removeShadow(ctx);
  20573. }
  20574. if (this.charSpacing !== 0) {
  20575. additionalSpace = this._getWidthOfCharSpacing();
  20576. chars = _char.split('');
  20577. charWidth = 0;
  20578. for (var j = 0, len = chars.length, char; j < len; j++) {
  20579. char = chars[j];
  20580. shouldFill && ctx.fillText(char, left + charWidth, top);
  20581. shouldStroke && ctx.strokeText(char, left + charWidth, top);
  20582. _charWidth = ctx.measureText(char).width + additionalSpace;
  20583. charWidth += _charWidth > 0 ? _charWidth : 0;
  20584. }
  20585. }
  20586. else {
  20587. shouldFill && ctx.fillText(_char, left, top);
  20588. shouldStroke && ctx.strokeText(_char, left, top);
  20589. }
  20590. if (textDecoration || textDecoration !== '') {
  20591. offset = this._fontSizeFraction * lineHeight / this.lineHeight;
  20592. this._renderCharDecoration(ctx, textDecoration, left, top, offset, charWidth, charHeight);
  20593. }
  20594. decl && ctx.restore();
  20595. ctx.translate(charWidth, 0);
  20596. },
  20597. /**
  20598. * @private
  20599. * @param {Object} prevStyle
  20600. * @param {Object} thisStyle
  20601. */
  20602. _hasStyleChanged: function(prevStyle, thisStyle) {
  20603. return (prevStyle.fill !== thisStyle.fill ||
  20604. prevStyle.fontSize !== thisStyle.fontSize ||
  20605. prevStyle.textBackgroundColor !== thisStyle.textBackgroundColor ||
  20606. prevStyle.textDecoration !== thisStyle.textDecoration ||
  20607. prevStyle.fontFamily !== thisStyle.fontFamily ||
  20608. prevStyle.fontWeight !== thisStyle.fontWeight ||
  20609. prevStyle.fontStyle !== thisStyle.fontStyle ||
  20610. prevStyle.stroke !== thisStyle.stroke ||
  20611. prevStyle.strokeWidth !== thisStyle.strokeWidth
  20612. );
  20613. },
  20614. /**
  20615. * @private
  20616. * @param {CanvasRenderingContext2D} ctx Context to render on
  20617. */
  20618. _renderCharDecoration: function(ctx, textDecoration, left, top, offset, charWidth, charHeight) {
  20619. if (!textDecoration) {
  20620. return;
  20621. }
  20622. var decorationWeight = charHeight / 15,
  20623. positions = {
  20624. underline: top + charHeight / 10,
  20625. 'line-through': top - charHeight * (this._fontSizeFraction + this._fontSizeMult - 1) + decorationWeight,
  20626. overline: top - (this._fontSizeMult - this._fontSizeFraction) * charHeight
  20627. },
  20628. decorations = ['underline', 'line-through', 'overline'], i, decoration;
  20629. for (i = 0; i < decorations.length; i++) {
  20630. decoration = decorations[i];
  20631. if (textDecoration.indexOf(decoration) > -1) {
  20632. ctx.fillRect(left, positions[decoration], charWidth , decorationWeight);
  20633. }
  20634. }
  20635. },
  20636. /**
  20637. * @private
  20638. * @param {String} method
  20639. * @param {CanvasRenderingContext2D} ctx Context to render on
  20640. * @param {String} line
  20641. * @param {Number} left
  20642. * @param {Number} top
  20643. * @param {Number} lineIndex
  20644. */
  20645. _renderTextLine: function(method, ctx, line, left, top, lineIndex) {
  20646. // to "cancel" this.fontSize subtraction in fabric.Text#_renderTextLine
  20647. // the adding 0.03 is just to align text with itext by overlap test
  20648. if (!this.isEmptyStyles()) {
  20649. top += this.fontSize * (this._fontSizeFraction + 0.03);
  20650. }
  20651. this.callSuper('_renderTextLine', method, ctx, line, left, top, lineIndex);
  20652. },
  20653. /**
  20654. * @private
  20655. * @param {CanvasRenderingContext2D} ctx Context to render on
  20656. */
  20657. _renderTextDecoration: function(ctx) {
  20658. if (this.isEmptyStyles()) {
  20659. return this.callSuper('_renderTextDecoration', ctx);
  20660. }
  20661. },
  20662. /**
  20663. * @private
  20664. * @param {CanvasRenderingContext2D} ctx Context to render on
  20665. */
  20666. _renderTextLinesBackground: function(ctx) {
  20667. this.callSuper('_renderTextLinesBackground', ctx);
  20668. var lineTopOffset = 0, heightOfLine,
  20669. lineWidth, lineLeftOffset,
  20670. leftOffset = this._getLeftOffset(),
  20671. topOffset = this._getTopOffset(),
  20672. colorCache = '',
  20673. line, _char, style, leftCache,
  20674. topCache, widthCache, heightCache;
  20675. ctx.save();
  20676. for (var i = 0, len = this._textLines.length; i < len; i++) {
  20677. heightOfLine = this._getHeightOfLine(ctx, i);
  20678. line = this._textLines[i];
  20679. if (line === '' || !this.styles || !this._getLineStyle(i)) {
  20680. lineTopOffset += heightOfLine;
  20681. continue;
  20682. }
  20683. lineWidth = this._getLineWidth(ctx, i);
  20684. lineLeftOffset = this._getLineLeftOffset(lineWidth);
  20685. leftCache = topCache = widthCache = heightCache = 0;
  20686. for (var j = 0, jlen = line.length; j < jlen; j++) {
  20687. style = this._getStyleDeclaration(i, j) || {};
  20688. if (colorCache !== style.textBackgroundColor) {
  20689. if (heightCache && widthCache) {
  20690. ctx.fillStyle = colorCache;
  20691. ctx.fillRect(leftCache, topCache, widthCache, heightCache);
  20692. }
  20693. leftCache = topCache = widthCache = heightCache = 0;
  20694. colorCache = style.textBackgroundColor || '';
  20695. }
  20696. if (!style.textBackgroundColor) {
  20697. colorCache = '';
  20698. continue;
  20699. }
  20700. _char = line[j];
  20701. if (colorCache === style.textBackgroundColor) {
  20702. colorCache = style.textBackgroundColor;
  20703. if (!leftCache) {
  20704. leftCache = leftOffset + lineLeftOffset + this._getWidthOfCharsAt(ctx, i, j);
  20705. }
  20706. topCache = topOffset + lineTopOffset;
  20707. widthCache += this._getWidthOfChar(ctx, _char, i, j);
  20708. heightCache = heightOfLine / this.lineHeight;
  20709. }
  20710. }
  20711. // if a textBackgroundColor ends on the last character of a line
  20712. if (heightCache && widthCache) {
  20713. ctx.fillStyle = colorCache;
  20714. ctx.fillRect(leftCache, topCache, widthCache, heightCache);
  20715. leftCache = topCache = widthCache = heightCache = 0;
  20716. }
  20717. lineTopOffset += heightOfLine;
  20718. }
  20719. ctx.restore();
  20720. },
  20721. /**
  20722. * @private
  20723. */
  20724. _getCacheProp: function(_char, styleDeclaration) {
  20725. return _char +
  20726. styleDeclaration.fontSize +
  20727. styleDeclaration.fontWeight +
  20728. styleDeclaration.fontStyle;
  20729. },
  20730. /**
  20731. * @private
  20732. * @param {String} fontFamily name
  20733. * @return {Object} reference to cache
  20734. */
  20735. _getFontCache: function(fontFamily) {
  20736. if (!fabric.charWidthsCache[fontFamily]) {
  20737. fabric.charWidthsCache[fontFamily] = { };
  20738. }
  20739. return fabric.charWidthsCache[fontFamily];
  20740. },
  20741. /**
  20742. * @private
  20743. * @param {CanvasRenderingContext2D} ctx Context to render on
  20744. * @param {String} _char
  20745. * @param {Number} lineIndex
  20746. * @param {Number} charIndex
  20747. * @param {Object} [decl]
  20748. */
  20749. _applyCharStylesGetWidth: function(ctx, _char, lineIndex, charIndex, decl) {
  20750. var charDecl = decl || this._getStyleDeclaration(lineIndex, charIndex),
  20751. styleDeclaration = clone(charDecl),
  20752. width, cacheProp, charWidthsCache;
  20753. this._applyFontStyles(styleDeclaration);
  20754. charWidthsCache = this._getFontCache(styleDeclaration.fontFamily);
  20755. cacheProp = this._getCacheProp(_char, styleDeclaration);
  20756. // short-circuit if no styles for this char
  20757. // global style from object is always applyed and handled by save and restore
  20758. if (!charDecl && charWidthsCache[cacheProp] && this.caching) {
  20759. return charWidthsCache[cacheProp];
  20760. }
  20761. if (typeof styleDeclaration.shadow === 'string') {
  20762. styleDeclaration.shadow = new fabric.Shadow(styleDeclaration.shadow);
  20763. }
  20764. var fill = styleDeclaration.fill || this.fill;
  20765. ctx.fillStyle = fill.toLive
  20766. ? fill.toLive(ctx, this)
  20767. : fill;
  20768. if (styleDeclaration.stroke) {
  20769. ctx.strokeStyle = (styleDeclaration.stroke && styleDeclaration.stroke.toLive)
  20770. ? styleDeclaration.stroke.toLive(ctx, this)
  20771. : styleDeclaration.stroke;
  20772. }
  20773. ctx.lineWidth = styleDeclaration.strokeWidth || this.strokeWidth;
  20774. ctx.font = this._getFontDeclaration.call(styleDeclaration);
  20775. //if we want this._setShadow.call to work with styleDeclarion
  20776. //we have to add those references
  20777. if (styleDeclaration.shadow) {
  20778. styleDeclaration.scaleX = this.scaleX;
  20779. styleDeclaration.scaleY = this.scaleY;
  20780. styleDeclaration.canvas = this.canvas;
  20781. styleDeclaration.getObjectScaling = this.getObjectScaling;
  20782. this._setShadow.call(styleDeclaration, ctx);
  20783. }
  20784. if (!this.caching || !charWidthsCache[cacheProp]) {
  20785. width = ctx.measureText(_char).width;
  20786. this.caching && (charWidthsCache[cacheProp] = width);
  20787. return width;
  20788. }
  20789. return charWidthsCache[cacheProp];
  20790. },
  20791. /**
  20792. * @private
  20793. * @param {Object} styleDeclaration
  20794. */
  20795. _applyFontStyles: function(styleDeclaration) {
  20796. if (!styleDeclaration.fontFamily) {
  20797. styleDeclaration.fontFamily = this.fontFamily;
  20798. }
  20799. if (!styleDeclaration.fontSize) {
  20800. styleDeclaration.fontSize = this.fontSize;
  20801. }
  20802. if (!styleDeclaration.fontWeight) {
  20803. styleDeclaration.fontWeight = this.fontWeight;
  20804. }
  20805. if (!styleDeclaration.fontStyle) {
  20806. styleDeclaration.fontStyle = this.fontStyle;
  20807. }
  20808. },
  20809. /**
  20810. * @param {Number} lineIndex
  20811. * @param {Number} charIndex
  20812. * @param {Boolean} [returnCloneOrEmpty=false]
  20813. * @private
  20814. */
  20815. _getStyleDeclaration: function(lineIndex, charIndex, returnCloneOrEmpty) {
  20816. if (returnCloneOrEmpty) {
  20817. return (this.styles[lineIndex] && this.styles[lineIndex][charIndex])
  20818. ? clone(this.styles[lineIndex][charIndex])
  20819. : { };
  20820. }
  20821. return this.styles[lineIndex] && this.styles[lineIndex][charIndex] ? this.styles[lineIndex][charIndex] : null;
  20822. },
  20823. /**
  20824. * @param {Number} lineIndex
  20825. * @param {Number} charIndex
  20826. * @param {Object} style
  20827. * @private
  20828. */
  20829. _setStyleDeclaration: function(lineIndex, charIndex, style) {
  20830. this.styles[lineIndex][charIndex] = style;
  20831. },
  20832. /**
  20833. *
  20834. * @param {Number} lineIndex
  20835. * @param {Number} charIndex
  20836. * @private
  20837. */
  20838. _deleteStyleDeclaration: function(lineIndex, charIndex) {
  20839. delete this.styles[lineIndex][charIndex];
  20840. },
  20841. /**
  20842. * @param {Number} lineIndex
  20843. * @private
  20844. */
  20845. _getLineStyle: function(lineIndex) {
  20846. return this.styles[lineIndex];
  20847. },
  20848. /**
  20849. * @param {Number} lineIndex
  20850. * @param {Object} style
  20851. * @private
  20852. */
  20853. _setLineStyle: function(lineIndex, style) {
  20854. this.styles[lineIndex] = style;
  20855. },
  20856. /**
  20857. * @param {Number} lineIndex
  20858. * @private
  20859. */
  20860. _deleteLineStyle: function(lineIndex) {
  20861. delete this.styles[lineIndex];
  20862. },
  20863. /**
  20864. * @private
  20865. * @param {CanvasRenderingContext2D} ctx Context to render on
  20866. */
  20867. _getWidthOfChar: function(ctx, _char, lineIndex, charIndex) {
  20868. if (!this._isMeasuring && this.textAlign === 'justify' && this._reSpacesAndTabs.test(_char)) {
  20869. return this._getWidthOfSpace(ctx, lineIndex);
  20870. }
  20871. ctx.save();
  20872. var width = this._applyCharStylesGetWidth(ctx, _char, lineIndex, charIndex);
  20873. if (this.charSpacing !== 0) {
  20874. width += this._getWidthOfCharSpacing();
  20875. }
  20876. ctx.restore();
  20877. return width > 0 ? width : 0;
  20878. },
  20879. /**
  20880. * @private
  20881. * @param {CanvasRenderingContext2D} ctx Context to render on
  20882. * @param {Number} lineIndex
  20883. * @param {Number} charIndex
  20884. */
  20885. _getHeightOfChar: function(ctx, lineIndex, charIndex) {
  20886. var style = this._getStyleDeclaration(lineIndex, charIndex);
  20887. return style && style.fontSize ? style.fontSize : this.fontSize;
  20888. },
  20889. /**
  20890. * @private
  20891. * @param {CanvasRenderingContext2D} ctx Context to render on
  20892. * @param {Number} lineIndex
  20893. * @param {Number} charIndex
  20894. */
  20895. _getWidthOfCharsAt: function(ctx, lineIndex, charIndex) {
  20896. var width = 0, i, _char;
  20897. for (i = 0; i < charIndex; i++) {
  20898. _char = this._textLines[lineIndex][i];
  20899. width += this._getWidthOfChar(ctx, _char, lineIndex, i);
  20900. }
  20901. return width;
  20902. },
  20903. /**
  20904. * @private
  20905. * @param {CanvasRenderingContext2D} ctx Context to render on
  20906. * @param {Number} lineIndex line number
  20907. * @return {Number} Line width
  20908. */
  20909. _measureLine: function(ctx, lineIndex) {
  20910. this._isMeasuring = true;
  20911. var width = this._getWidthOfCharsAt(ctx, lineIndex, this._textLines[lineIndex].length);
  20912. if (this.charSpacing !== 0) {
  20913. width -= this._getWidthOfCharSpacing();
  20914. }
  20915. this._isMeasuring = false;
  20916. return width > 0 ? width : 0;
  20917. },
  20918. /**
  20919. * @private
  20920. * @param {CanvasRenderingContext2D} ctx Context to render on
  20921. * @param {Number} lineIndex
  20922. */
  20923. _getWidthOfSpace: function (ctx, lineIndex) {
  20924. if (this.__widthOfSpace[lineIndex]) {
  20925. return this.__widthOfSpace[lineIndex];
  20926. }
  20927. var line = this._textLines[lineIndex],
  20928. wordsWidth = this._getWidthOfWords(ctx, line, lineIndex, 0),
  20929. widthDiff = this.width - wordsWidth,
  20930. numSpaces = line.length - line.replace(this._reSpacesAndTabs, '').length,
  20931. width = Math.max(widthDiff / numSpaces, ctx.measureText(' ').width);
  20932. this.__widthOfSpace[lineIndex] = width;
  20933. return width;
  20934. },
  20935. /**
  20936. * @private
  20937. * @param {CanvasRenderingContext2D} ctx Context to render on
  20938. * @param {String} line
  20939. * @param {Number} lineIndex
  20940. * @param {Number} charOffset
  20941. */
  20942. _getWidthOfWords: function (ctx, line, lineIndex, charOffset) {
  20943. var width = 0;
  20944. for (var charIndex = 0; charIndex < line.length; charIndex++) {
  20945. var _char = line[charIndex];
  20946. if (!_char.match(/\s/)) {
  20947. width += this._getWidthOfChar(ctx, _char, lineIndex, charIndex + charOffset);
  20948. }
  20949. }
  20950. return width;
  20951. },
  20952. /**
  20953. * @private
  20954. * @param {CanvasRenderingContext2D} ctx Context to render on
  20955. */
  20956. _getHeightOfLine: function(ctx, lineIndex) {
  20957. if (this.__lineHeights[lineIndex]) {
  20958. return this.__lineHeights[lineIndex];
  20959. }
  20960. var line = this._textLines[lineIndex],
  20961. maxHeight = this._getHeightOfChar(ctx, lineIndex, 0);
  20962. for (var i = 1, len = line.length; i < len; i++) {
  20963. var currentCharHeight = this._getHeightOfChar(ctx, lineIndex, i);
  20964. if (currentCharHeight > maxHeight) {
  20965. maxHeight = currentCharHeight;
  20966. }
  20967. }
  20968. this.__lineHeights[lineIndex] = maxHeight * this.lineHeight * this._fontSizeMult;
  20969. return this.__lineHeights[lineIndex];
  20970. },
  20971. /**
  20972. * @private
  20973. * @param {CanvasRenderingContext2D} ctx Context to render on
  20974. */
  20975. _getTextHeight: function(ctx) {
  20976. var lineHeight, height = 0;
  20977. for (var i = 0, len = this._textLines.length; i < len; i++) {
  20978. lineHeight = this._getHeightOfLine(ctx, i);
  20979. height += (i === len - 1 ? lineHeight / this.lineHeight : lineHeight);
  20980. }
  20981. return height;
  20982. },
  20983. /**
  20984. * Returns object representation of an instance
  20985. * @method toObject
  20986. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  20987. * @return {Object} object representation of an instance
  20988. */
  20989. toObject: function(propertiesToInclude) {
  20990. return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), {
  20991. styles: clone(this.styles, true)
  20992. });
  20993. }
  20994. });
  20995. /**
  20996. * Returns fabric.IText instance from an object representation
  20997. * @static
  20998. * @memberOf fabric.IText
  20999. * @param {Object} object Object to create an instance from
  21000. * @param {function} [callback] invoked with new instance as argument
  21001. * @param {Boolean} [forceAsync] Force an async behaviour trying to create pattern first
  21002. * @return {fabric.IText} instance of fabric.IText
  21003. */
  21004. fabric.IText.fromObject = function(object, callback, forceAsync) {
  21005. return fabric.Object._fromObject('IText', object, callback, forceAsync, 'text');
  21006. };
  21007. })();
  21008. (function() {
  21009. var clone = fabric.util.object.clone;
  21010. fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ {
  21011. /**
  21012. * Initializes all the interactive behavior of IText
  21013. */
  21014. initBehavior: function() {
  21015. this.initAddedHandler();
  21016. this.initRemovedHandler();
  21017. this.initCursorSelectionHandlers();
  21018. this.initDoubleClickSimulation();
  21019. this.mouseMoveHandler = this.mouseMoveHandler.bind(this);
  21020. },
  21021. onDeselect: function() {
  21022. this.isEditing && this.exitEditing();
  21023. this.selected = false;
  21024. },
  21025. /**
  21026. * Initializes "added" event handler
  21027. */
  21028. initAddedHandler: function() {
  21029. var _this = this;
  21030. this.on('added', function() {
  21031. var canvas = _this.canvas;
  21032. if (canvas) {
  21033. if (!canvas._hasITextHandlers) {
  21034. canvas._hasITextHandlers = true;
  21035. _this._initCanvasHandlers(canvas);
  21036. }
  21037. canvas._iTextInstances = canvas._iTextInstances || [];
  21038. canvas._iTextInstances.push(_this);
  21039. }
  21040. });
  21041. },
  21042. initRemovedHandler: function() {
  21043. var _this = this;
  21044. this.on('removed', function() {
  21045. var canvas = _this.canvas;
  21046. if (canvas) {
  21047. canvas._iTextInstances = canvas._iTextInstances || [];
  21048. fabric.util.removeFromArray(canvas._iTextInstances, _this);
  21049. if (canvas._iTextInstances.length === 0) {
  21050. canvas._hasITextHandlers = false;
  21051. _this._removeCanvasHandlers(canvas);
  21052. }
  21053. }
  21054. });
  21055. },
  21056. /**
  21057. * register canvas event to manage exiting on other instances
  21058. * @private
  21059. */
  21060. _initCanvasHandlers: function(canvas) {
  21061. canvas._mouseUpITextHandler = (function() {
  21062. if (canvas._iTextInstances) {
  21063. canvas._iTextInstances.forEach(function(obj) {
  21064. obj.__isMousedown = false;
  21065. });
  21066. }
  21067. }).bind(this);
  21068. canvas.on('mouse:up', canvas._mouseUpITextHandler);
  21069. },
  21070. /**
  21071. * remove canvas event to manage exiting on other instances
  21072. * @private
  21073. */
  21074. _removeCanvasHandlers: function(canvas) {
  21075. canvas.off('mouse:up', canvas._mouseUpITextHandler);
  21076. },
  21077. /**
  21078. * @private
  21079. */
  21080. _tick: function() {
  21081. this._currentTickState = this._animateCursor(this, 1, this.cursorDuration, '_onTickComplete');
  21082. },
  21083. /**
  21084. * @private
  21085. */
  21086. _animateCursor: function(obj, targetOpacity, duration, completeMethod) {
  21087. var tickState;
  21088. tickState = {
  21089. isAborted: false,
  21090. abort: function() {
  21091. this.isAborted = true;
  21092. },
  21093. };
  21094. obj.animate('_currentCursorOpacity', targetOpacity, {
  21095. duration: duration,
  21096. onComplete: function() {
  21097. if (!tickState.isAborted) {
  21098. obj[completeMethod]();
  21099. }
  21100. },
  21101. onChange: function() {
  21102. // we do not want to animate a selection, only cursor
  21103. if (obj.canvas && obj.selectionStart === obj.selectionEnd) {
  21104. obj.renderCursorOrSelection();
  21105. }
  21106. },
  21107. abort: function() {
  21108. return tickState.isAborted;
  21109. }
  21110. });
  21111. return tickState;
  21112. },
  21113. /**
  21114. * @private
  21115. */
  21116. _onTickComplete: function() {
  21117. var _this = this;
  21118. if (this._cursorTimeout1) {
  21119. clearTimeout(this._cursorTimeout1);
  21120. }
  21121. this._cursorTimeout1 = setTimeout(function() {
  21122. _this._currentTickCompleteState = _this._animateCursor(_this, 0, this.cursorDuration / 2, '_tick');
  21123. }, 100);
  21124. },
  21125. /**
  21126. * Initializes delayed cursor
  21127. */
  21128. initDelayedCursor: function(restart) {
  21129. var _this = this,
  21130. delay = restart ? 0 : this.cursorDelay;
  21131. this.abortCursorAnimation();
  21132. this._currentCursorOpacity = 1;
  21133. this._cursorTimeout2 = setTimeout(function() {
  21134. _this._tick();
  21135. }, delay);
  21136. },
  21137. /**
  21138. * Aborts cursor animation and clears all timeouts
  21139. */
  21140. abortCursorAnimation: function() {
  21141. var shouldClear = this._currentTickState || this._currentTickCompleteState;
  21142. this._currentTickState && this._currentTickState.abort();
  21143. this._currentTickCompleteState && this._currentTickCompleteState.abort();
  21144. clearTimeout(this._cursorTimeout1);
  21145. clearTimeout(this._cursorTimeout2);
  21146. this._currentCursorOpacity = 0;
  21147. // to clear just itext area we need to transform the context
  21148. // it may not be worth it
  21149. if (shouldClear) {
  21150. this.canvas && this.canvas.clearContext(this.canvas.contextTop || this.ctx);
  21151. }
  21152. },
  21153. /**
  21154. * Selects entire text
  21155. */
  21156. selectAll: function() {
  21157. this.selectionStart = 0;
  21158. this.selectionEnd = this.text.length;
  21159. this._fireSelectionChanged();
  21160. this._updateTextarea();
  21161. },
  21162. /**
  21163. * Returns selected text
  21164. * @return {String}
  21165. */
  21166. getSelectedText: function() {
  21167. return this.text.slice(this.selectionStart, this.selectionEnd);
  21168. },
  21169. /**
  21170. * Find new selection index representing start of current word according to current selection index
  21171. * @param {Number} startFrom Surrent selection index
  21172. * @return {Number} New selection index
  21173. */
  21174. findWordBoundaryLeft: function(startFrom) {
  21175. var offset = 0, index = startFrom - 1;
  21176. // remove space before cursor first
  21177. if (this._reSpace.test(this.text.charAt(index))) {
  21178. while (this._reSpace.test(this.text.charAt(index))) {
  21179. offset++;
  21180. index--;
  21181. }
  21182. }
  21183. while (/\S/.test(this.text.charAt(index)) && index > -1) {
  21184. offset++;
  21185. index--;
  21186. }
  21187. return startFrom - offset;
  21188. },
  21189. /**
  21190. * Find new selection index representing end of current word according to current selection index
  21191. * @param {Number} startFrom Current selection index
  21192. * @return {Number} New selection index
  21193. */
  21194. findWordBoundaryRight: function(startFrom) {
  21195. var offset = 0, index = startFrom;
  21196. // remove space after cursor first
  21197. if (this._reSpace.test(this.text.charAt(index))) {
  21198. while (this._reSpace.test(this.text.charAt(index))) {
  21199. offset++;
  21200. index++;
  21201. }
  21202. }
  21203. while (/\S/.test(this.text.charAt(index)) && index < this.text.length) {
  21204. offset++;
  21205. index++;
  21206. }
  21207. return startFrom + offset;
  21208. },
  21209. /**
  21210. * Find new selection index representing start of current line according to current selection index
  21211. * @param {Number} startFrom Current selection index
  21212. * @return {Number} New selection index
  21213. */
  21214. findLineBoundaryLeft: function(startFrom) {
  21215. var offset = 0, index = startFrom - 1;
  21216. while (!/\n/.test(this.text.charAt(index)) && index > -1) {
  21217. offset++;
  21218. index--;
  21219. }
  21220. return startFrom - offset;
  21221. },
  21222. /**
  21223. * Find new selection index representing end of current line according to current selection index
  21224. * @param {Number} startFrom Current selection index
  21225. * @return {Number} New selection index
  21226. */
  21227. findLineBoundaryRight: function(startFrom) {
  21228. var offset = 0, index = startFrom;
  21229. while (!/\n/.test(this.text.charAt(index)) && index < this.text.length) {
  21230. offset++;
  21231. index++;
  21232. }
  21233. return startFrom + offset;
  21234. },
  21235. /**
  21236. * Returns number of newlines in selected text
  21237. * @return {Number} Number of newlines in selected text
  21238. */
  21239. getNumNewLinesInSelectedText: function() {
  21240. var selectedText = this.getSelectedText(),
  21241. numNewLines = 0;
  21242. for (var i = 0, len = selectedText.length; i < len; i++) {
  21243. if (selectedText[i] === '\n') {
  21244. numNewLines++;
  21245. }
  21246. }
  21247. return numNewLines;
  21248. },
  21249. /**
  21250. * Finds index corresponding to beginning or end of a word
  21251. * @param {Number} selectionStart Index of a character
  21252. * @param {Number} direction 1 or -1
  21253. * @return {Number} Index of the beginning or end of a word
  21254. */
  21255. searchWordBoundary: function(selectionStart, direction) {
  21256. var index = this._reSpace.test(this.text.charAt(selectionStart)) ? selectionStart - 1 : selectionStart,
  21257. _char = this.text.charAt(index),
  21258. reNonWord = /[ \n\.,;!\?\-]/;
  21259. while (!reNonWord.test(_char) && index > 0 && index < this.text.length) {
  21260. index += direction;
  21261. _char = this.text.charAt(index);
  21262. }
  21263. if (reNonWord.test(_char) && _char !== '\n') {
  21264. index += direction === 1 ? 0 : 1;
  21265. }
  21266. return index;
  21267. },
  21268. /**
  21269. * Selects a word based on the index
  21270. * @param {Number} selectionStart Index of a character
  21271. */
  21272. selectWord: function(selectionStart) {
  21273. selectionStart = selectionStart || this.selectionStart;
  21274. var newSelectionStart = this.searchWordBoundary(selectionStart, -1), /* search backwards */
  21275. newSelectionEnd = this.searchWordBoundary(selectionStart, 1); /* search forward */
  21276. this.selectionStart = newSelectionStart;
  21277. this.selectionEnd = newSelectionEnd;
  21278. this._fireSelectionChanged();
  21279. this._updateTextarea();
  21280. this.renderCursorOrSelection();
  21281. },
  21282. /**
  21283. * Selects a line based on the index
  21284. * @param {Number} selectionStart Index of a character
  21285. */
  21286. selectLine: function(selectionStart) {
  21287. selectionStart = selectionStart || this.selectionStart;
  21288. var newSelectionStart = this.findLineBoundaryLeft(selectionStart),
  21289. newSelectionEnd = this.findLineBoundaryRight(selectionStart);
  21290. this.selectionStart = newSelectionStart;
  21291. this.selectionEnd = newSelectionEnd;
  21292. this._fireSelectionChanged();
  21293. this._updateTextarea();
  21294. },
  21295. /**
  21296. * Enters editing state
  21297. * @return {fabric.IText} thisArg
  21298. * @chainable
  21299. */
  21300. enterEditing: function(e) {
  21301. if (this.isEditing || !this.editable) {
  21302. return;
  21303. }
  21304. if (this.canvas) {
  21305. this.exitEditingOnOthers(this.canvas);
  21306. }
  21307. this.isEditing = true;
  21308. this.selected = true;
  21309. this.initHiddenTextarea(e);
  21310. this.hiddenTextarea.focus();
  21311. this._updateTextarea();
  21312. this._saveEditingProps();
  21313. this._setEditingProps();
  21314. this._textBeforeEdit = this.text;
  21315. this._tick();
  21316. this.fire('editing:entered');
  21317. this._fireSelectionChanged();
  21318. if (!this.canvas) {
  21319. return this;
  21320. }
  21321. this.canvas.fire('text:editing:entered', { target: this });
  21322. this.initMouseMoveHandler();
  21323. this.canvas.renderAll();
  21324. return this;
  21325. },
  21326. exitEditingOnOthers: function(canvas) {
  21327. if (canvas._iTextInstances) {
  21328. canvas._iTextInstances.forEach(function(obj) {
  21329. obj.selected = false;
  21330. if (obj.isEditing) {
  21331. obj.exitEditing();
  21332. }
  21333. });
  21334. }
  21335. },
  21336. /**
  21337. * Initializes "mousemove" event handler
  21338. */
  21339. initMouseMoveHandler: function() {
  21340. this.canvas.on('mouse:move', this.mouseMoveHandler);
  21341. },
  21342. /**
  21343. * @private
  21344. */
  21345. mouseMoveHandler: function(options) {
  21346. if (!this.__isMousedown || !this.isEditing) {
  21347. return;
  21348. }
  21349. var newSelectionStart = this.getSelectionStartFromPointer(options.e),
  21350. currentStart = this.selectionStart,
  21351. currentEnd = this.selectionEnd;
  21352. if (
  21353. (newSelectionStart !== this.__selectionStartOnMouseDown || currentStart === currentEnd)
  21354. &&
  21355. (currentStart === newSelectionStart || currentEnd === newSelectionStart)
  21356. ) {
  21357. return;
  21358. }
  21359. if (newSelectionStart > this.__selectionStartOnMouseDown) {
  21360. this.selectionStart = this.__selectionStartOnMouseDown;
  21361. this.selectionEnd = newSelectionStart;
  21362. }
  21363. else {
  21364. this.selectionStart = newSelectionStart;
  21365. this.selectionEnd = this.__selectionStartOnMouseDown;
  21366. }
  21367. if (this.selectionStart !== currentStart || this.selectionEnd !== currentEnd) {
  21368. this.restartCursorIfNeeded();
  21369. this._fireSelectionChanged();
  21370. this._updateTextarea();
  21371. this.renderCursorOrSelection();
  21372. }
  21373. },
  21374. /**
  21375. * @private
  21376. */
  21377. _setEditingProps: function() {
  21378. this.hoverCursor = 'text';
  21379. if (this.canvas) {
  21380. this.canvas.defaultCursor = this.canvas.moveCursor = 'text';
  21381. }
  21382. this.borderColor = this.editingBorderColor;
  21383. //console.log(this.borderColor)
  21384. this.hasControls = this.selectable = false;
  21385. this.lockMovementX = this.lockMovementY = true;
  21386. },
  21387. /**
  21388. * @private
  21389. */
  21390. _updateTextarea: function() {
  21391. if (!this.hiddenTextarea || this.inCompositionMode) {
  21392. return;
  21393. }
  21394. this.cursorOffsetCache = { };
  21395. this.hiddenTextarea.value = this.text;
  21396. this.hiddenTextarea.selectionStart = this.selectionStart;
  21397. this.hiddenTextarea.selectionEnd = this.selectionEnd;
  21398. if (this.selectionStart === this.selectionEnd) {
  21399. var style = this._calcTextareaPosition();
  21400. this.hiddenTextarea.style.left = style.left;
  21401. this.hiddenTextarea.style.top = style.top;
  21402. this.hiddenTextarea.style.fontSize = style.fontSize;
  21403. }
  21404. },
  21405. /**
  21406. * @private
  21407. * @return {Object} style contains style for hiddenTextarea
  21408. */
  21409. _calcTextareaPosition: function() {
  21410. if (!this.canvas) {
  21411. return { x: 1, y: 1 };
  21412. }
  21413. var chars = this.text.split(''),
  21414. boundaries = this._getCursorBoundaries(chars, 'cursor'),
  21415. cursorLocation = this.get2DCursorLocation(),
  21416. lineIndex = cursorLocation.lineIndex,
  21417. charIndex = cursorLocation.charIndex,
  21418. charHeight = this.getCurrentCharFontSize(lineIndex, charIndex),
  21419. leftOffset = boundaries.leftOffset,
  21420. m = this.calcTransformMatrix(),
  21421. p = {
  21422. x: boundaries.left + leftOffset,
  21423. y: boundaries.top + boundaries.topOffset + charHeight
  21424. },
  21425. upperCanvas = this.canvas.upperCanvasEl,
  21426. maxWidth = upperCanvas.width - charHeight,
  21427. maxHeight = upperCanvas.height - charHeight;
  21428. p = fabric.util.transformPoint(p, m);
  21429. p = fabric.util.transformPoint(p, this.canvas.viewportTransform);
  21430. if (p.x < 0) {
  21431. p.x = 0;
  21432. }
  21433. if (p.x > maxWidth) {
  21434. p.x = maxWidth;
  21435. }
  21436. if (p.y < 0) {
  21437. p.y = 0;
  21438. }
  21439. if (p.y > maxHeight) {
  21440. p.y = maxHeight;
  21441. }
  21442. // add canvas offset on document
  21443. p.x += this.canvas._offset.left;
  21444. p.y += this.canvas._offset.top;
  21445. return { left: p.x + 'px', top: p.y + 'px', fontSize: charHeight };
  21446. },
  21447. /**
  21448. * @private
  21449. */
  21450. _saveEditingProps: function() {
  21451. this._savedProps = {
  21452. hasControls: this.hasControls,
  21453. borderColor: this.borderColor,
  21454. lockMovementX: this.lockMovementX,
  21455. lockMovementY: this.lockMovementY,
  21456. hoverCursor: this.hoverCursor,
  21457. defaultCursor: this.canvas && this.canvas.defaultCursor,
  21458. moveCursor: this.canvas && this.canvas.moveCursor
  21459. };
  21460. },
  21461. /**
  21462. * @private
  21463. */
  21464. _restoreEditingProps: function() {
  21465. if (!this._savedProps) {
  21466. return;
  21467. }
  21468. this.hoverCursor = this._savedProps.overCursor;
  21469. this.hasControls = this._savedProps.hasControls;
  21470. this.borderColor = this._savedProps.borderColor;
  21471. this.lockMovementX = this._savedProps.lockMovementX;
  21472. this.lockMovementY = this._savedProps.lockMovementY;
  21473. if (this.canvas) {
  21474. this.canvas.defaultCursor = this._savedProps.defaultCursor;
  21475. this.canvas.moveCursor = this._savedProps.moveCursor;
  21476. }
  21477. },
  21478. /**
  21479. * Exits from editing state
  21480. * @return {fabric.IText} thisArg
  21481. * @chainable
  21482. */
  21483. exitEditing: function() {
  21484. var isTextChanged = (this._textBeforeEdit !== this.text);
  21485. this.selected = false;
  21486. this.isEditing = false;
  21487. this.selectable = true;
  21488. this.selectionEnd = this.selectionStart;
  21489. if (this.hiddenTextarea) {
  21490. this.hiddenTextarea.blur && this.hiddenTextarea.blur();
  21491. this.canvas && this.hiddenTextarea.parentNode.removeChild(this.hiddenTextarea);
  21492. this.hiddenTextarea = null;
  21493. }
  21494. this.abortCursorAnimation();
  21495. this._restoreEditingProps();
  21496. this._currentCursorOpacity = 0;
  21497. this.fire('editing:exited');
  21498. isTextChanged && this.fire('modified');
  21499. if (this.canvas) {
  21500. this.canvas.off('mouse:move', this.mouseMoveHandler);
  21501. this.canvas.fire('text:editing:exited', { target: this });
  21502. isTextChanged && this.canvas.fire('object:modified', { target: this });
  21503. }
  21504. return this;
  21505. },
  21506. /**
  21507. * @private
  21508. */
  21509. _removeExtraneousStyles: function() {
  21510. for (var prop in this.styles) {
  21511. if (!this._textLines[prop]) {
  21512. delete this.styles[prop];
  21513. }
  21514. }
  21515. },
  21516. /**
  21517. * @private
  21518. */
  21519. _removeCharsFromTo: function(start, end) {
  21520. while (end !== start) {
  21521. this._removeSingleCharAndStyle(start + 1);
  21522. end--;
  21523. }
  21524. this.selectionStart = start;
  21525. this.selectionEnd = start;
  21526. },
  21527. _removeSingleCharAndStyle: function(index) {
  21528. var isBeginningOfLine = this.text[index - 1] === '\n',
  21529. indexStyle = isBeginningOfLine ? index : index - 1;
  21530. this.removeStyleObject(isBeginningOfLine, indexStyle);
  21531. this.text = this.text.slice(0, index - 1) +
  21532. this.text.slice(index);
  21533. this._textLines = this._splitTextIntoLines();
  21534. },
  21535. /**
  21536. * Inserts characters where cursor is (replacing selection if one exists)
  21537. * @param {String} _chars Characters to insert
  21538. * @param {Boolean} useCopiedStyle use fabric.copiedTextStyle
  21539. */
  21540. insertChars: function(_chars, useCopiedStyle) {
  21541. var style;
  21542. if (this.selectionEnd - this.selectionStart > 1) {
  21543. this._removeCharsFromTo(this.selectionStart, this.selectionEnd);
  21544. }
  21545. //short circuit for block paste
  21546. if (!useCopiedStyle && this.isEmptyStyles()) {
  21547. this.insertChar(_chars, false);
  21548. return;
  21549. }
  21550. for (var i = 0, len = _chars.length; i < len; i++) {
  21551. if (useCopiedStyle) {
  21552. style = fabric.util.object.clone(fabric.copiedTextStyle[i], true);
  21553. }
  21554. this.insertChar(_chars[i], i < len - 1, style);
  21555. }
  21556. },
  21557. /**
  21558. * Inserts a character where cursor is
  21559. * @param {String} _char Characters to insert
  21560. * @param {Boolean} skipUpdate trigger rendering and updates at the end of text insert
  21561. * @param {Object} styleObject Style to be inserted for the new char
  21562. */
  21563. insertChar: function(_char, skipUpdate, styleObject) {
  21564. var isEndOfLine = this.text[this.selectionStart] === '\n';
  21565. this.text = this.text.slice(0, this.selectionStart) +
  21566. _char + this.text.slice(this.selectionEnd);
  21567. this._textLines = this._splitTextIntoLines();
  21568. this.insertStyleObjects(_char, isEndOfLine, styleObject);
  21569. this.selectionStart += _char.length;
  21570. this.selectionEnd = this.selectionStart;
  21571. if (skipUpdate) {
  21572. return;
  21573. }
  21574. this._updateTextarea();
  21575. this.setCoords();
  21576. this._fireSelectionChanged();
  21577. this.fire('changed');
  21578. this.restartCursorIfNeeded();
  21579. if (this.canvas) {
  21580. this.canvas.fire('text:changed', { target: this });
  21581. this.canvas.renderAll();
  21582. }
  21583. },
  21584. restartCursorIfNeeded: function() {
  21585. if (!this._currentTickState || this._currentTickState.isAborted
  21586. || !this._currentTickCompleteState || this._currentTickCompleteState.isAborted
  21587. ) {
  21588. this.initDelayedCursor();
  21589. }
  21590. },
  21591. /**
  21592. * Inserts new style object
  21593. * @param {Number} lineIndex Index of a line
  21594. * @param {Number} charIndex Index of a char
  21595. * @param {Boolean} isEndOfLine True if it's end of line
  21596. */
  21597. insertNewlineStyleObject: function(lineIndex, charIndex, isEndOfLine) {
  21598. this.shiftLineStyles(lineIndex, +1);
  21599. var currentCharStyle = {},
  21600. newLineStyles = {};
  21601. if (this.styles[lineIndex] && this.styles[lineIndex][charIndex - 1]) {
  21602. currentCharStyle = this.styles[lineIndex][charIndex - 1];
  21603. }
  21604. // if there's nothing after cursor,
  21605. // we clone current char style onto the next (otherwise empty) line
  21606. if (isEndOfLine && currentCharStyle) {
  21607. newLineStyles[0] = clone(currentCharStyle);
  21608. this.styles[lineIndex + 1] = newLineStyles;
  21609. }
  21610. // otherwise we clone styles of all chars
  21611. // after cursor onto the next line, from the beginning
  21612. else {
  21613. var somethingAdded = false;
  21614. for (var index in this.styles[lineIndex]) {
  21615. var numIndex = parseInt(index, 10);
  21616. if (numIndex >= charIndex) {
  21617. somethingAdded = true;
  21618. newLineStyles[numIndex - charIndex] = this.styles[lineIndex][index];
  21619. // remove lines from the previous line since they're on a new line now
  21620. delete this.styles[lineIndex][index];
  21621. }
  21622. }
  21623. somethingAdded && (this.styles[lineIndex + 1] = newLineStyles);
  21624. }
  21625. this._forceClearCache = true;
  21626. },
  21627. /**
  21628. * Inserts style object for a given line/char index
  21629. * @param {Number} lineIndex Index of a line
  21630. * @param {Number} charIndex Index of a char
  21631. * @param {Object} [style] Style object to insert, if given
  21632. */
  21633. insertCharStyleObject: function(lineIndex, charIndex, style) {
  21634. var currentLineStyles = this.styles[lineIndex],
  21635. currentLineStylesCloned = clone(currentLineStyles);
  21636. if (charIndex === 0 && !style) {
  21637. charIndex = 1;
  21638. }
  21639. // shift all char styles by 1 forward
  21640. // 0,1,2,3 -> (charIndex=2) -> 0,1,3,4 -> (insert 2) -> 0,1,2,3,4
  21641. for (var index in currentLineStylesCloned) {
  21642. var numericIndex = parseInt(index, 10);
  21643. if (numericIndex >= charIndex) {
  21644. currentLineStyles[numericIndex + 1] = currentLineStylesCloned[numericIndex];
  21645. // only delete the style if there was nothing moved there
  21646. if (!currentLineStylesCloned[numericIndex - 1]) {
  21647. delete currentLineStyles[numericIndex];
  21648. }
  21649. }
  21650. }
  21651. var newStyle = style || clone(currentLineStyles[charIndex - 1]);
  21652. newStyle && (this.styles[lineIndex][charIndex] = newStyle);
  21653. this._forceClearCache = true;
  21654. },
  21655. /**
  21656. * Inserts style object(s)
  21657. * @param {String} _chars Characters at the location where style is inserted
  21658. * @param {Boolean} isEndOfLine True if it's end of line
  21659. * @param {Object} [styleObject] Style to insert
  21660. */
  21661. insertStyleObjects: function(_chars, isEndOfLine, styleObject) {
  21662. // removed shortcircuit over isEmptyStyles
  21663. var cursorLocation = this.get2DCursorLocation(),
  21664. lineIndex = cursorLocation.lineIndex,
  21665. charIndex = cursorLocation.charIndex;
  21666. if (!this._getLineStyle(lineIndex)) {
  21667. this._setLineStyle(lineIndex, {});
  21668. }
  21669. if (_chars === '\n') {
  21670. this.insertNewlineStyleObject(lineIndex, charIndex, isEndOfLine);
  21671. }
  21672. else {
  21673. this.insertCharStyleObject(lineIndex, charIndex, styleObject);
  21674. }
  21675. },
  21676. /**
  21677. * Shifts line styles up or down
  21678. * @param {Number} lineIndex Index of a line
  21679. * @param {Number} offset Can be -1 or +1
  21680. */
  21681. shiftLineStyles: function(lineIndex, offset) {
  21682. // shift all line styles by 1 upward or downward
  21683. var clonedStyles = clone(this.styles);
  21684. for (var line in clonedStyles) {
  21685. var numericLine = parseInt(line, 10);
  21686. if (numericLine <= lineIndex) {
  21687. delete clonedStyles[numericLine];
  21688. }
  21689. }
  21690. for (var line in this.styles) {
  21691. var numericLine = parseInt(line, 10);
  21692. if (numericLine > lineIndex) {
  21693. this.styles[numericLine + offset] = clonedStyles[numericLine];
  21694. if (!clonedStyles[numericLine - offset]) {
  21695. delete this.styles[numericLine];
  21696. }
  21697. }
  21698. }
  21699. //TODO: evaluate if delete old style lines with offset -1
  21700. },
  21701. /**
  21702. * Removes style object
  21703. * @param {Boolean} isBeginningOfLine True if cursor is at the beginning of line
  21704. * @param {Number} [index] Optional index. When not given, current selectionStart is used.
  21705. */
  21706. removeStyleObject: function(isBeginningOfLine, index) {
  21707. var cursorLocation = this.get2DCursorLocation(index),
  21708. lineIndex = cursorLocation.lineIndex,
  21709. charIndex = cursorLocation.charIndex;
  21710. this._removeStyleObject(isBeginningOfLine, cursorLocation, lineIndex, charIndex);
  21711. },
  21712. _getTextOnPreviousLine: function(lIndex) {
  21713. return this._textLines[lIndex - 1];
  21714. },
  21715. _removeStyleObject: function(isBeginningOfLine, cursorLocation, lineIndex, charIndex) {
  21716. if (isBeginningOfLine) {
  21717. var textOnPreviousLine = this._getTextOnPreviousLine(cursorLocation.lineIndex),
  21718. newCharIndexOnPrevLine = textOnPreviousLine ? textOnPreviousLine.length : 0;
  21719. if (!this.styles[lineIndex - 1]) {
  21720. this.styles[lineIndex - 1] = {};
  21721. }
  21722. for (charIndex in this.styles[lineIndex]) {
  21723. this.styles[lineIndex - 1][parseInt(charIndex, 10) + newCharIndexOnPrevLine]
  21724. = this.styles[lineIndex][charIndex];
  21725. }
  21726. this.shiftLineStyles(cursorLocation.lineIndex, -1);
  21727. }
  21728. else {
  21729. var currentLineStyles = this.styles[lineIndex];
  21730. if (currentLineStyles) {
  21731. delete currentLineStyles[charIndex];
  21732. }
  21733. var currentLineStylesCloned = clone(currentLineStyles);
  21734. // shift all styles by 1 backwards
  21735. for (var i in currentLineStylesCloned) {
  21736. var numericIndex = parseInt(i, 10);
  21737. if (numericIndex >= charIndex && numericIndex !== 0) {
  21738. currentLineStyles[numericIndex - 1] = currentLineStylesCloned[numericIndex];
  21739. delete currentLineStyles[numericIndex];
  21740. }
  21741. }
  21742. }
  21743. },
  21744. /**
  21745. * Inserts new line
  21746. */
  21747. insertNewline: function() {
  21748. this.insertChars('\n');
  21749. },
  21750. /**
  21751. * Set the selectionStart and selectionEnd according to the ne postion of cursor
  21752. * mimic the key - mouse navigation when shift is pressed.
  21753. */
  21754. setSelectionStartEndWithShift: function(start, end, newSelection) {
  21755. if (newSelection <= start) {
  21756. if (end === start) {
  21757. this._selectionDirection = 'left';
  21758. }
  21759. else if (this._selectionDirection === 'right') {
  21760. this._selectionDirection = 'left';
  21761. this.selectionEnd = start;
  21762. }
  21763. this.selectionStart = newSelection;
  21764. }
  21765. else if (newSelection > start && newSelection < end) {
  21766. if (this._selectionDirection === 'right') {
  21767. this.selectionEnd = newSelection;
  21768. }
  21769. else {
  21770. this.selectionStart = newSelection;
  21771. }
  21772. }
  21773. else {
  21774. // newSelection is > selection start and end
  21775. if (end === start) {
  21776. this._selectionDirection = 'right';
  21777. }
  21778. else if (this._selectionDirection === 'left') {
  21779. this._selectionDirection = 'right';
  21780. this.selectionStart = end;
  21781. }
  21782. this.selectionEnd = newSelection;
  21783. }
  21784. },
  21785. setSelectionInBoundaries: function() {
  21786. var length = this.text.length;
  21787. if (this.selectionStart > length) {
  21788. this.selectionStart = length;
  21789. }
  21790. else if (this.selectionStart < 0) {
  21791. this.selectionStart = 0;
  21792. }
  21793. if (this.selectionEnd > length) {
  21794. this.selectionEnd = length;
  21795. }
  21796. else if (this.selectionEnd < 0) {
  21797. this.selectionEnd = 0;
  21798. }
  21799. }
  21800. });
  21801. })();
  21802. fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ {
  21803. /**
  21804. * Initializes "dbclick" event handler
  21805. */
  21806. initDoubleClickSimulation: function() {
  21807. // for double click
  21808. this.__lastClickTime = +new Date();
  21809. // for triple click
  21810. this.__lastLastClickTime = +new Date();
  21811. this.__lastPointer = { };
  21812. this.on('mousedown', this.onMouseDown.bind(this));
  21813. },
  21814. onMouseDown: function(options) {
  21815. this.__newClickTime = +new Date();
  21816. var newPointer = this.canvas.getPointer(options.e);
  21817. if (this.isTripleClick(newPointer)) {
  21818. this.fire('tripleclick', options);
  21819. this._stopEvent(options.e);
  21820. }
  21821. else if (this.isDoubleClick(newPointer)) {
  21822. this.fire('dblclick', options);
  21823. this._stopEvent(options.e);
  21824. }
  21825. this.__lastLastClickTime = this.__lastClickTime;
  21826. this.__lastClickTime = this.__newClickTime;
  21827. this.__lastPointer = newPointer;
  21828. this.__lastIsEditing = this.isEditing;
  21829. this.__lastSelected = this.selected;
  21830. },
  21831. isDoubleClick: function(newPointer) {
  21832. return this.__newClickTime - this.__lastClickTime < 500 &&
  21833. this.__lastPointer.x === newPointer.x &&
  21834. this.__lastPointer.y === newPointer.y && this.__lastIsEditing;
  21835. },
  21836. isTripleClick: function(newPointer) {
  21837. return this.__newClickTime - this.__lastClickTime < 500 &&
  21838. this.__lastClickTime - this.__lastLastClickTime < 500 &&
  21839. this.__lastPointer.x === newPointer.x &&
  21840. this.__lastPointer.y === newPointer.y;
  21841. },
  21842. /**
  21843. * @private
  21844. */
  21845. _stopEvent: function(e) {
  21846. e.preventDefault && e.preventDefault();
  21847. e.stopPropagation && e.stopPropagation();
  21848. },
  21849. /**
  21850. * Initializes event handlers related to cursor or selection
  21851. */
  21852. initCursorSelectionHandlers: function() {
  21853. this.initMousedownHandler();
  21854. this.initMouseupHandler();
  21855. this.initClicks();
  21856. },
  21857. /**
  21858. * Initializes double and triple click event handlers
  21859. */
  21860. initClicks: function() {
  21861. this.on('dblclick', function(options) {
  21862. this.selectWord(this.getSelectionStartFromPointer(options.e));
  21863. });
  21864. this.on('tripleclick', function(options) {
  21865. this.selectLine(this.getSelectionStartFromPointer(options.e));
  21866. });
  21867. },
  21868. /**
  21869. * Initializes "mousedown" event handler
  21870. */
  21871. initMousedownHandler: function() {
  21872. this.on('mousedown', function(options) {
  21873. if (!this.editable) {
  21874. return;
  21875. }
  21876. var pointer = this.canvas.getPointer(options.e);
  21877. this.__mousedownX = pointer.x;
  21878. this.__mousedownY = pointer.y;
  21879. this.__isMousedown = true;
  21880. if (this.selected) {
  21881. this.setCursorByClick(options.e);
  21882. }
  21883. if (this.isEditing) {
  21884. this.__selectionStartOnMouseDown = this.selectionStart;
  21885. if (this.selectionStart === this.selectionEnd) {
  21886. this.abortCursorAnimation();
  21887. }
  21888. this.renderCursorOrSelection();
  21889. }
  21890. });
  21891. },
  21892. /**
  21893. * @private
  21894. */
  21895. _isObjectMoved: function(e) {
  21896. var pointer = this.canvas.getPointer(e);
  21897. return this.__mousedownX !== pointer.x ||
  21898. this.__mousedownY !== pointer.y;
  21899. },
  21900. /**
  21901. * Initializes "mouseup" event handler
  21902. */
  21903. initMouseupHandler: function() {
  21904. this.on('mouseup', function(options) {
  21905. this.__isMousedown = false;
  21906. if (!this.editable || this._isObjectMoved(options.e)) {
  21907. return;
  21908. }
  21909. if (this.__lastSelected && !this.__corner) {
  21910. this.enterEditing(options.e);
  21911. if (this.selectionStart === this.selectionEnd) {
  21912. this.initDelayedCursor(true);
  21913. }
  21914. else {
  21915. this.renderCursorOrSelection();
  21916. }
  21917. }
  21918. this.selected = true;
  21919. });
  21920. },
  21921. /**
  21922. * Changes cursor location in a text depending on passed pointer (x/y) object
  21923. * @param {Event} e Event object
  21924. */
  21925. setCursorByClick: function(e) {
  21926. var newSelection = this.getSelectionStartFromPointer(e),
  21927. start = this.selectionStart, end = this.selectionEnd;
  21928. if (e.shiftKey) {
  21929. this.setSelectionStartEndWithShift(start, end, newSelection);
  21930. }
  21931. else {
  21932. this.selectionStart = newSelection;
  21933. this.selectionEnd = newSelection;
  21934. }
  21935. if (this.isEditing) {
  21936. this._fireSelectionChanged();
  21937. this._updateTextarea();
  21938. }
  21939. },
  21940. /**
  21941. * Returns index of a character corresponding to where an object was clicked
  21942. * @param {Event} e Event object
  21943. * @return {Number} Index of a character
  21944. */
  21945. getSelectionStartFromPointer: function(e) {
  21946. var mouseOffset = this.getLocalPointer(e),
  21947. prevWidth = 0,
  21948. width = 0,
  21949. height = 0,
  21950. charIndex = 0,
  21951. newSelectionStart,
  21952. line;
  21953. for (var i = 0, len = this._textLines.length; i < len; i++) {
  21954. line = this._textLines[i];
  21955. height += this._getHeightOfLine(this.ctx, i) * this.scaleY;
  21956. var widthOfLine = this._getLineWidth(this.ctx, i),
  21957. lineLeftOffset = this._getLineLeftOffset(widthOfLine);
  21958. width = lineLeftOffset * this.scaleX;
  21959. for (var j = 0, jlen = line.length; j < jlen; j++) {
  21960. prevWidth = width;
  21961. width += this._getWidthOfChar(this.ctx, line[j], i, this.flipX ? jlen - j : j) *
  21962. this.scaleX;
  21963. if (height <= mouseOffset.y || width <= mouseOffset.x) {
  21964. charIndex++;
  21965. continue;
  21966. }
  21967. return this._getNewSelectionStartFromOffset(
  21968. mouseOffset, prevWidth, width, charIndex + i, jlen);
  21969. }
  21970. if (mouseOffset.y < height) {
  21971. //this happens just on end of lines.
  21972. return this._getNewSelectionStartFromOffset(
  21973. mouseOffset, prevWidth, width, charIndex + i - 1, jlen);
  21974. }
  21975. }
  21976. // clicked somewhere after all chars, so set at the end
  21977. if (typeof newSelectionStart === 'undefined') {
  21978. return this.text.length;
  21979. }
  21980. },
  21981. /**
  21982. * @private
  21983. */
  21984. _getNewSelectionStartFromOffset: function(mouseOffset, prevWidth, width, index, jlen) {
  21985. var distanceBtwLastCharAndCursor = mouseOffset.x - prevWidth,
  21986. distanceBtwNextCharAndCursor = width - mouseOffset.x,
  21987. offset = distanceBtwNextCharAndCursor > distanceBtwLastCharAndCursor ? 0 : 1,
  21988. newSelectionStart = index + offset;
  21989. // if object is horizontally flipped, mirror cursor location from the end
  21990. if (this.flipX) {
  21991. newSelectionStart = jlen - newSelectionStart;
  21992. }
  21993. if (newSelectionStart > this.text.length) {
  21994. newSelectionStart = this.text.length;
  21995. }
  21996. return newSelectionStart;
  21997. }
  21998. });
  21999. fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ {
  22000. /**
  22001. * Initializes hidden textarea (needed to bring up keyboard in iOS)
  22002. */
  22003. initHiddenTextarea: function() {
  22004. this.hiddenTextarea = fabric.document.createElement('textarea');
  22005. this.hiddenTextarea.setAttribute('autocapitalize', 'off');
  22006. var style = this._calcTextareaPosition();
  22007. this.hiddenTextarea.style.cssText = 'white-space: nowrap; position: absolute; top: ' + style.top +
  22008. '; left: ' + style.left + '; opacity: 0; width: 1px; height: 1px; z-index: -999;';
  22009. fabric.document.body.appendChild(this.hiddenTextarea);
  22010. fabric.util.addListener(this.hiddenTextarea, 'keydown', this.onKeyDown.bind(this));
  22011. fabric.util.addListener(this.hiddenTextarea, 'keyup', this.onKeyUp.bind(this));
  22012. fabric.util.addListener(this.hiddenTextarea, 'input', this.onInput.bind(this));
  22013. fabric.util.addListener(this.hiddenTextarea, 'copy', this.copy.bind(this));
  22014. fabric.util.addListener(this.hiddenTextarea, 'cut', this.cut.bind(this));
  22015. fabric.util.addListener(this.hiddenTextarea, 'paste', this.paste.bind(this));
  22016. fabric.util.addListener(this.hiddenTextarea, 'compositionstart', this.onCompositionStart.bind(this));
  22017. fabric.util.addListener(this.hiddenTextarea, 'compositionupdate', this.onCompositionUpdate.bind(this));
  22018. fabric.util.addListener(this.hiddenTextarea, 'compositionend', this.onCompositionEnd.bind(this));
  22019. if (!this._clickHandlerInitialized && this.canvas) {
  22020. fabric.util.addListener(this.canvas.upperCanvasEl, 'click', this.onClick.bind(this));
  22021. this._clickHandlerInitialized = true;
  22022. }
  22023. },
  22024. /**
  22025. * @private
  22026. */
  22027. _keysMap: {
  22028. 8: 'removeChars',
  22029. 9: 'exitEditing',
  22030. 27: 'exitEditing',
  22031. 13: 'insertNewline',
  22032. 33: 'moveCursorUp',
  22033. 34: 'moveCursorDown',
  22034. 35: 'moveCursorRight',
  22035. 36: 'moveCursorLeft',
  22036. 37: 'moveCursorLeft',
  22037. 38: 'moveCursorUp',
  22038. 39: 'moveCursorRight',
  22039. 40: 'moveCursorDown',
  22040. 46: 'forwardDelete'
  22041. },
  22042. /**
  22043. * @private
  22044. */
  22045. _ctrlKeysMapUp: {
  22046. 67: 'copy',
  22047. 88: 'cut'
  22048. },
  22049. /**
  22050. * @private
  22051. */
  22052. _ctrlKeysMapDown: {
  22053. 65: 'selectAll'
  22054. },
  22055. onClick: function() {
  22056. // No need to trigger click event here, focus is enough to have the keyboard appear on Android
  22057. this.hiddenTextarea && this.hiddenTextarea.focus();
  22058. },
  22059. /**
  22060. * Handles keyup event
  22061. * @param {Event} e Event object
  22062. */
  22063. onKeyDown: function(e) {
  22064. if (!this.isEditing) {
  22065. return;
  22066. }
  22067. if (e.keyCode in this._keysMap) {
  22068. this[this._keysMap[e.keyCode]](e);
  22069. }
  22070. else if ((e.keyCode in this._ctrlKeysMapDown) && (e.ctrlKey || e.metaKey)) {
  22071. this[this._ctrlKeysMapDown[e.keyCode]](e);
  22072. }
  22073. else {
  22074. return;
  22075. }
  22076. e.stopImmediatePropagation();
  22077. e.preventDefault();
  22078. if (e.keyCode >= 33 && e.keyCode <= 40) {
  22079. // if i press an arrow key just update selection
  22080. this.clearContextTop();
  22081. this.renderCursorOrSelection();
  22082. }
  22083. else {
  22084. this.canvas && this.canvas.renderAll();
  22085. }
  22086. },
  22087. /**
  22088. * Handles keyup event
  22089. * We handle KeyUp because ie11 and edge have difficulties copy/pasting
  22090. * if a copy/cut event fired, keyup is dismissed
  22091. * @param {Event} e Event object
  22092. */
  22093. onKeyUp: function(e) {
  22094. if (!this.isEditing || this._copyDone) {
  22095. this._copyDone = false;
  22096. return;
  22097. }
  22098. if ((e.keyCode in this._ctrlKeysMapUp) && (e.ctrlKey || e.metaKey)) {
  22099. this[this._ctrlKeysMapUp[e.keyCode]](e);
  22100. }
  22101. else {
  22102. return;
  22103. }
  22104. e.stopImmediatePropagation();
  22105. e.preventDefault();
  22106. this.canvas && this.canvas.renderAll();
  22107. },
  22108. /**
  22109. * Handles onInput event
  22110. * @param {Event} e Event object
  22111. */
  22112. onInput: function(e) {
  22113. if (!this.isEditing || this.inCompositionMode) {
  22114. return;
  22115. }
  22116. var offset = this.selectionStart || 0,
  22117. offsetEnd = this.selectionEnd || 0,
  22118. textLength = this.text.length,
  22119. newTextLength = this.hiddenTextarea.value.length,
  22120. diff, charsToInsert, start;
  22121. if (newTextLength > textLength) {
  22122. //we added some character
  22123. start = this._selectionDirection === 'left' ? offsetEnd : offset;
  22124. diff = newTextLength - textLength;
  22125. charsToInsert = this.hiddenTextarea.value.slice(start, start + diff);
  22126. }
  22127. else {
  22128. //we selected a portion of text and then input something else.
  22129. //Internet explorer does not trigger this else
  22130. diff = newTextLength - textLength + offsetEnd - offset;
  22131. charsToInsert = this.hiddenTextarea.value.slice(offset, offset + diff);
  22132. }
  22133. this.insertChars(charsToInsert);
  22134. e.stopPropagation();
  22135. },
  22136. /**
  22137. * Composition start
  22138. */
  22139. onCompositionStart: function() {
  22140. this.inCompositionMode = true;
  22141. this.prevCompositionLength = 0;
  22142. this.compositionStart = this.selectionStart;
  22143. },
  22144. /**
  22145. * Composition end
  22146. */
  22147. onCompositionEnd: function() {
  22148. this.inCompositionMode = false;
  22149. },
  22150. /**
  22151. * Composition update
  22152. */
  22153. onCompositionUpdate: function(e) {
  22154. var data = e.data;
  22155. this.selectionStart = this.compositionStart;
  22156. this.selectionEnd = this.selectionEnd === this.selectionStart ?
  22157. this.compositionStart + this.prevCompositionLength : this.selectionEnd;
  22158. this.insertChars(data, false);
  22159. this.prevCompositionLength = data.length;
  22160. },
  22161. /**
  22162. * Forward delete
  22163. */
  22164. forwardDelete: function(e) {
  22165. if (this.selectionStart === this.selectionEnd) {
  22166. if (this.selectionStart === this.text.length) {
  22167. return;
  22168. }
  22169. this.moveCursorRight(e);
  22170. }
  22171. this.removeChars(e);
  22172. },
  22173. /**
  22174. * Copies selected text
  22175. * @param {Event} e Event object
  22176. */
  22177. copy: function(e) {
  22178. if (this.selectionStart === this.selectionEnd) {
  22179. //do not cut-copy if no selection
  22180. return;
  22181. }
  22182. var selectedText = this.getSelectedText(),
  22183. clipboardData = this._getClipboardData(e);
  22184. // Check for backward compatibility with old browsers
  22185. if (clipboardData) {
  22186. clipboardData.setData('text', selectedText);
  22187. }
  22188. fabric.copiedText = selectedText;
  22189. fabric.copiedTextStyle = this.getSelectionStyles(this.selectionStart, this.selectionEnd);
  22190. e.stopImmediatePropagation();
  22191. e.preventDefault();
  22192. this._copyDone = true;
  22193. },
  22194. /**
  22195. * Pastes text
  22196. * @param {Event} e Event object
  22197. */
  22198. paste: function(e) {
  22199. var copiedText = null,
  22200. clipboardData = this._getClipboardData(e),
  22201. useCopiedStyle = true;
  22202. // Check for backward compatibility with old browsers
  22203. if (clipboardData) {
  22204. copiedText = clipboardData.getData('text').replace(/\r/g, '');
  22205. if (!fabric.copiedTextStyle || fabric.copiedText !== copiedText) {
  22206. useCopiedStyle = false;
  22207. }
  22208. }
  22209. else {
  22210. copiedText = fabric.copiedText;
  22211. }
  22212. if (copiedText) {
  22213. this.insertChars(copiedText, useCopiedStyle);
  22214. }
  22215. e.stopImmediatePropagation();
  22216. e.preventDefault();
  22217. },
  22218. /**
  22219. * Cuts text
  22220. * @param {Event} e Event object
  22221. */
  22222. cut: function(e) {
  22223. if (this.selectionStart === this.selectionEnd) {
  22224. return;
  22225. }
  22226. this.copy(e);
  22227. this.removeChars(e);
  22228. },
  22229. /**
  22230. * @private
  22231. * @param {Event} e Event object
  22232. * @return {Object} Clipboard data object
  22233. */
  22234. _getClipboardData: function(e) {
  22235. return (e && e.clipboardData) || fabric.window.clipboardData;
  22236. },
  22237. /**
  22238. * Finds the width in pixels before the cursor on the same line
  22239. * @private
  22240. * @param {Number} lineIndex
  22241. * @param {Number} charIndex
  22242. * @return {Number} widthBeforeCursor width before cursor
  22243. */
  22244. _getWidthBeforeCursor: function(lineIndex, charIndex) {
  22245. var textBeforeCursor = this._textLines[lineIndex].slice(0, charIndex),
  22246. widthOfLine = this._getLineWidth(this.ctx, lineIndex),
  22247. widthBeforeCursor = this._getLineLeftOffset(widthOfLine), _char;
  22248. for (var i = 0, len = textBeforeCursor.length; i < len; i++) {
  22249. _char = textBeforeCursor[i];
  22250. widthBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i);
  22251. }
  22252. return widthBeforeCursor;
  22253. },
  22254. /**
  22255. * Gets start offset of a selection
  22256. * @param {Event} e Event object
  22257. * @param {Boolean} isRight
  22258. * @return {Number}
  22259. */
  22260. getDownCursorOffset: function(e, isRight) {
  22261. var selectionProp = this._getSelectionForOffset(e, isRight),
  22262. cursorLocation = this.get2DCursorLocation(selectionProp),
  22263. lineIndex = cursorLocation.lineIndex;
  22264. // if on last line, down cursor goes to end of line
  22265. if (lineIndex === this._textLines.length - 1 || e.metaKey || e.keyCode === 34) {
  22266. // move to the end of a text
  22267. return this.text.length - selectionProp;
  22268. }
  22269. var charIndex = cursorLocation.charIndex,
  22270. widthBeforeCursor = this._getWidthBeforeCursor(lineIndex, charIndex),
  22271. indexOnOtherLine = this._getIndexOnLine(lineIndex + 1, widthBeforeCursor),
  22272. textAfterCursor = this._textLines[lineIndex].slice(charIndex);
  22273. return textAfterCursor.length + indexOnOtherLine + 2;
  22274. },
  22275. /**
  22276. * private
  22277. * Helps finding if the offset should be counted from Start or End
  22278. * @param {Event} e Event object
  22279. * @param {Boolean} isRight
  22280. * @return {Number}
  22281. */
  22282. _getSelectionForOffset: function(e, isRight) {
  22283. if (e.shiftKey && this.selectionStart !== this.selectionEnd && isRight) {
  22284. return this.selectionEnd;
  22285. }
  22286. else {
  22287. return this.selectionStart;
  22288. }
  22289. },
  22290. /**
  22291. * @param {Event} e Event object
  22292. * @param {Boolean} isRight
  22293. * @return {Number}
  22294. */
  22295. getUpCursorOffset: function(e, isRight) {
  22296. var selectionProp = this._getSelectionForOffset(e, isRight),
  22297. cursorLocation = this.get2DCursorLocation(selectionProp),
  22298. lineIndex = cursorLocation.lineIndex;
  22299. if (lineIndex === 0 || e.metaKey || e.keyCode === 33) {
  22300. // if on first line, up cursor goes to start of line
  22301. return -selectionProp;
  22302. }
  22303. var charIndex = cursorLocation.charIndex,
  22304. widthBeforeCursor = this._getWidthBeforeCursor(lineIndex, charIndex),
  22305. indexOnOtherLine = this._getIndexOnLine(lineIndex - 1, widthBeforeCursor),
  22306. textBeforeCursor = this._textLines[lineIndex].slice(0, charIndex);
  22307. // return a negative offset
  22308. return -this._textLines[lineIndex - 1].length + indexOnOtherLine - textBeforeCursor.length;
  22309. },
  22310. /**
  22311. * find for a given width it founds the matching character.
  22312. * @private
  22313. */
  22314. _getIndexOnLine: function(lineIndex, width) {
  22315. var widthOfLine = this._getLineWidth(this.ctx, lineIndex),
  22316. textOnLine = this._textLines[lineIndex],
  22317. lineLeftOffset = this._getLineLeftOffset(widthOfLine),
  22318. widthOfCharsOnLine = lineLeftOffset,
  22319. indexOnLine = 0,
  22320. foundMatch;
  22321. for (var j = 0, jlen = textOnLine.length; j < jlen; j++) {
  22322. var _char = textOnLine[j],
  22323. widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j);
  22324. widthOfCharsOnLine += widthOfChar;
  22325. if (widthOfCharsOnLine > width) {
  22326. foundMatch = true;
  22327. var leftEdge = widthOfCharsOnLine - widthOfChar,
  22328. rightEdge = widthOfCharsOnLine,
  22329. offsetFromLeftEdge = Math.abs(leftEdge - width),
  22330. offsetFromRightEdge = Math.abs(rightEdge - width);
  22331. indexOnLine = offsetFromRightEdge < offsetFromLeftEdge ? j : (j - 1);
  22332. break;
  22333. }
  22334. }
  22335. // reached end
  22336. if (!foundMatch) {
  22337. indexOnLine = textOnLine.length - 1;
  22338. }
  22339. return indexOnLine;
  22340. },
  22341. /**
  22342. * Moves cursor down
  22343. * @param {Event} e Event object
  22344. */
  22345. moveCursorDown: function(e) {
  22346. if (this.selectionStart >= this.text.length && this.selectionEnd >= this.text.length) {
  22347. return;
  22348. }
  22349. this._moveCursorUpOrDown('Down', e);
  22350. },
  22351. /**
  22352. * Moves cursor up
  22353. * @param {Event} e Event object
  22354. */
  22355. moveCursorUp: function(e) {
  22356. if (this.selectionStart === 0 && this.selectionEnd === 0) {
  22357. return;
  22358. }
  22359. this._moveCursorUpOrDown('Up', e);
  22360. },
  22361. /**
  22362. * Moves cursor up or down, fires the events
  22363. * @param {String} direction 'Up' or 'Down'
  22364. * @param {Event} e Event object
  22365. */
  22366. _moveCursorUpOrDown: function(direction, e) {
  22367. // getUpCursorOffset
  22368. // getDownCursorOffset
  22369. var action = 'get' + direction + 'CursorOffset',
  22370. offset = this[action](e, this._selectionDirection === 'right');
  22371. if (e.shiftKey) {
  22372. this.moveCursorWithShift(offset);
  22373. }
  22374. else {
  22375. this.moveCursorWithoutShift(offset);
  22376. }
  22377. if (offset !== 0) {
  22378. this.setSelectionInBoundaries();
  22379. this.abortCursorAnimation();
  22380. this._currentCursorOpacity = 1;
  22381. this.initDelayedCursor();
  22382. this._fireSelectionChanged();
  22383. this._updateTextarea();
  22384. }
  22385. },
  22386. /**
  22387. * Moves cursor with shift
  22388. * @param {Number} offset
  22389. */
  22390. moveCursorWithShift: function(offset) {
  22391. var newSelection = this._selectionDirection === 'left'
  22392. ? this.selectionStart + offset
  22393. : this.selectionEnd + offset;
  22394. this.setSelectionStartEndWithShift(this.selectionStart, this.selectionEnd, newSelection);
  22395. return offset !== 0;
  22396. },
  22397. /**
  22398. * Moves cursor up without shift
  22399. * @param {Number} offset
  22400. */
  22401. moveCursorWithoutShift: function(offset) {
  22402. if (offset < 0) {
  22403. this.selectionStart += offset;
  22404. this.selectionEnd = this.selectionStart;
  22405. }
  22406. else {
  22407. this.selectionEnd += offset;
  22408. this.selectionStart = this.selectionEnd;
  22409. }
  22410. return offset !== 0;
  22411. },
  22412. /**
  22413. * Moves cursor left
  22414. * @param {Event} e Event object
  22415. */
  22416. moveCursorLeft: function(e) {
  22417. if (this.selectionStart === 0 && this.selectionEnd === 0) {
  22418. return;
  22419. }
  22420. this._moveCursorLeftOrRight('Left', e);
  22421. },
  22422. /**
  22423. * @private
  22424. * @return {Boolean} true if a change happened
  22425. */
  22426. _move: function(e, prop, direction) {
  22427. var newValue;
  22428. if (e.altKey) {
  22429. newValue = this['findWordBoundary' + direction](this[prop]);
  22430. }
  22431. else if (e.metaKey || e.keyCode === 35 || e.keyCode === 36 ) {
  22432. newValue = this['findLineBoundary' + direction](this[prop]);
  22433. }
  22434. else {
  22435. this[prop] += direction === 'Left' ? -1 : 1;
  22436. return true;
  22437. }
  22438. if (typeof newValue !== undefined && this[prop] !== newValue) {
  22439. this[prop] = newValue;
  22440. return true;
  22441. }
  22442. },
  22443. /**
  22444. * @private
  22445. */
  22446. _moveLeft: function(e, prop) {
  22447. return this._move(e, prop, 'Left');
  22448. },
  22449. /**
  22450. * @private
  22451. */
  22452. _moveRight: function(e, prop) {
  22453. return this._move(e, prop, 'Right');
  22454. },
  22455. /**
  22456. * Moves cursor left without keeping selection
  22457. * @param {Event} e
  22458. */
  22459. moveCursorLeftWithoutShift: function(e) {
  22460. var change = true;
  22461. this._selectionDirection = 'left';
  22462. // only move cursor when there is no selection,
  22463. // otherwise we discard it, and leave cursor on same place
  22464. if (this.selectionEnd === this.selectionStart && this.selectionStart !== 0) {
  22465. change = this._moveLeft(e, 'selectionStart');
  22466. }
  22467. this.selectionEnd = this.selectionStart;
  22468. return change;
  22469. },
  22470. /**
  22471. * Moves cursor left while keeping selection
  22472. * @param {Event} e
  22473. */
  22474. moveCursorLeftWithShift: function(e) {
  22475. if (this._selectionDirection === 'right' && this.selectionStart !== this.selectionEnd) {
  22476. return this._moveLeft(e, 'selectionEnd');
  22477. }
  22478. else if (this.selectionStart !== 0){
  22479. this._selectionDirection = 'left';
  22480. return this._moveLeft(e, 'selectionStart');
  22481. }
  22482. },
  22483. /**
  22484. * Moves cursor right
  22485. * @param {Event} e Event object
  22486. */
  22487. moveCursorRight: function(e) {
  22488. if (this.selectionStart >= this.text.length && this.selectionEnd >= this.text.length) {
  22489. return;
  22490. }
  22491. this._moveCursorLeftOrRight('Right', e);
  22492. },
  22493. /**
  22494. * Moves cursor right or Left, fires event
  22495. * @param {String} direction 'Left', 'Right'
  22496. * @param {Event} e Event object
  22497. */
  22498. _moveCursorLeftOrRight: function(direction, e) {
  22499. var actionName = 'moveCursor' + direction + 'With';
  22500. this._currentCursorOpacity = 1;
  22501. if (e.shiftKey) {
  22502. actionName += 'Shift';
  22503. }
  22504. else {
  22505. actionName += 'outShift';
  22506. }
  22507. if (this[actionName](e)) {
  22508. this.abortCursorAnimation();
  22509. this.initDelayedCursor();
  22510. this._fireSelectionChanged();
  22511. this._updateTextarea();
  22512. }
  22513. },
  22514. /**
  22515. * Moves cursor right while keeping selection
  22516. * @param {Event} e
  22517. */
  22518. moveCursorRightWithShift: function(e) {
  22519. if (this._selectionDirection === 'left' && this.selectionStart !== this.selectionEnd) {
  22520. return this._moveRight(e, 'selectionStart');
  22521. }
  22522. else if (this.selectionEnd !== this.text.length) {
  22523. this._selectionDirection = 'right';
  22524. return this._moveRight(e, 'selectionEnd');
  22525. }
  22526. },
  22527. /**
  22528. * Moves cursor right without keeping selection
  22529. * @param {Event} e Event object
  22530. */
  22531. moveCursorRightWithoutShift: function(e) {
  22532. var changed = true;
  22533. this._selectionDirection = 'right';
  22534. if (this.selectionStart === this.selectionEnd) {
  22535. changed = this._moveRight(e, 'selectionStart');
  22536. this.selectionEnd = this.selectionStart;
  22537. }
  22538. else {
  22539. this.selectionStart = this.selectionEnd;
  22540. }
  22541. return changed;
  22542. },
  22543. /**
  22544. * Removes characters selected by selection
  22545. * @param {Event} e Event object
  22546. */
  22547. removeChars: function(e) {
  22548. if (this.selectionStart === this.selectionEnd) {
  22549. this._removeCharsNearCursor(e);
  22550. }
  22551. else {
  22552. this._removeCharsFromTo(this.selectionStart, this.selectionEnd);
  22553. }
  22554. this.set('dirty', true);
  22555. this.setSelectionEnd(this.selectionStart);
  22556. this._removeExtraneousStyles();
  22557. this.canvas && this.canvas.renderAll();
  22558. this.setCoords();
  22559. this.fire('changed');
  22560. this.canvas && this.canvas.fire('text:changed', { target: this });
  22561. },
  22562. /**
  22563. * @private
  22564. * @param {Event} e Event object
  22565. */
  22566. _removeCharsNearCursor: function(e) {
  22567. if (this.selectionStart === 0) {
  22568. return;
  22569. }
  22570. if (e.metaKey) {
  22571. // remove all till the start of current line
  22572. var leftLineBoundary = this.findLineBoundaryLeft(this.selectionStart);
  22573. this._removeCharsFromTo(leftLineBoundary, this.selectionStart);
  22574. this.setSelectionStart(leftLineBoundary);
  22575. }
  22576. else if (e.altKey) {
  22577. // remove all till the start of current word
  22578. var leftWordBoundary = this.findWordBoundaryLeft(this.selectionStart);
  22579. this._removeCharsFromTo(leftWordBoundary, this.selectionStart);
  22580. this.setSelectionStart(leftWordBoundary);
  22581. }
  22582. else {
  22583. this._removeSingleCharAndStyle(this.selectionStart);
  22584. this.setSelectionStart(this.selectionStart - 1);
  22585. }
  22586. }
  22587. });
  22588. /* _TO_SVG_START_ */
  22589. (function() {
  22590. var toFixed = fabric.util.toFixed,
  22591. NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS;
  22592. fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ {
  22593. /**
  22594. * @private
  22595. */
  22596. _setSVGTextLineText: function(lineIndex, textSpans, height, textLeftOffset, textTopOffset, textBgRects) {
  22597. if (!this._getLineStyle(lineIndex)) {
  22598. fabric.Text.prototype._setSVGTextLineText.call(this,
  22599. lineIndex, textSpans, height, textLeftOffset, textTopOffset);
  22600. }
  22601. else {
  22602. this._setSVGTextLineChars(
  22603. lineIndex, textSpans, height, textLeftOffset, textBgRects);
  22604. }
  22605. },
  22606. /**
  22607. * @private
  22608. */
  22609. _setSVGTextLineChars: function(lineIndex, textSpans, height, textLeftOffset, textBgRects) {
  22610. var chars = this._textLines[lineIndex],
  22611. charOffset = 0,
  22612. lineLeftOffset = this._getLineLeftOffset(this._getLineWidth(this.ctx, lineIndex)) - this.width / 2,
  22613. lineOffset = this._getSVGLineTopOffset(lineIndex),
  22614. heightOfLine = this._getHeightOfLine(this.ctx, lineIndex);
  22615. for (var i = 0, len = chars.length; i < len; i++) {
  22616. var styleDecl = this._getStyleDeclaration(lineIndex, i) || { };
  22617. textSpans.push(
  22618. this._createTextCharSpan(
  22619. chars[i], styleDecl, lineLeftOffset, lineOffset.lineTop + lineOffset.offset, charOffset));
  22620. var charWidth = this._getWidthOfChar(this.ctx, chars[i], lineIndex, i);
  22621. if (styleDecl.textBackgroundColor) {
  22622. textBgRects.push(
  22623. this._createTextCharBg(
  22624. styleDecl, lineLeftOffset, lineOffset.lineTop, heightOfLine, charWidth, charOffset));
  22625. }
  22626. charOffset += charWidth;
  22627. }
  22628. },
  22629. /**
  22630. * @private
  22631. */
  22632. _getSVGLineTopOffset: function(lineIndex) {
  22633. var lineTopOffset = 0, lastHeight = 0;
  22634. for (var j = 0; j < lineIndex; j++) {
  22635. lineTopOffset += this._getHeightOfLine(this.ctx, j);
  22636. }
  22637. lastHeight = this._getHeightOfLine(this.ctx, j);
  22638. return {
  22639. lineTop: lineTopOffset,
  22640. offset: (this._fontSizeMult - this._fontSizeFraction) * lastHeight / (this.lineHeight * this._fontSizeMult)
  22641. };
  22642. },
  22643. /**
  22644. * @private
  22645. */
  22646. _createTextCharBg: function(styleDecl, lineLeftOffset, lineTopOffset, heightOfLine, charWidth, charOffset) {
  22647. return [
  22648. '\t\t<rect fill="', styleDecl.textBackgroundColor,
  22649. '" x="', toFixed(lineLeftOffset + charOffset, NUM_FRACTION_DIGITS),
  22650. '" y="', toFixed(lineTopOffset - this.height / 2, NUM_FRACTION_DIGITS),
  22651. '" width="', toFixed(charWidth, NUM_FRACTION_DIGITS),
  22652. '" height="', toFixed(heightOfLine / this.lineHeight, NUM_FRACTION_DIGITS),
  22653. '"></rect>\n'
  22654. ].join('');
  22655. },
  22656. /**
  22657. * @private
  22658. */
  22659. _createTextCharSpan: function(_char, styleDecl, lineLeftOffset, lineTopOffset, charOffset) {
  22660. var fillStyles = this.getSvgStyles.call(fabric.util.object.extend({
  22661. visible: true,
  22662. fill: this.fill,
  22663. stroke: this.stroke,
  22664. type: 'text',
  22665. getSvgFilter: fabric.Object.prototype.getSvgFilter
  22666. }, styleDecl));
  22667. return [
  22668. '\t\t\t<tspan x="', toFixed(lineLeftOffset + charOffset, NUM_FRACTION_DIGITS), '" y="',
  22669. toFixed(lineTopOffset - this.height / 2, NUM_FRACTION_DIGITS), '" ',
  22670. (styleDecl.fontFamily ? 'font-family="' + styleDecl.fontFamily.replace(/"/g, '\'') + '" ' : ''),
  22671. (styleDecl.fontSize ? 'font-size="' + styleDecl.fontSize + '" ' : ''),
  22672. (styleDecl.fontStyle ? 'font-style="' + styleDecl.fontStyle + '" ' : ''),
  22673. (styleDecl.fontWeight ? 'font-weight="' + styleDecl.fontWeight + '" ' : ''),
  22674. (styleDecl.textDecoration ? 'text-decoration="' + styleDecl.textDecoration + '" ' : ''),
  22675. 'style="', fillStyles, '">',
  22676. fabric.util.string.escapeXml(_char),
  22677. '</tspan>\n'
  22678. ].join('');
  22679. }
  22680. });
  22681. })();
  22682. /* _TO_SVG_END_ */
  22683. (function(global) {
  22684. 'use strict';
  22685. var fabric = global.fabric || (global.fabric = {});
  22686. /**
  22687. * Textbox class, based on IText, allows the user to resize the text rectangle
  22688. * and wraps lines automatically. Textboxes have their Y scaling locked, the
  22689. * user can only change width. Height is adjusted automatically based on the
  22690. * wrapping of lines.
  22691. * @class fabric.Textbox
  22692. * @extends fabric.IText
  22693. * @mixes fabric.Observable
  22694. * @return {fabric.Textbox} thisArg
  22695. * @see {@link fabric.Textbox#initialize} for constructor definition
  22696. */
  22697. fabric.Textbox = fabric.util.createClass(fabric.IText, fabric.Observable, {
  22698. /**
  22699. * Type of an object
  22700. * @type String
  22701. * @default
  22702. */
  22703. type: 'textbox',
  22704. /**
  22705. * Minimum width of textbox, in pixels.
  22706. * @type Number
  22707. * @default
  22708. */
  22709. minWidth: 20,
  22710. /**
  22711. * Minimum calculated width of a textbox, in pixels.
  22712. * fixed to 2 so that an empty textbox cannot go to 0
  22713. * and is still selectable without text.
  22714. * @type Number
  22715. * @default
  22716. */
  22717. dynamicMinWidth: 2,
  22718. /**
  22719. * Cached array of text wrapping.
  22720. * @type Array
  22721. */
  22722. __cachedLines: null,
  22723. /**
  22724. * Override standard Object class values
  22725. */
  22726. lockScalingY: true,
  22727. /**
  22728. * Override standard Object class values
  22729. */
  22730. lockScalingFlip: true,
  22731. /**
  22732. * Override standard Object class values
  22733. * Textbox needs this on false
  22734. */
  22735. noScaleCache: false,
  22736. /**
  22737. * Constructor. Some scaling related property values are forced. Visibility
  22738. * of controls is also fixed; only the rotation and width controls are
  22739. * made available.
  22740. * @param {String} text Text string
  22741. * @param {Object} [options] Options object
  22742. * @return {fabric.Textbox} thisArg
  22743. */
  22744. initialize: function(text, options) {
  22745. this.callSuper('initialize', text, options);
  22746. this.setControlsVisibility(fabric.Textbox.getTextboxControlVisibility());
  22747. this.ctx = this.objectCaching ? this._cacheContext : fabric.util.createCanvasElement().getContext('2d');
  22748. // add width to this list of props that effect line wrapping.
  22749. this._dimensionAffectingProps.push('width');
  22750. },
  22751. /**
  22752. * Unlike superclass's version of this function, Textbox does not update
  22753. * its width.
  22754. * @param {CanvasRenderingContext2D} ctx Context to use for measurements
  22755. * @private
  22756. * @override
  22757. */
  22758. _initDimensions: function(ctx) {
  22759. if (this.__skipDimension) {
  22760. return;
  22761. }
  22762. if (!ctx) {
  22763. ctx = fabric.util.createCanvasElement().getContext('2d');
  22764. this._setTextStyles(ctx);
  22765. this.clearContextTop();
  22766. }
  22767. // clear dynamicMinWidth as it will be different after we re-wrap line
  22768. this.dynamicMinWidth = 0;
  22769. // wrap lines
  22770. this._textLines = this._splitTextIntoLines(ctx);
  22771. // if after wrapping, the width is smaller than dynamicMinWidth, change the width and re-wrap
  22772. if (this.dynamicMinWidth > this.width) {
  22773. this._set('width', this.dynamicMinWidth);
  22774. }
  22775. // clear cache and re-calculate height
  22776. this._clearCache();
  22777. this.height = this._getTextHeight(ctx);
  22778. },
  22779. /**
  22780. * Generate an object that translates the style object so that it is
  22781. * broken up by visual lines (new lines and automatic wrapping).
  22782. * The original text styles object is broken up by actual lines (new lines only),
  22783. * which is only sufficient for Text / IText
  22784. * @private
  22785. */
  22786. _generateStyleMap: function() {
  22787. var realLineCount = 0,
  22788. realLineCharCount = 0,
  22789. charCount = 0,
  22790. map = {};
  22791. for (var i = 0; i < this._textLines.length; i++) {
  22792. if (this.text[charCount] === '\n' && i > 0) {
  22793. realLineCharCount = 0;
  22794. charCount++;
  22795. realLineCount++;
  22796. }
  22797. else if (this.text[charCount] === ' ' && i > 0) {
  22798. // this case deals with space's that are removed from end of lines when wrapping
  22799. realLineCharCount++;
  22800. charCount++;
  22801. }
  22802. map[i] = { line: realLineCount, offset: realLineCharCount };
  22803. charCount += this._textLines[i].length;
  22804. realLineCharCount += this._textLines[i].length;
  22805. }
  22806. return map;
  22807. },
  22808. /**
  22809. * @param {Number} lineIndex
  22810. * @param {Number} charIndex
  22811. * @param {Boolean} [returnCloneOrEmpty=false]
  22812. * @private
  22813. */
  22814. _getStyleDeclaration: function(lineIndex, charIndex, returnCloneOrEmpty) {
  22815. if (this._styleMap) {
  22816. var map = this._styleMap[lineIndex];
  22817. if (!map) {
  22818. return returnCloneOrEmpty ? { } : null;
  22819. }
  22820. lineIndex = map.line;
  22821. charIndex = map.offset + charIndex;
  22822. }
  22823. return this.callSuper('_getStyleDeclaration', lineIndex, charIndex, returnCloneOrEmpty);
  22824. },
  22825. /**
  22826. * @param {Number} lineIndex
  22827. * @param {Number} charIndex
  22828. * @param {Object} style
  22829. * @private
  22830. */
  22831. _setStyleDeclaration: function(lineIndex, charIndex, style) {
  22832. var map = this._styleMap[lineIndex];
  22833. lineIndex = map.line;
  22834. charIndex = map.offset + charIndex;
  22835. this.styles[lineIndex][charIndex] = style;
  22836. },
  22837. /**
  22838. * @param {Number} lineIndex
  22839. * @param {Number} charIndex
  22840. * @private
  22841. */
  22842. _deleteStyleDeclaration: function(lineIndex, charIndex) {
  22843. var map = this._styleMap[lineIndex];
  22844. lineIndex = map.line;
  22845. charIndex = map.offset + charIndex;
  22846. delete this.styles[lineIndex][charIndex];
  22847. },
  22848. /**
  22849. * @param {Number} lineIndex
  22850. * @private
  22851. */
  22852. _getLineStyle: function(lineIndex) {
  22853. var map = this._styleMap[lineIndex];
  22854. return this.styles[map.line];
  22855. },
  22856. /**
  22857. * @param {Number} lineIndex
  22858. * @param {Object} style
  22859. * @private
  22860. */
  22861. _setLineStyle: function(lineIndex, style) {
  22862. var map = this._styleMap[lineIndex];
  22863. this.styles[map.line] = style;
  22864. },
  22865. /**
  22866. * @param {Number} lineIndex
  22867. * @private
  22868. */
  22869. _deleteLineStyle: function(lineIndex) {
  22870. var map = this._styleMap[lineIndex];
  22871. delete this.styles[map.line];
  22872. },
  22873. /**
  22874. * Wraps text using the 'width' property of Textbox. First this function
  22875. * splits text on newlines, so we preserve newlines entered by the user.
  22876. * Then it wraps each line using the width of the Textbox by calling
  22877. * _wrapLine().
  22878. * @param {CanvasRenderingContext2D} ctx Context to use for measurements
  22879. * @param {String} text The string of text that is split into lines
  22880. * @returns {Array} Array of lines
  22881. */
  22882. _wrapText: function(ctx, text) {
  22883. var lines = text.split(this._reNewline), wrapped = [], i;
  22884. for (i = 0; i < lines.length; i++) {
  22885. wrapped = wrapped.concat(this._wrapLine(ctx, lines[i], i));
  22886. }
  22887. return wrapped;
  22888. },
  22889. /**
  22890. * Helper function to measure a string of text, given its lineIndex and charIndex offset
  22891. *
  22892. * @param {CanvasRenderingContext2D} ctx
  22893. * @param {String} text
  22894. * @param {number} lineIndex
  22895. * @param {number} charOffset
  22896. * @returns {number}
  22897. * @private
  22898. */
  22899. _measureText: function(ctx, text, lineIndex, charOffset) {
  22900. var width = 0;
  22901. charOffset = charOffset || 0;
  22902. for (var i = 0, len = text.length; i < len; i++) {
  22903. width += this._getWidthOfChar(ctx, text[i], lineIndex, i + charOffset);
  22904. }
  22905. return width;
  22906. },
  22907. /**
  22908. * Wraps a line of text using the width of the Textbox and a context.
  22909. * @param {CanvasRenderingContext2D} ctx Context to use for measurements
  22910. * @param {String} text The string of text to split into lines
  22911. * @param {Number} lineIndex
  22912. * @returns {Array} Array of line(s) into which the given text is wrapped
  22913. * to.
  22914. */
  22915. _wrapLine: function(ctx, text, lineIndex) {
  22916. var lineWidth = 0,
  22917. lines = [],
  22918. line = '',
  22919. words = text.split(' '),
  22920. word = '',
  22921. offset = 0,
  22922. infix = ' ',
  22923. wordWidth = 0,
  22924. infixWidth = 0,
  22925. largestWordWidth = 0,
  22926. lineJustStarted = true,
  22927. additionalSpace = this._getWidthOfCharSpacing();
  22928. for (var i = 0; i < words.length; i++) {
  22929. word = words[i];
  22930. wordWidth = this._measureText(ctx, word, lineIndex, offset);
  22931. offset += word.length;
  22932. lineWidth += infixWidth + wordWidth - additionalSpace;
  22933. if (lineWidth >= this.width && !lineJustStarted) {
  22934. lines.push(line);
  22935. line = '';
  22936. lineWidth = wordWidth;
  22937. lineJustStarted = true;
  22938. }
  22939. else {
  22940. lineWidth += additionalSpace;
  22941. }
  22942. if (!lineJustStarted) {
  22943. line += infix;
  22944. }
  22945. line += word;
  22946. infixWidth = this._measureText(ctx, infix, lineIndex, offset);
  22947. offset++;
  22948. lineJustStarted = false;
  22949. // keep track of largest word
  22950. if (wordWidth > largestWordWidth) {
  22951. largestWordWidth = wordWidth;
  22952. }
  22953. }
  22954. i && lines.push(line);
  22955. if (largestWordWidth > this.dynamicMinWidth) {
  22956. this.dynamicMinWidth = largestWordWidth - additionalSpace;
  22957. }
  22958. return lines;
  22959. },
  22960. /**
  22961. * Gets lines of text to render in the Textbox. This function calculates
  22962. * text wrapping on the fly everytime it is called.
  22963. * @returns {Array} Array of lines in the Textbox.
  22964. * @override
  22965. */
  22966. _splitTextIntoLines: function(ctx) {
  22967. ctx = ctx || this.ctx;
  22968. var originalAlign = this.textAlign;
  22969. this._styleMap = null;
  22970. ctx.save();
  22971. this._setTextStyles(ctx);
  22972. this.textAlign = 'left';
  22973. var lines = this._wrapText(ctx, this.text);
  22974. this.textAlign = originalAlign;
  22975. ctx.restore();
  22976. this._textLines = lines;
  22977. this._styleMap = this._generateStyleMap();
  22978. return lines;
  22979. },
  22980. /**
  22981. * When part of a group, we don't want the Textbox's scale to increase if
  22982. * the group's increases. That's why we reduce the scale of the Textbox by
  22983. * the amount that the group's increases. This is to maintain the effective
  22984. * scale of the Textbox at 1, so that font-size values make sense. Otherwise
  22985. * the same font-size value would result in different actual size depending
  22986. * on the value of the scale.
  22987. * @param {String} key
  22988. * @param {*} value
  22989. */
  22990. setOnGroup: function(key, value) {
  22991. if (key === 'scaleX') {
  22992. this.set('scaleX', Math.abs(1 / value));
  22993. this.set('width', (this.get('width') * value) /
  22994. (typeof this.__oldScaleX === 'undefined' ? 1 : this.__oldScaleX));
  22995. this.__oldScaleX = value;
  22996. }
  22997. },
  22998. /**
  22999. * Returns 2d representation (lineIndex and charIndex) of cursor (or selection start).
  23000. * Overrides the superclass function to take into account text wrapping.
  23001. *
  23002. * @param {Number} [selectionStart] Optional index. When not given, current selectionStart is used.
  23003. */
  23004. get2DCursorLocation: function(selectionStart) {
  23005. if (typeof selectionStart === 'undefined') {
  23006. selectionStart = this.selectionStart;
  23007. }
  23008. var numLines = this._textLines.length,
  23009. removed = 0;
  23010. for (var i = 0; i < numLines; i++) {
  23011. var line = this._textLines[i],
  23012. lineLen = line.length;
  23013. if (selectionStart <= removed + lineLen) {
  23014. return {
  23015. lineIndex: i,
  23016. charIndex: selectionStart - removed
  23017. };
  23018. }
  23019. removed += lineLen;
  23020. if (this.text[removed] === '\n' || this.text[removed] === ' ') {
  23021. removed++;
  23022. }
  23023. }
  23024. return {
  23025. lineIndex: numLines - 1,
  23026. charIndex: this._textLines[numLines - 1].length
  23027. };
  23028. },
  23029. /**
  23030. * Overrides superclass function and uses text wrapping data to get cursor
  23031. * boundary offsets instead of the array of chars.
  23032. * @param {Array} chars Unused
  23033. * @param {String} typeOfBoundaries Can be 'cursor' or 'selection'
  23034. * @returns {Object} Object with 'top', 'left', and 'lineLeft' properties set.
  23035. */
  23036. _getCursorBoundariesOffsets: function(chars, typeOfBoundaries) {
  23037. var topOffset = 0,
  23038. leftOffset = 0,
  23039. cursorLocation = this.get2DCursorLocation(),
  23040. lineChars = this._textLines[cursorLocation.lineIndex].split(''),
  23041. lineLeftOffset = this._getLineLeftOffset(this._getLineWidth(this.ctx, cursorLocation.lineIndex));
  23042. for (var i = 0; i < cursorLocation.charIndex; i++) {
  23043. leftOffset += this._getWidthOfChar(this.ctx, lineChars[i], cursorLocation.lineIndex, i);
  23044. }
  23045. for (i = 0; i < cursorLocation.lineIndex; i++) {
  23046. topOffset += this._getHeightOfLine(this.ctx, i);
  23047. }
  23048. if (typeOfBoundaries === 'cursor') {
  23049. topOffset += (1 - this._fontSizeFraction) * this._getHeightOfLine(this.ctx, cursorLocation.lineIndex)
  23050. / this.lineHeight - this.getCurrentCharFontSize(cursorLocation.lineIndex, cursorLocation.charIndex)
  23051. * (1 - this._fontSizeFraction);
  23052. }
  23053. return {
  23054. top: topOffset,
  23055. left: leftOffset,
  23056. lineLeft: lineLeftOffset
  23057. };
  23058. },
  23059. getMinWidth: function() {
  23060. return Math.max(this.minWidth, this.dynamicMinWidth);
  23061. },
  23062. /**
  23063. * Returns object representation of an instance
  23064. * @method toObject
  23065. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  23066. * @return {Object} object representation of an instance
  23067. */
  23068. toObject: function(propertiesToInclude) {
  23069. return this.callSuper('toObject', ['minWidth'].concat(propertiesToInclude));
  23070. }
  23071. });
  23072. /**
  23073. * Returns fabric.Textbox instance from an object representation
  23074. * @static
  23075. * @memberOf fabric.Textbox
  23076. * @param {Object} object Object to create an instance from
  23077. * @param {Function} [callback] Callback to invoke when an fabric.Textbox instance is created
  23078. * @param {Boolean} [forceAsync] Force an async behaviour trying to create pattern first
  23079. * @return {fabric.Textbox} instance of fabric.Textbox
  23080. */
  23081. fabric.Textbox.fromObject = function(object, callback, forceAsync) {
  23082. return fabric.Object._fromObject('Textbox', object, callback, forceAsync, 'text');
  23083. };
  23084. /**
  23085. * Returns the default controls visibility required for Textboxes.
  23086. * @returns {Object}
  23087. */
  23088. fabric.Textbox.getTextboxControlVisibility = function() {
  23089. return {
  23090. tl: false,
  23091. tr: false,
  23092. br: false,
  23093. bl: false,
  23094. ml: true,
  23095. mt: false,
  23096. mr: true,
  23097. mb: false,
  23098. mtr: true
  23099. };
  23100. };
  23101. })(typeof exports !== 'undefined' ? exports : this);
  23102. (function() {
  23103. /**
  23104. * Override _setObjectScale and add Textbox specific resizing behavior. Resizing
  23105. * a Textbox doesn't scale text, it only changes width and makes text wrap automatically.
  23106. */
  23107. var setObjectScaleOverridden = fabric.Canvas.prototype._setObjectScale;
  23108. fabric.Canvas.prototype._setObjectScale = function(localMouse, transform,
  23109. lockScalingX, lockScalingY, by, lockScalingFlip, _dim) {
  23110. var t = transform.target;
  23111. if (t instanceof fabric.Textbox) {
  23112. var w = t.width * ((localMouse.x / transform.scaleX) / (t.width + t.strokeWidth));
  23113. if (w >= t.getMinWidth()) {
  23114. t.set('width', w);
  23115. return true;
  23116. }
  23117. }
  23118. else {
  23119. return setObjectScaleOverridden.call(fabric.Canvas.prototype, localMouse, transform,
  23120. lockScalingX, lockScalingY, by, lockScalingFlip, _dim);
  23121. }
  23122. };
  23123. /**
  23124. * Sets controls of this group to the Textbox's special configuration if
  23125. * one is present in the group. Deletes _controlsVisibility otherwise, so that
  23126. * it gets initialized to default value at runtime.
  23127. */
  23128. fabric.Group.prototype._refreshControlsVisibility = function() {
  23129. if (typeof fabric.Textbox === 'undefined') {
  23130. return;
  23131. }
  23132. for (var i = this._objects.length; i--;) {
  23133. if (this._objects[i] instanceof fabric.Textbox) {
  23134. this.setControlsVisibility(fabric.Textbox.getTextboxControlVisibility());
  23135. return;
  23136. }
  23137. }
  23138. };
  23139. fabric.util.object.extend(fabric.Textbox.prototype, /** @lends fabric.IText.prototype */ {
  23140. /**
  23141. * @private
  23142. */
  23143. _removeExtraneousStyles: function() {
  23144. for (var prop in this._styleMap) {
  23145. if (!this._textLines[prop]) {
  23146. delete this.styles[this._styleMap[prop].line];
  23147. }
  23148. }
  23149. },
  23150. /**
  23151. * Inserts style object for a given line/char index
  23152. * @param {Number} lineIndex Index of a line
  23153. * @param {Number} charIndex Index of a char
  23154. * @param {Object} [style] Style object to insert, if given
  23155. */
  23156. insertCharStyleObject: function(lineIndex, charIndex, style) {
  23157. // adjust lineIndex and charIndex
  23158. var map = this._styleMap[lineIndex];
  23159. lineIndex = map.line;
  23160. charIndex = map.offset + charIndex;
  23161. fabric.IText.prototype.insertCharStyleObject.apply(this, [lineIndex, charIndex, style]);
  23162. },
  23163. /**
  23164. * Inserts new style object
  23165. * @param {Number} lineIndex Index of a line
  23166. * @param {Number} charIndex Index of a char
  23167. * @param {Boolean} isEndOfLine True if it's end of line
  23168. */
  23169. insertNewlineStyleObject: function(lineIndex, charIndex, isEndOfLine) {
  23170. // adjust lineIndex and charIndex
  23171. var map = this._styleMap[lineIndex];
  23172. lineIndex = map.line;
  23173. charIndex = map.offset + charIndex;
  23174. fabric.IText.prototype.insertNewlineStyleObject.apply(this, [lineIndex, charIndex, isEndOfLine]);
  23175. },
  23176. /**
  23177. * Shifts line styles up or down. This function is slightly different than the one in
  23178. * itext_behaviour as it takes into account the styleMap.
  23179. *
  23180. * @param {Number} lineIndex Index of a line
  23181. * @param {Number} offset Can be -1 or +1
  23182. */
  23183. shiftLineStyles: function(lineIndex, offset) {
  23184. // shift all line styles by 1 upward
  23185. var map = this._styleMap[lineIndex];
  23186. // adjust line index
  23187. lineIndex = map.line;
  23188. fabric.IText.prototype.shiftLineStyles.call(this, lineIndex, offset);
  23189. },
  23190. /**
  23191. * Figure out programatically the text on previous actual line (actual = separated by \n);
  23192. *
  23193. * @param {Number} lIndex
  23194. * @returns {String}
  23195. * @private
  23196. */
  23197. _getTextOnPreviousLine: function(lIndex) {
  23198. var textOnPreviousLine = this._textLines[lIndex - 1];
  23199. while (this._styleMap[lIndex - 2] && this._styleMap[lIndex - 2].line === this._styleMap[lIndex - 1].line) {
  23200. textOnPreviousLine = this._textLines[lIndex - 2] + textOnPreviousLine;
  23201. lIndex--;
  23202. }
  23203. return textOnPreviousLine;
  23204. },
  23205. /**
  23206. * Removes style object
  23207. * @param {Boolean} isBeginningOfLine True if cursor is at the beginning of line
  23208. * @param {Number} [index] Optional index. When not given, current selectionStart is used.
  23209. */
  23210. removeStyleObject: function(isBeginningOfLine, index) {
  23211. var cursorLocation = this.get2DCursorLocation(index),
  23212. map = this._styleMap[cursorLocation.lineIndex],
  23213. lineIndex = map.line,
  23214. charIndex = map.offset + cursorLocation.charIndex;
  23215. this._removeStyleObject(isBeginningOfLine, cursorLocation, lineIndex, charIndex);
  23216. }
  23217. });
  23218. })();
  23219. (function() {
  23220. var override = fabric.IText.prototype._getNewSelectionStartFromOffset;
  23221. /**
  23222. * Overrides the IText implementation and adjusts character index as there is not always a linebreak
  23223. *
  23224. * @param {Number} mouseOffset
  23225. * @param {Number} prevWidth
  23226. * @param {Number} width
  23227. * @param {Number} index
  23228. * @param {Number} jlen
  23229. * @returns {Number}
  23230. */
  23231. fabric.IText.prototype._getNewSelectionStartFromOffset = function(mouseOffset, prevWidth, width, index, jlen) {
  23232. index = override.call(this, mouseOffset, prevWidth, width, index, jlen);
  23233. // the index passed into the function is padded by the amount of lines from _textLines (to account for \n)
  23234. // we need to remove this padding, and pad it by actual lines, and / or spaces that are meant to be there
  23235. var tmp = 0,
  23236. removed = 0;
  23237. // account for removed characters
  23238. for (var i = 0; i < this._textLines.length; i++) {
  23239. tmp += this._textLines[i].length;
  23240. if (tmp + removed >= index) {
  23241. break;
  23242. }
  23243. if (this.text[tmp + removed] === '\n' || this.text[tmp + removed] === ' ') {
  23244. removed++;
  23245. }
  23246. }
  23247. return index - i + removed;
  23248. };
  23249. })();
  23250. (function() {
  23251. if (typeof document !== 'undefined' && typeof window !== 'undefined') {
  23252. return;
  23253. }
  23254. var DOMParser = require('xmldom').DOMParser,
  23255. URL = require('url'),
  23256. HTTP = require('http'),
  23257. HTTPS = require('https'),
  23258. Canvas = require('canvas'),
  23259. Image = require('canvas').Image;
  23260. /** @private */
  23261. function request(url, encoding, callback) {
  23262. var oURL = URL.parse(url);
  23263. // detect if http or https is used
  23264. if ( !oURL.port ) {
  23265. oURL.port = ( oURL.protocol.indexOf('https:') === 0 ) ? 443 : 80;
  23266. }
  23267. // assign request handler based on protocol
  23268. var reqHandler = (oURL.protocol.indexOf('https:') === 0 ) ? HTTPS : HTTP,
  23269. req = reqHandler.request({
  23270. hostname: oURL.hostname,
  23271. port: oURL.port,
  23272. path: oURL.path,
  23273. method: 'GET'
  23274. }, function(response) {
  23275. var body = '';
  23276. if (encoding) {
  23277. response.setEncoding(encoding);
  23278. }
  23279. response.on('end', function () {
  23280. callback(body);
  23281. });
  23282. response.on('data', function (chunk) {
  23283. if (response.statusCode === 200) {
  23284. body += chunk;
  23285. }
  23286. });
  23287. });
  23288. req.on('error', function(err) {
  23289. if (err.errno === process.ECONNREFUSED) {
  23290. fabric.log('ECONNREFUSED: connection refused to ' + oURL.hostname + ':' + oURL.port);
  23291. }
  23292. else {
  23293. fabric.log(err.message);
  23294. }
  23295. callback(null);
  23296. });
  23297. req.end();
  23298. }
  23299. /** @private */
  23300. function requestFs(path, callback) {
  23301. var fs = require('fs');
  23302. fs.readFile(path, function (err, data) {
  23303. if (err) {
  23304. fabric.log(err);
  23305. throw err;
  23306. }
  23307. else {
  23308. callback(data);
  23309. }
  23310. });
  23311. }
  23312. fabric.util.loadImage = function(url, callback, context) {
  23313. function createImageAndCallBack(data) {
  23314. if (data) {
  23315. img.src = new Buffer(data, 'binary');
  23316. // preserving original url, which seems to be lost in node-canvas
  23317. img._src = url;
  23318. callback && callback.call(context, img);
  23319. }
  23320. else {
  23321. img = null;
  23322. callback && callback.call(context, null, true);
  23323. }
  23324. }
  23325. var img = new Image();
  23326. if (url && (url instanceof Buffer || url.indexOf('data') === 0)) {
  23327. img.src = img._src = url;
  23328. callback && callback.call(context, img);
  23329. }
  23330. else if (url && url.indexOf('http') !== 0) {
  23331. requestFs(url, createImageAndCallBack);
  23332. }
  23333. else if (url) {
  23334. request(url, 'binary', createImageAndCallBack);
  23335. }
  23336. else {
  23337. callback && callback.call(context, url);
  23338. }
  23339. };
  23340. fabric.loadSVGFromURL = function(url, callback, reviver) {
  23341. url = url.replace(/^\n\s*/, '').replace(/\?.*$/, '').trim();
  23342. if (url.indexOf('http') !== 0) {
  23343. requestFs(url, function(body) {
  23344. fabric.loadSVGFromString(body.toString(), callback, reviver);
  23345. });
  23346. }
  23347. else {
  23348. request(url, '', function(body) {
  23349. fabric.loadSVGFromString(body, callback, reviver);
  23350. });
  23351. }
  23352. };
  23353. fabric.loadSVGFromString = function(string, callback, reviver) {
  23354. var doc = new DOMParser().parseFromString(string);
  23355. fabric.parseSVGDocument(doc.documentElement, function(results, options) {
  23356. callback && callback(results, options);
  23357. }, reviver);
  23358. };
  23359. fabric.util.getScript = function(url, callback) {
  23360. request(url, '', function(body) {
  23361. // eslint-disable-next-line no-eval
  23362. eval(body);
  23363. callback && callback();
  23364. });
  23365. };
  23366. // fabric.util.createCanvasElement = function(_, width, height) {
  23367. // return new Canvas(width, height);
  23368. // }
  23369. /**
  23370. * Only available when running fabric on node.js
  23371. * @param {Number} width Canvas width
  23372. * @param {Number} height Canvas height
  23373. * @param {Object} [options] Options to pass to FabricCanvas.
  23374. * @param {Object} [nodeCanvasOptions] Options to pass to NodeCanvas.
  23375. * @return {Object} wrapped canvas instance
  23376. */
  23377. fabric.createCanvasForNode = function(width, height, options, nodeCanvasOptions) {
  23378. nodeCanvasOptions = nodeCanvasOptions || options;
  23379. var canvasEl = fabric.document.createElement('canvas'),
  23380. nodeCanvas = new Canvas(width || 600, height || 600, nodeCanvasOptions),
  23381. nodeCacheCanvas = new Canvas(width || 600, height || 600, nodeCanvasOptions);
  23382. // jsdom doesn't create style on canvas element, so here be temp. workaround
  23383. canvasEl.style = { };
  23384. canvasEl.width = nodeCanvas.width;
  23385. canvasEl.height = nodeCanvas.height;
  23386. options = options || { };
  23387. options.nodeCanvas = nodeCanvas;
  23388. options.nodeCacheCanvas = nodeCacheCanvas;
  23389. var FabricCanvas = fabric.Canvas || fabric.StaticCanvas,
  23390. fabricCanvas = new FabricCanvas(canvasEl, options);
  23391. fabricCanvas.nodeCanvas = nodeCanvas;
  23392. fabricCanvas.nodeCacheCanvas = nodeCacheCanvas;
  23393. fabricCanvas.contextContainer = nodeCanvas.getContext('2d');
  23394. fabricCanvas.contextCache = nodeCacheCanvas.getContext('2d');
  23395. fabricCanvas.Font = Canvas.Font;
  23396. return fabricCanvas;
  23397. };
  23398. var originaInitStatic = fabric.StaticCanvas.prototype._initStatic;
  23399. fabric.StaticCanvas.prototype._initStatic = function(el, options) {
  23400. el = el || fabric.document.createElement('canvas');
  23401. this.nodeCanvas = new Canvas(el.width, el.height);
  23402. this.nodeCacheCanvas = new Canvas(el.width, el.height);
  23403. originaInitStatic.call(this, el, options);
  23404. this.contextContainer = this.nodeCanvas.getContext('2d');
  23405. this.contextCache = this.nodeCacheCanvas.getContext('2d');
  23406. this.Font = Canvas.Font;
  23407. };
  23408. /** @ignore */
  23409. fabric.StaticCanvas.prototype.createPNGStream = function() {
  23410. return this.nodeCanvas.createPNGStream();
  23411. };
  23412. fabric.StaticCanvas.prototype.createJPEGStream = function(opts) {
  23413. return this.nodeCanvas.createJPEGStream(opts);
  23414. };
  23415. fabric.StaticCanvas.prototype._initRetinaScaling = function() {
  23416. if (!this._isRetinaScaling()) {
  23417. return;
  23418. }
  23419. this.lowerCanvasEl.setAttribute('width', this.width * fabric.devicePixelRatio);
  23420. this.lowerCanvasEl.setAttribute('height', this.height * fabric.devicePixelRatio);
  23421. this.nodeCanvas.width = this.width * fabric.devicePixelRatio;
  23422. this.nodeCanvas.height = this.height * fabric.devicePixelRatio;
  23423. this.contextContainer.scale(fabric.devicePixelRatio, fabric.devicePixelRatio);
  23424. return this;
  23425. };
  23426. if (fabric.Canvas) {
  23427. fabric.Canvas.prototype._initRetinaScaling = fabric.StaticCanvas.prototype._initRetinaScaling;
  23428. }
  23429. var origSetBackstoreDimension = fabric.StaticCanvas.prototype._setBackstoreDimension;
  23430. fabric.StaticCanvas.prototype._setBackstoreDimension = function(prop, value) {
  23431. origSetBackstoreDimension.call(this, prop, value);
  23432. this.nodeCanvas[prop] = value;
  23433. return this;
  23434. };
  23435. if (fabric.Canvas) {
  23436. fabric.Canvas.prototype._setBackstoreDimension = fabric.StaticCanvas.prototype._setBackstoreDimension;
  23437. }
  23438. })();