svgcanvas.js 249 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770
  1. /*
  2. * svgcanvas.js
  3. *
  4. * Licensed under the Apache License, Version 2
  5. *
  6. * Copyright(c) 2010 Alexis Deveria
  7. * Copyright(c) 2010 Pavol Rusnak
  8. * Copyright(c) 2010 Jeff Schiller
  9. *
  10. */
  11. // Dependencies:
  12. // 1) jQuery
  13. // 2) browser.js
  14. // 3) svgtransformlist.js
  15. // 4) math.js
  16. // 5) units.js
  17. // 6) svgutils.js
  18. // 7) sanitize.js
  19. // 8) history.js
  20. // 9) select.js
  21. // 10) draw.js
  22. // 11) path.js
  23. if(!window.console) {
  24. window.console = {};
  25. window.console.log = function(str) {};
  26. window.console.dir = function(str) {};
  27. }
  28. if(window.opera) {
  29. window.console.log = function(str) { opera.postError(str); };
  30. window.console.dir = function(str) {};
  31. }
  32. (function() {
  33. // This fixes $(...).attr() to work as expected with SVG elements.
  34. // Does not currently use *AttributeNS() since we rarely need that.
  35. // See http://api.jquery.com/attr/ for basic documentation of .attr()
  36. // Additional functionality:
  37. // - When getting attributes, a string that's a number is return as type number.
  38. // - If an array is supplied as first parameter, multiple values are returned
  39. // as an object with values for each given attributes
  40. var proxied = jQuery.fn.attr, svgns = "http://www.w3.org/2000/svg";
  41. jQuery.fn.attr = function(key, value) {
  42. var len = this.length;
  43. if(!len) return this;
  44. for(var i=0; i<len; i++) {
  45. var elem = this[i];
  46. // set/get SVG attribute
  47. if(elem.namespaceURI === svgns) {
  48. // Setting attribute
  49. if(value !== undefined) {
  50. elem.setAttribute(key, value);
  51. } else if($.isArray(key)) {
  52. // Getting attributes from array
  53. var j = key.length, obj = {};
  54. while(j--) {
  55. var aname = key[j];
  56. var attr = elem.getAttribute(aname);
  57. // This returns a number when appropriate
  58. if(attr || attr === "0") {
  59. attr = isNaN(attr)?attr:attr-0;
  60. }
  61. obj[aname] = attr;
  62. }
  63. return obj;
  64. } else if(typeof key === "object") {
  65. // Setting attributes form object
  66. for(var v in key) {
  67. elem.setAttribute(v, key[v]);
  68. }
  69. // Getting attribute
  70. } else {
  71. var attr = elem.getAttribute(key);
  72. if(attr || attr === "0") {
  73. attr = isNaN(attr)?attr:attr-0;
  74. }
  75. return attr;
  76. }
  77. } else {
  78. return proxied.apply(this, arguments);
  79. }
  80. }
  81. return this;
  82. };
  83. }());
  84. // Class: SvgCanvas
  85. // The main SvgCanvas class that manages all SVG-related functions
  86. //
  87. // Parameters:
  88. // container - The container HTML element that should hold the SVG root element
  89. // config - An object that contains configuration data
  90. $.SvgCanvas = function(container, config)
  91. {
  92. // Namespace constants
  93. var svgns = "http://www.w3.org/2000/svg",
  94. xlinkns = "http://www.w3.org/1999/xlink",
  95. xmlns = "http://www.w3.org/XML/1998/namespace",
  96. xmlnsns = "http://www.w3.org/2000/xmlns/", // see http://www.w3.org/TR/REC-xml-names/#xmlReserved
  97. se_ns = "http://svg-edit.googlecode.com",
  98. htmlns = "http://www.w3.org/1999/xhtml",
  99. mathns = "http://www.w3.org/1998/Math/MathML";
  100. // Default configuration options
  101. var curConfig = {
  102. show_outside_canvas: true,
  103. selectNew: true,
  104. dimensions: [640, 480]
  105. };
  106. // Update config with new one if given
  107. if(config) {
  108. $.extend(curConfig, config);
  109. }
  110. // Array with width/height of canvas
  111. var dimensions = curConfig.dimensions;
  112. var canvas = this;
  113. // "document" element associated with the container (same as window.document using default svg-editor.js)
  114. // NOTE: This is not actually a SVG document, but a HTML document.
  115. var svgdoc = container.ownerDocument;
  116. // This is a container for the document being edited, not the document itself.
  117. var svgroot = svgdoc.importNode(svgedit.utilities.text2xml(
  118. '<svg id="svgroot" xmlns="' + svgns + '" xlinkns="' + xlinkns + '" ' +
  119. 'width="' + dimensions[0] + '" height="' + dimensions[1] + '" x="' + dimensions[0] + '" y="' + dimensions[1] + '" overflow="visible">' +
  120. '<defs>' +
  121. '<filter id="canvashadow" filterUnits="objectBoundingBox">' +
  122. '<feGaussianBlur in="SourceAlpha" stdDeviation="4" result="blur"/>'+
  123. '<feOffset in="blur" dx="5" dy="5" result="offsetBlur"/>'+
  124. '<feMerge>'+
  125. '<feMergeNode in="offsetBlur"/>'+
  126. '<feMergeNode in="SourceGraphic"/>'+
  127. '</feMerge>'+
  128. '</filter>'+
  129. '</defs>'+
  130. '</svg>').documentElement, true);
  131. container.appendChild(svgroot);
  132. // The actual element that represents the final output SVG element
  133. var svgcontent = svgdoc.createElementNS(svgns, "svg");
  134. // This function resets the svgcontent element while keeping it in the DOM.
  135. var clearSvgContentElement = canvas.clearSvgContentElement = function() {
  136. while (svgcontent.firstChild) { svgcontent.removeChild(svgcontent.firstChild); }
  137. // TODO: Clear out all other attributes first?
  138. $(svgcontent).attr({
  139. id: 'svgcontent',
  140. width: dimensions[0],
  141. height: dimensions[1],
  142. x: dimensions[0],
  143. y: dimensions[1],
  144. overflow: curConfig.show_outside_canvas ? 'visible' : 'hidden',
  145. xmlns: svgns,
  146. "xmlns:se": se_ns,
  147. "xmlns:xlink": xlinkns
  148. }).appendTo(svgroot);
  149. // TODO: make this string optional and set by the client
  150. var comment = svgdoc.createComment(" Created with SVG-edit - http://svg-edit.googlecode.com/ ");
  151. svgcontent.appendChild(comment);
  152. };
  153. clearSvgContentElement();
  154. // Prefix string for element IDs
  155. var idprefix = "svg_";
  156. // Function: setIdPrefix
  157. // Changes the ID prefix to the given value
  158. //
  159. // Parameters:
  160. // p - String with the new prefix
  161. canvas.setIdPrefix = function(p) {
  162. idprefix = p;
  163. };
  164. // Current svgedit.draw.Drawing object
  165. // @type {svgedit.draw.Drawing}
  166. canvas.current_drawing_ = new svgedit.draw.Drawing(svgcontent, idprefix);
  167. // Function: getCurrentDrawing
  168. // Returns the current Drawing.
  169. // @return {svgedit.draw.Drawing}
  170. var getCurrentDrawing = canvas.getCurrentDrawing = function() {
  171. return canvas.current_drawing_;
  172. };
  173. // Float displaying the current zoom level (1 = 100%, .5 = 50%, etc)
  174. var current_zoom = 1;
  175. // pointer to current group (for in-group editing)
  176. var current_group = null;
  177. // Object containing data for the currently selected styles
  178. var all_properties = {
  179. shape: {
  180. fill: "#" + curConfig.initFill.color,
  181. fill_paint: null,
  182. fill_opacity: curConfig.initFill.opacity,
  183. stroke: "#" + curConfig.initStroke.color,
  184. stroke_paint: null,
  185. stroke_opacity: curConfig.initStroke.opacity,
  186. stroke_width: curConfig.initStroke.width,
  187. stroke_dasharray: 'none',
  188. stroke_linejoin: 'miter',
  189. stroke_linecap: 'butt',
  190. opacity: curConfig.initOpacity
  191. }
  192. };
  193. all_properties.text = $.extend(true, {}, all_properties.shape);
  194. $.extend(all_properties.text, {
  195. fill: "#000000",
  196. stroke_width: 0,
  197. font_size: 24,
  198. font_family: 'serif'
  199. });
  200. // Current shape style properties
  201. var cur_shape = all_properties.shape;
  202. // Array with all the currently selected elements
  203. // default size of 1 until it needs to grow bigger
  204. var selectedElements = new Array(1);
  205. // Function: addSvgElementFromJson
  206. // Create a new SVG element based on the given object keys/values and add it to the current layer
  207. // The element will be ran through cleanupElement before being returned
  208. //
  209. // Parameters:
  210. // data - Object with the following keys/values:
  211. // * element - tag name of the SVG element to create
  212. // * attr - Object with attributes key-values to assign to the new element
  213. // * curStyles - Boolean indicating that current style attributes should be applied first
  214. //
  215. // Returns: The new element
  216. var addSvgElementFromJson = this.addSvgElementFromJson = function(data) {
  217. var shape = svgedit.utilities.getElem(data.attr.id);
  218. // if shape is a path but we need to create a rect/ellipse, then remove the path
  219. var current_layer = getCurrentDrawing().getCurrentLayer();
  220. if (shape && data.element != shape.tagName) {
  221. current_layer.removeChild(shape);
  222. shape = null;
  223. }
  224. if (!shape) {
  225. shape = svgdoc.createElementNS(svgns, data.element);
  226. if (current_layer) {
  227. (current_group || current_layer).appendChild(shape);
  228. }
  229. }
  230. if(data.curStyles) {
  231. svgedit.utilities.assignAttributes(shape, {
  232. "fill": cur_shape.fill,
  233. "stroke": cur_shape.stroke,
  234. "stroke-width": cur_shape.stroke_width,
  235. "stroke-dasharray": cur_shape.stroke_dasharray,
  236. "stroke-linejoin": cur_shape.stroke_linejoin,
  237. "stroke-linecap": cur_shape.stroke_linecap,
  238. "stroke-opacity": cur_shape.stroke_opacity,
  239. "fill-opacity": cur_shape.fill_opacity,
  240. "opacity": cur_shape.opacity / 2,
  241. "style": "pointer-events:inherit"
  242. }, 100);
  243. }
  244. svgedit.utilities.assignAttributes(shape, data.attr, 100);
  245. svgedit.utilities.cleanupElement(shape);
  246. return shape;
  247. };
  248. // import svgtransformlist.js
  249. var getTransformList = canvas.getTransformList = svgedit.transformlist.getTransformList;
  250. // import from math.js.
  251. var transformPoint = svgedit.math.transformPoint;
  252. var matrixMultiply = canvas.matrixMultiply = svgedit.math.matrixMultiply;
  253. var hasMatrixTransform = canvas.hasMatrixTransform = svgedit.math.hasMatrixTransform;
  254. var transformListToTransform = canvas.transformListToTransform = svgedit.math.transformListToTransform;
  255. var snapToAngle = svgedit.math.snapToAngle;
  256. var getMatrix = svgedit.math.getMatrix;
  257. // initialize from units.js
  258. // send in an object implementing the ElementContainer interface (see units.js)
  259. svgedit.units.init({
  260. getBaseUnit: function() { return curConfig.baseUnit; },
  261. getElement: svgedit.utilities.getElem,
  262. getHeight: function() { return svgcontent.getAttribute("height")/current_zoom; },
  263. getWidth: function() { return svgcontent.getAttribute("width")/current_zoom; },
  264. getRoundDigits: function() { return save_options.round_digits; }
  265. });
  266. // import from units.js
  267. var convertToNum = canvas.convertToNum = svgedit.units.convertToNum;
  268. // import from svgutils.js
  269. svgedit.utilities.init({
  270. getDOMDocument: function() { return svgdoc; },
  271. getDOMContainer: function() { return container; },
  272. getSVGRoot: function() { return svgroot; },
  273. // TODO: replace this mostly with a way to get the current drawing.
  274. getSelectedElements: function() { return selectedElements; },
  275. getSVGContent: function() { return svgcontent; }
  276. });
  277. var getUrlFromAttr = canvas.getUrlFromAttr = svgedit.utilities.getUrlFromAttr;
  278. var getHref = canvas.getHref = svgedit.utilities.getHref;
  279. var setHref = canvas.setHref = svgedit.utilities.setHref;
  280. var getPathBBox = svgedit.utilities.getPathBBox;
  281. var getBBox = canvas.getBBox = svgedit.utilities.getBBox;
  282. var getRotationAngle = canvas.getRotationAngle = svgedit.utilities.getRotationAngle;
  283. var getElem = canvas.getElem = svgedit.utilities.getElem;
  284. var assignAttributes = canvas.assignAttributes = svgedit.utilities.assignAttributes;
  285. var cleanupElement = this.cleanupElement = svgedit.utilities.cleanupElement;
  286. // import from sanitize.js
  287. var nsMap = svgedit.sanitize.getNSMap();
  288. var sanitizeSvg = canvas.sanitizeSvg = svgedit.sanitize.sanitizeSvg;
  289. // import from history.js
  290. var MoveElementCommand = svgedit.history.MoveElementCommand;
  291. var InsertElementCommand = svgedit.history.InsertElementCommand;
  292. var RemoveElementCommand = svgedit.history.RemoveElementCommand;
  293. var ChangeElementCommand = svgedit.history.ChangeElementCommand;
  294. var BatchCommand = svgedit.history.BatchCommand;
  295. // Implement the svgedit.history.HistoryEventHandler interface.
  296. canvas.undoMgr = new svgedit.history.UndoManager({
  297. handleHistoryEvent: function(eventType, cmd) {
  298. var EventTypes = svgedit.history.HistoryEventTypes;
  299. // TODO: handle setBlurOffsets.
  300. if (eventType == EventTypes.BEFORE_UNAPPLY || eventType == EventTypes.BEFORE_APPLY) {
  301. canvas.clearSelection();
  302. } else if (eventType == EventTypes.AFTER_APPLY || eventType == EventTypes.AFTER_UNAPPLY) {
  303. var elems = cmd.elements();
  304. canvas.pathActions.clear();
  305. call("changed", elems);
  306. var cmdType = cmd.type();
  307. var isApply = (eventType == EventTypes.AFTER_APPLY);
  308. if (cmdType == MoveElementCommand.type()) {
  309. var parent = isApply ? cmd.newParent : cmd.oldParent;
  310. if (parent == svgcontent) {
  311. canvas.identifyLayers();
  312. }
  313. } else if (cmdType == InsertElementCommand.type() ||
  314. cmdType == RemoveElementCommand.type()) {
  315. if (cmd.parent == svgcontent) {
  316. canvas.identifyLayers();
  317. }
  318. if (cmdType == InsertElementCommand.type()) {
  319. if (isApply) restoreRefElems(cmd.elem);
  320. } else {
  321. if (!isApply) restoreRefElems(cmd.elem);
  322. }
  323. if(cmd.elem.tagName === 'use') {
  324. setUseData(cmd.elem);
  325. }
  326. } else if (cmdType == ChangeElementCommand.type()) {
  327. // if we are changing layer names, re-identify all layers
  328. if (cmd.elem.tagName == "title" && cmd.elem.parentNode.parentNode == svgcontent) {
  329. canvas.identifyLayers();
  330. }
  331. var values = isApply ? cmd.newValues : cmd.oldValues;
  332. // If stdDeviation was changed, update the blur.
  333. if (values["stdDeviation"]) {
  334. canvas.setBlurOffsets(cmd.elem.parentNode, values["stdDeviation"]);
  335. }
  336. // Remove & Re-add hack for Webkit (issue 775)
  337. if(cmd.elem.tagName === 'use' && svgedit.browser.isWebkit()) {
  338. var elem = cmd.elem;
  339. if(!elem.getAttribute('x') && !elem.getAttribute('y')) {
  340. var parent = elem.parentNode;
  341. var sib = elem.nextSibling;
  342. parent.removeChild(elem);
  343. parent.insertBefore(elem, sib);
  344. }
  345. }
  346. }
  347. }
  348. }
  349. });
  350. var addCommandToHistory = function(cmd) {
  351. canvas.undoMgr.addCommandToHistory(cmd);
  352. };
  353. // import from select.js
  354. svgedit.select.init(curConfig, {
  355. createSVGElement: function(jsonMap) { return canvas.addSvgElementFromJson(jsonMap); },
  356. svgRoot: function() { return svgroot; },
  357. svgContent: function() { return svgcontent; },
  358. currentZoom: function() { return current_zoom; },
  359. // TODO(codedread): Remove when getStrokedBBox() has been put into svgutils.js.
  360. getStrokedBBox: function(elems) { return canvas.getStrokedBBox([elems]); }
  361. });
  362. // this object manages selectors for us
  363. var selectorManager = this.selectorManager = svgedit.select.getSelectorManager();
  364. // Import from path.js
  365. svgedit.path.init({
  366. getCurrentZoom: function() { return current_zoom; },
  367. getSVGRoot: function() { return svgroot; }
  368. });
  369. // Function: snapToGrid
  370. // round value to for snapping
  371. // NOTE: This function did not move to svgutils.js since it depends on curConfig.
  372. svgedit.utilities.snapToGrid = function(value){
  373. var stepSize = curConfig.snappingStep;
  374. var unit = curConfig.baseUnit;
  375. if(unit !== "px") {
  376. stepSize *= svgedit.units.getTypeMap()[unit];
  377. }
  378. value = Math.round(value/stepSize)*stepSize;
  379. return value;
  380. };
  381. var snapToGrid = svgedit.utilities.snapToGrid;
  382. // Interface strings, usually for title elements
  383. var uiStrings = {
  384. "exportNoBlur": "Blurred elements will appear as un-blurred",
  385. "exportNoforeignObject": "foreignObject elements will not appear",
  386. "exportNoDashArray": "Strokes will appear filled",
  387. "exportNoText": "Text may not appear as expected"
  388. };
  389. var visElems = 'a,circle,ellipse,foreignObject,g,image,line,path,polygon,polyline,rect,svg,text,tspan,use';
  390. var ref_attrs = ["clip-path", "fill", "filter", "marker-end", "marker-mid", "marker-start", "mask", "stroke"];
  391. var elData = $.data;
  392. // Animation element to change the opacity of any newly created element
  393. var opac_ani = document.createElementNS(svgns, 'animate');
  394. $(opac_ani).attr({
  395. attributeName: 'opacity',
  396. begin: 'indefinite',
  397. dur: 1,
  398. fill: 'freeze'
  399. }).appendTo(svgroot);
  400. var restoreRefElems = function(elem) {
  401. // Look for missing reference elements, restore any found
  402. var attrs = $(elem).attr(ref_attrs);
  403. for(var o in attrs) {
  404. var val = attrs[o];
  405. if (val && val.indexOf('url(') === 0) {
  406. var id = getUrlFromAttr(val).substr(1);
  407. var ref = getElem(id);
  408. if(!ref) {
  409. findDefs().appendChild(removedElements[id]);
  410. delete removedElements[id];
  411. }
  412. }
  413. }
  414. var childs = elem.getElementsByTagName('*');
  415. if(childs.length) {
  416. for(var i = 0, l = childs.length; i < l; i++) {
  417. restoreRefElems(childs[i]);
  418. }
  419. }
  420. };
  421. (function() {
  422. // TODO For Issue 208: this is a start on a thumbnail
  423. // var svgthumb = svgdoc.createElementNS(svgns, "use");
  424. // svgthumb.setAttribute('width', '100');
  425. // svgthumb.setAttribute('height', '100');
  426. // svgedit.utilities.setHref(svgthumb, '#svgcontent');
  427. // svgroot.appendChild(svgthumb);
  428. })();
  429. // Object to contain image data for raster images that were found encodable
  430. var encodableImages = {},
  431. // String with image URL of last loadable image
  432. last_good_img_url = curConfig.imgPath + 'logo.png',
  433. // Array with current disabled elements (for in-group editing)
  434. disabled_elems = [],
  435. // Object with save options
  436. save_options = {round_digits: 5},
  437. // Boolean indicating whether or not a draw action has been started
  438. started = false,
  439. // String with an element's initial transform attribute value
  440. start_transform = null,
  441. // String indicating the current editor mode
  442. current_mode = "select",
  443. // String with the current direction in which an element is being resized
  444. current_resize_mode = "none",
  445. // Object with IDs for imported files, to see if one was already added
  446. import_ids = {};
  447. // Current text style properties
  448. var cur_text = all_properties.text,
  449. // Current general properties
  450. cur_properties = cur_shape,
  451. // Array with selected elements' Bounding box object
  452. // selectedBBoxes = new Array(1),
  453. // The DOM element that was just selected
  454. justSelected = null,
  455. // DOM element for selection rectangle drawn by the user
  456. rubberBox = null,
  457. // Array of current BBoxes (still needed?)
  458. curBBoxes = [],
  459. // Object to contain all included extensions
  460. extensions = {},
  461. // Canvas point for the most recent right click
  462. lastClickPoint = null,
  463. // Map of deleted reference elements
  464. removedElements = {}
  465. // Clipboard for cut, copy&pasted elements
  466. canvas.clipBoard = [];
  467. // Should this return an array by default, so extension results aren't overwritten?
  468. var runExtensions = this.runExtensions = function(action, vars, returnArray) {
  469. var result = false;
  470. if(returnArray) result = [];
  471. $.each(extensions, function(name, opts) {
  472. if(action in opts) {
  473. if(returnArray) {
  474. result.push(opts[action](vars))
  475. } else {
  476. result = opts[action](vars);
  477. }
  478. }
  479. });
  480. return result;
  481. }
  482. // Function: addExtension
  483. // Add an extension to the editor
  484. //
  485. // Parameters:
  486. // name - String with the ID of the extension
  487. // ext_func - Function supplied by the extension with its data
  488. this.addExtension = function(name, ext_func) {
  489. if(!(name in extensions)) {
  490. // Provide private vars/funcs here. Is there a better way to do this?
  491. if($.isFunction(ext_func)) {
  492. var ext = ext_func($.extend(canvas.getPrivateMethods(), {
  493. svgroot: svgroot,
  494. svgcontent: svgcontent,
  495. nonce: getCurrentDrawing().getNonce(),
  496. selectorManager: selectorManager
  497. }));
  498. } else {
  499. var ext = ext_func;
  500. }
  501. extensions[name] = ext;
  502. call("extension_added", ext);
  503. } else {
  504. console.log('Cannot add extension "' + name + '", an extension by that name already exists"');
  505. }
  506. };
  507. // This method rounds the incoming value to the nearest value based on the current_zoom
  508. var round = this.round = function(val) {
  509. return parseInt(val*current_zoom)/current_zoom;
  510. };
  511. // This method sends back an array or a NodeList full of elements that
  512. // intersect the multi-select rubber-band-box on the current_layer only.
  513. //
  514. // Since the only browser that supports the SVG DOM getIntersectionList is Opera,
  515. // we need to provide an implementation here. We brute-force it for now.
  516. //
  517. // Reference:
  518. // Firefox does not implement getIntersectionList(), see https://bugzilla.mozilla.org/show_bug.cgi?id=501421
  519. // Webkit does not implement getIntersectionList(), see https://bugs.webkit.org/show_bug.cgi?id=11274
  520. var getIntersectionList = this.getIntersectionList = function(rect) {
  521. if (rubberBox == null) { return null; }
  522. var parent = current_group || getCurrentDrawing().getCurrentLayer();
  523. if(!curBBoxes.length) {
  524. // Cache all bboxes
  525. curBBoxes = getVisibleElementsAndBBoxes(parent);
  526. }
  527. var resultList = null;
  528. try {
  529. resultList = parent.getIntersectionList(rect, null);
  530. } catch(e) { }
  531. if (resultList == null || typeof(resultList.item) != "function") {
  532. resultList = [];
  533. if(!rect) {
  534. var rubberBBox = rubberBox.getBBox();
  535. var bb = {};
  536. for(var o in rubberBBox) {
  537. bb[o] = rubberBBox[o] / current_zoom;
  538. }
  539. rubberBBox = bb;
  540. } else {
  541. var rubberBBox = rect;
  542. }
  543. var i = curBBoxes.length;
  544. while (i--) {
  545. if(!rubberBBox.width || !rubberBBox.width) continue;
  546. if (svgedit.math.rectsIntersect(rubberBBox, curBBoxes[i].bbox)) {
  547. resultList.push(curBBoxes[i].elem);
  548. }
  549. }
  550. }
  551. // addToSelection expects an array, but it's ok to pass a NodeList
  552. // because using square-bracket notation is allowed:
  553. // http://www.w3.org/TR/DOM-Level-2-Core/ecma-script-binding.html
  554. return resultList;
  555. };
  556. // TODO(codedread): Migrate this into svgutils.js
  557. // Function: getStrokedBBox
  558. // Get the bounding box for one or more stroked and/or transformed elements
  559. //
  560. // Parameters:
  561. // elems - Array with DOM elements to check
  562. //
  563. // Returns:
  564. // A single bounding box object
  565. getStrokedBBox = this.getStrokedBBox = function(elems) {
  566. if(!elems) elems = getVisibleElements();
  567. if(!elems.length) return false;
  568. // Make sure the expected BBox is returned if the element is a group
  569. var getCheckedBBox = function(elem) {
  570. try {
  571. // TODO: Fix issue with rotated groups. Currently they work
  572. // fine in FF, but not in other browsers (same problem mentioned
  573. // in Issue 339 comment #2).
  574. var bb = svgedit.utilities.getBBox(elem);
  575. var angle = svgedit.utilities.getRotationAngle(elem);
  576. if ((angle && angle % 90) ||
  577. svgedit.math.hasMatrixTransform(svgedit.transformlist.getTransformList(elem))) {
  578. // Accurate way to get BBox of rotated element in Firefox:
  579. // Put element in group and get its BBox
  580. var good_bb = false;
  581. // Get the BBox from the raw path for these elements
  582. var elemNames = ['ellipse','path','line','polyline','polygon'];
  583. if(elemNames.indexOf(elem.tagName) >= 0) {
  584. bb = good_bb = canvas.convertToPath(elem, true);
  585. } else if(elem.tagName == 'rect') {
  586. // Look for radius
  587. var rx = elem.getAttribute('rx');
  588. var ry = elem.getAttribute('ry');
  589. if(rx || ry) {
  590. bb = good_bb = canvas.convertToPath(elem, true);
  591. }
  592. }
  593. if(!good_bb) {
  594. // Must use clone else FF freaks out
  595. var clone = elem.cloneNode(true);
  596. var g = document.createElementNS(svgns, "g");
  597. var parent = elem.parentNode;
  598. parent.appendChild(g);
  599. g.appendChild(clone);
  600. bb = svgedit.utilities.bboxToObj(g.getBBox());
  601. parent.removeChild(g);
  602. }
  603. // Old method: Works by giving the rotated BBox,
  604. // this is (unfortunately) what Opera and Safari do
  605. // natively when getting the BBox of the parent group
  606. // var angle = angle * Math.PI / 180.0;
  607. // var rminx = Number.MAX_VALUE, rminy = Number.MAX_VALUE,
  608. // rmaxx = Number.MIN_VALUE, rmaxy = Number.MIN_VALUE;
  609. // var cx = round(bb.x + bb.width/2),
  610. // cy = round(bb.y + bb.height/2);
  611. // var pts = [ [bb.x - cx, bb.y - cy],
  612. // [bb.x + bb.width - cx, bb.y - cy],
  613. // [bb.x + bb.width - cx, bb.y + bb.height - cy],
  614. // [bb.x - cx, bb.y + bb.height - cy] ];
  615. // var j = 4;
  616. // while (j--) {
  617. // var x = pts[j][0],
  618. // y = pts[j][1],
  619. // r = Math.sqrt( x*x + y*y );
  620. // var theta = Math.atan2(y,x) + angle;
  621. // x = round(r * Math.cos(theta) + cx);
  622. // y = round(r * Math.sin(theta) + cy);
  623. //
  624. // // now set the bbox for the shape after it's been rotated
  625. // if (x < rminx) rminx = x;
  626. // if (y < rminy) rminy = y;
  627. // if (x > rmaxx) rmaxx = x;
  628. // if (y > rmaxy) rmaxy = y;
  629. // }
  630. //
  631. // bb.x = rminx;
  632. // bb.y = rminy;
  633. // bb.width = rmaxx - rminx;
  634. // bb.height = rmaxy - rminy;
  635. }
  636. return bb;
  637. } catch(e) {
  638. console.log(elem, e);
  639. return null;
  640. }
  641. };
  642. var full_bb;
  643. $.each(elems, function() {
  644. if(full_bb) return;
  645. if(!this.parentNode) return;
  646. full_bb = getCheckedBBox(this);
  647. });
  648. // This shouldn't ever happen...
  649. if(full_bb == null) return null;
  650. // full_bb doesn't include the stoke, so this does no good!
  651. // if(elems.length == 1) return full_bb;
  652. var max_x = full_bb.x + full_bb.width;
  653. var max_y = full_bb.y + full_bb.height;
  654. var min_x = full_bb.x;
  655. var min_y = full_bb.y;
  656. // FIXME: same re-creation problem with this function as getCheckedBBox() above
  657. var getOffset = function(elem) {
  658. var sw = elem.getAttribute("stroke-width");
  659. var offset = 0;
  660. if (elem.getAttribute("stroke") != "none" && !isNaN(sw)) {
  661. offset += sw/2;
  662. }
  663. return offset;
  664. }
  665. var bboxes = [];
  666. $.each(elems, function(i, elem) {
  667. var cur_bb = getCheckedBBox(elem);
  668. if(cur_bb) {
  669. var offset = getOffset(elem);
  670. min_x = Math.min(min_x, cur_bb.x - offset);
  671. min_y = Math.min(min_y, cur_bb.y - offset);
  672. bboxes.push(cur_bb);
  673. }
  674. });
  675. full_bb.x = min_x;
  676. full_bb.y = min_y;
  677. $.each(elems, function(i, elem) {
  678. var cur_bb = bboxes[i];
  679. // ensure that elem is really an element node
  680. if (cur_bb && elem.nodeType == 1) {
  681. var offset = getOffset(elem);
  682. max_x = Math.max(max_x, cur_bb.x + cur_bb.width + offset);
  683. max_y = Math.max(max_y, cur_bb.y + cur_bb.height + offset);
  684. }
  685. });
  686. full_bb.width = max_x - min_x;
  687. full_bb.height = max_y - min_y;
  688. return full_bb;
  689. }
  690. // Function: getVisibleElements
  691. // Get all elements that have a BBox (excludes <defs>, <title>, etc).
  692. // Note that 0-opacity, off-screen etc elements are still considered "visible"
  693. // for this function
  694. //
  695. // Parameters:
  696. // parent - The parent DOM element to search within
  697. //
  698. // Returns:
  699. // An array with all "visible" elements.
  700. var getVisibleElements = this.getVisibleElements = function(parent) {
  701. if(!parent) parent = $(svgcontent).children(); // Prevent layers from being included
  702. var contentElems = [];
  703. $(parent).children().each(function(i, elem) {
  704. try {
  705. if (elem.getBBox()) {
  706. contentElems.push(elem);
  707. }
  708. } catch(e) {}
  709. });
  710. return contentElems.reverse();
  711. };
  712. // Function: getVisibleElementsAndBBoxes
  713. // Get all elements that have a BBox (excludes <defs>, <title>, etc).
  714. // Note that 0-opacity, off-screen etc elements are still considered "visible"
  715. // for this function
  716. //
  717. // Parameters:
  718. // parent - The parent DOM element to search within
  719. //
  720. // Returns:
  721. // An array with objects that include:
  722. // * elem - The element
  723. // * bbox - The element's BBox as retrieved from getStrokedBBox
  724. var getVisibleElementsAndBBoxes = this.getVisibleElementsAndBBoxes = function(parent) {
  725. if(!parent) parent = $(svgcontent).children(); // Prevent layers from being included
  726. var contentElems = [];
  727. $(parent).children().each(function(i, elem) {
  728. try {
  729. if (elem.getBBox()) {
  730. contentElems.push({'elem':elem, 'bbox':getStrokedBBox([elem])});
  731. }
  732. } catch(e) {}
  733. });
  734. return contentElems.reverse();
  735. };
  736. // Function: groupSvgElem
  737. // Wrap an SVG element into a group element, mark the group as 'gsvg'
  738. //
  739. // Parameters:
  740. // elem - SVG element to wrap
  741. var groupSvgElem = this.groupSvgElem = function(elem) {
  742. var g = document.createElementNS(svgns, "g");
  743. elem.parentNode.replaceChild(g, elem);
  744. $(g).append(elem).data('gsvg', elem)[0].id = getNextId();
  745. }
  746. // Function: copyElem
  747. // Create a clone of an element, updating its ID and its children's IDs when needed
  748. //
  749. // Parameters:
  750. // el - DOM element to clone
  751. //
  752. // Returns: The cloned element
  753. var copyElem = function(el) {
  754. // manually create a copy of the element
  755. var new_el = document.createElementNS(el.namespaceURI, el.nodeName);
  756. $.each(el.attributes, function(i, attr) {
  757. if (attr.localName != '-moz-math-font-style') {
  758. new_el.setAttributeNS(attr.namespaceURI, attr.nodeName, attr.nodeValue);
  759. }
  760. });
  761. // set the copied element's new id
  762. new_el.removeAttribute("id");
  763. new_el.id = getNextId();
  764. // Opera's "d" value needs to be reset for Opera/Win/non-EN
  765. // Also needed for webkit (else does not keep curved segments on clone)
  766. if(svgedit.browser.isWebkit() && el.nodeName == 'path') {
  767. var fixed_d = pathActions.convertPath(el);
  768. new_el.setAttribute('d', fixed_d);
  769. }
  770. // now create copies of all children
  771. $.each(el.childNodes, function(i, child) {
  772. switch(child.nodeType) {
  773. case 1: // element node
  774. new_el.appendChild(copyElem(child));
  775. break;
  776. case 3: // text node
  777. new_el.textContent = child.nodeValue;
  778. break;
  779. default:
  780. break;
  781. }
  782. });
  783. if($(el).data('gsvg')) {
  784. $(new_el).data('gsvg', new_el.firstChild);
  785. } else if($(el).data('symbol')) {
  786. var ref = $(el).data('symbol');
  787. $(new_el).data('ref', ref).data('symbol', ref);
  788. }
  789. else if(new_el.tagName == 'image') {
  790. preventClickDefault(new_el);
  791. }
  792. return new_el;
  793. };
  794. // Set scope for these functions
  795. var getId, getNextId, call;
  796. (function(c) {
  797. // Object to contain editor event names and callback functions
  798. var events = {};
  799. getId = c.getId = function() { return getCurrentDrawing().getId(); };
  800. getNextId = c.getNextId = function() { return getCurrentDrawing().getNextId(); };
  801. // Function: call
  802. // Run the callback function associated with the given event
  803. //
  804. // Parameters:
  805. // event - String with the event name
  806. // arg - Argument to pass through to the callback function
  807. call = c.call = function(event, arg) {
  808. if (events[event]) {
  809. return events[event](this, arg);
  810. }
  811. };
  812. // Function: bind
  813. // Attaches a callback function to an event
  814. //
  815. // Parameters:
  816. // event - String indicating the name of the event
  817. // f - The callback function to bind to the event
  818. //
  819. // Return:
  820. // The previous event
  821. c.bind = function(event, f) {
  822. var old = events[event];
  823. events[event] = f;
  824. return old;
  825. };
  826. }(canvas));
  827. // Function: canvas.prepareSvg
  828. // Runs the SVG Document through the sanitizer and then updates its paths.
  829. //
  830. // Parameters:
  831. // newDoc - The SVG DOM document
  832. this.prepareSvg = function(newDoc) {
  833. this.sanitizeSvg(newDoc.documentElement);
  834. // convert paths into absolute commands
  835. var paths = newDoc.getElementsByTagNameNS(svgns, "path");
  836. for (var i = 0, len = paths.length; i < len; ++i) {
  837. var path = paths[i];
  838. path.setAttribute('d', pathActions.convertPath(path));
  839. pathActions.fixEnd(path);
  840. }
  841. };
  842. // Function getRefElem
  843. // Get the reference element associated with the given attribute value
  844. //
  845. // Parameters:
  846. // attrVal - The attribute value as a string
  847. var getRefElem = this.getRefElem = function(attrVal) {
  848. return getElem(getUrlFromAttr(attrVal).substr(1));
  849. }
  850. // Function: ffClone
  851. // Hack for Firefox bugs where text element features aren't updated or get
  852. // messed up. See issue 136 and issue 137.
  853. // This function clones the element and re-selects it
  854. // TODO: Test for this bug on load and add it to "support" object instead of
  855. // browser sniffing
  856. //
  857. // Parameters:
  858. // elem - The (text) DOM element to clone
  859. var ffClone = function(elem) {
  860. if(!svgedit.browser.isGecko()) return elem;
  861. var clone = elem.cloneNode(true)
  862. elem.parentNode.insertBefore(clone, elem);
  863. elem.parentNode.removeChild(elem);
  864. selectorManager.releaseSelector(elem);
  865. selectedElements[0] = clone;
  866. selectorManager.requestSelector(clone).showGrips(true);
  867. return clone;
  868. }
  869. // this.each is deprecated, if any extension used this it can be recreated by doing this:
  870. // $(canvas.getRootElem()).children().each(...)
  871. // this.each = function(cb) {
  872. // $(svgroot).children().each(cb);
  873. // };
  874. // Function: setRotationAngle
  875. // Removes any old rotations if present, prepends a new rotation at the
  876. // transformed center
  877. //
  878. // Parameters:
  879. // val - The new rotation angle in degrees
  880. // preventUndo - Boolean indicating whether the action should be undoable or not
  881. this.setRotationAngle = function(val, preventUndo) {
  882. // ensure val is the proper type
  883. val = parseFloat(val);
  884. var elem = selectedElements[0];
  885. var oldTransform = elem.getAttribute("transform");
  886. var bbox = svgedit.utilities.getBBox(elem);
  887. var cx = bbox.x+bbox.width/2, cy = bbox.y+bbox.height/2;
  888. var tlist = getTransformList(elem);
  889. // only remove the real rotational transform if present (i.e. at index=0)
  890. if (tlist.numberOfItems > 0) {
  891. var xform = tlist.getItem(0);
  892. if (xform.type == 4) {
  893. tlist.removeItem(0);
  894. }
  895. }
  896. // find R_nc and insert it
  897. if (val != 0) {
  898. var center = transformPoint(cx,cy,transformListToTransform(tlist).matrix);
  899. var R_nc = svgroot.createSVGTransform();
  900. R_nc.setRotate(val, center.x, center.y);
  901. if(tlist.numberOfItems) {
  902. tlist.insertItemBefore(R_nc, 0);
  903. } else {
  904. tlist.appendItem(R_nc);
  905. }
  906. }
  907. else if (tlist.numberOfItems == 0) {
  908. elem.removeAttribute("transform");
  909. }
  910. if (!preventUndo) {
  911. // we need to undo it, then redo it so it can be undo-able! :)
  912. // TODO: figure out how to make changes to transform list undo-able cross-browser?
  913. var newTransform = elem.getAttribute("transform");
  914. elem.setAttribute("transform", oldTransform);
  915. changeSelectedAttribute("transform",newTransform,selectedElements);
  916. call("changed", selectedElements);
  917. }
  918. var pointGripContainer = getElem("pathpointgrip_container");
  919. // if(elem.nodeName == "path" && pointGripContainer) {
  920. // pathActions.setPointContainerTransform(elem.getAttribute("transform"));
  921. // }
  922. var selector = selectorManager.requestSelector(selectedElements[0]);
  923. selector.resize();
  924. selector.updateGripCursors(val);
  925. };
  926. // Function: recalculateAllSelectedDimensions
  927. // Runs recalculateDimensions on the selected elements,
  928. // adding the changes to a single batch command
  929. var recalculateAllSelectedDimensions = this.recalculateAllSelectedDimensions = function() {
  930. var text = (current_resize_mode == "none" ? "position" : "size");
  931. var batchCmd = new BatchCommand(text);
  932. var i = selectedElements.length;
  933. while(i--) {
  934. var elem = selectedElements[i];
  935. // if(getRotationAngle(elem) && !hasMatrixTransform(getTransformList(elem))) continue;
  936. var cmd = recalculateDimensions(elem);
  937. if (cmd) {
  938. batchCmd.addSubCommand(cmd);
  939. }
  940. }
  941. if (!batchCmd.isEmpty()) {
  942. addCommandToHistory(batchCmd);
  943. call("changed", selectedElements);
  944. }
  945. };
  946. // this is how we map paths to our preferred relative segment types
  947. var pathMap = [0, 'z', 'M', 'm', 'L', 'l', 'C', 'c', 'Q', 'q', 'A', 'a',
  948. 'H', 'h', 'V', 'v', 'S', 's', 'T', 't'];
  949. // Debug tool to easily see the current matrix in the browser's console
  950. var logMatrix = function(m) {
  951. console.log([m.a,m.b,m.c,m.d,m.e,m.f]);
  952. };
  953. // Function: remapElement
  954. // Applies coordinate changes to an element based on the given matrix
  955. //
  956. // Parameters:
  957. // selected - DOM element to be changed
  958. // changes - Object with changes to be remapped
  959. // m - Matrix object to use for remapping coordinates
  960. var remapElement = this.remapElement = function(selected,changes,m) {
  961. var remap = function(x,y) { return transformPoint(x,y,m); },
  962. scalew = function(w) { return m.a*w; },
  963. scaleh = function(h) { return m.d*h; },
  964. doSnapping = curConfig.gridSnapping && selected.parentNode.parentNode.localName === "svg",
  965. finishUp = function() {
  966. if(doSnapping) for(var o in changes) changes[o] = snapToGrid(changes[o]);
  967. assignAttributes(selected, changes, 1000, true);
  968. }
  969. box = svgedit.utilities.getBBox(selected);
  970. for(var i = 0; i < 2; i++) {
  971. var type = i === 0 ? 'fill' : 'stroke';
  972. var attrVal = selected.getAttribute(type);
  973. if(attrVal && attrVal.indexOf('url(') === 0) {
  974. if(m.a < 0 || m.d < 0) {
  975. var grad = getRefElem(attrVal);
  976. var newgrad = grad.cloneNode(true);
  977. if(m.a < 0) {
  978. //flip x
  979. var x1 = newgrad.getAttribute('x1');
  980. var x2 = newgrad.getAttribute('x2');
  981. newgrad.setAttribute('x1', -(x1 - 1));
  982. newgrad.setAttribute('x2', -(x2 - 1));
  983. }
  984. if(m.d < 0) {
  985. //flip y
  986. var y1 = newgrad.getAttribute('y1');
  987. var y2 = newgrad.getAttribute('y2');
  988. newgrad.setAttribute('y1', -(y1 - 1));
  989. newgrad.setAttribute('y2', -(y2 - 1));
  990. }
  991. newgrad.id = getNextId();
  992. findDefs().appendChild(newgrad);
  993. selected.setAttribute(type, 'url(#' + newgrad.id + ')');
  994. }
  995. // Not really working :(
  996. // if(selected.tagName === 'path') {
  997. // reorientGrads(selected, m);
  998. // }
  999. }
  1000. }
  1001. var elName = selected.tagName;
  1002. if(elName === "g" || elName === "text" || elName === "use") {
  1003. // if it was a translate, then just update x,y
  1004. if (m.a == 1 && m.b == 0 && m.c == 0 && m.d == 1 &&
  1005. (m.e != 0 || m.f != 0) )
  1006. {
  1007. // [T][M] = [M][T']
  1008. // therefore [T'] = [M_inv][T][M]
  1009. var existing = transformListToTransform(selected).matrix,
  1010. t_new = matrixMultiply(existing.inverse(), m, existing);
  1011. changes.x = parseFloat(changes.x) + t_new.e;
  1012. changes.y = parseFloat(changes.y) + t_new.f;
  1013. }
  1014. else {
  1015. // we just absorb all matrices into the element and don't do any remapping
  1016. var chlist = getTransformList(selected);
  1017. var mt = svgroot.createSVGTransform();
  1018. mt.setMatrix(matrixMultiply(transformListToTransform(chlist).matrix,m));
  1019. chlist.clear();
  1020. chlist.appendItem(mt);
  1021. }
  1022. }
  1023. // now we have a set of changes and an applied reduced transform list
  1024. // we apply the changes directly to the DOM
  1025. switch (elName)
  1026. {
  1027. case "foreignObject":
  1028. case "rect":
  1029. case "image":
  1030. // Allow images to be inverted (give them matrix when flipped)
  1031. if(elName === 'image' && (m.a < 0 || m.d < 0)) {
  1032. // Convert to matrix
  1033. var chlist = getTransformList(selected);
  1034. var mt = svgroot.createSVGTransform();
  1035. mt.setMatrix(matrixMultiply(transformListToTransform(chlist).matrix,m));
  1036. chlist.clear();
  1037. chlist.appendItem(mt);
  1038. } else {
  1039. var pt1 = remap(changes.x,changes.y);
  1040. changes.width = scalew(changes.width);
  1041. changes.height = scaleh(changes.height);
  1042. changes.x = pt1.x + Math.min(0,changes.width);
  1043. changes.y = pt1.y + Math.min(0,changes.height);
  1044. changes.width = Math.abs(changes.width);
  1045. changes.height = Math.abs(changes.height);
  1046. }
  1047. finishUp();
  1048. break;
  1049. case "ellipse":
  1050. var c = remap(changes.cx,changes.cy);
  1051. changes.cx = c.x;
  1052. changes.cy = c.y;
  1053. changes.rx = scalew(changes.rx);
  1054. changes.ry = scaleh(changes.ry);
  1055. changes.rx = Math.abs(changes.rx);
  1056. changes.ry = Math.abs(changes.ry);
  1057. finishUp();
  1058. break;
  1059. case "circle":
  1060. var c = remap(changes.cx,changes.cy);
  1061. changes.cx = c.x;
  1062. changes.cy = c.y;
  1063. // take the minimum of the new selected box's dimensions for the new circle radius
  1064. var tbox = svgedit.math.transformBox(box.x, box.y, box.width, box.height, m);
  1065. var w = tbox.tr.x - tbox.tl.x, h = tbox.bl.y - tbox.tl.y;
  1066. changes.r = Math.min(w/2, h/2);
  1067. if(changes.r) changes.r = Math.abs(changes.r);
  1068. finishUp();
  1069. break;
  1070. case "line":
  1071. var pt1 = remap(changes.x1,changes.y1),
  1072. pt2 = remap(changes.x2,changes.y2);
  1073. changes.x1 = pt1.x;
  1074. changes.y1 = pt1.y;
  1075. changes.x2 = pt2.x;
  1076. changes.y2 = pt2.y;
  1077. case "text":
  1078. case "use":
  1079. finishUp();
  1080. break;
  1081. case "g":
  1082. var gsvg = $(selected).data('gsvg');
  1083. if(gsvg) {
  1084. assignAttributes(gsvg, changes, 1000, true);
  1085. }
  1086. break;
  1087. case "polyline":
  1088. case "polygon":
  1089. var len = changes.points.length;
  1090. for (var i = 0; i < len; ++i) {
  1091. var pt = changes.points[i];
  1092. pt = remap(pt.x,pt.y);
  1093. changes.points[i].x = pt.x;
  1094. changes.points[i].y = pt.y;
  1095. }
  1096. var len = changes.points.length;
  1097. var pstr = "";
  1098. for (var i = 0; i < len; ++i) {
  1099. var pt = changes.points[i];
  1100. pstr += pt.x + "," + pt.y + " ";
  1101. }
  1102. selected.setAttribute("points", pstr);
  1103. break;
  1104. case "path":
  1105. var segList = selected.pathSegList;
  1106. var len = segList.numberOfItems;
  1107. changes.d = new Array(len);
  1108. for (var i = 0; i < len; ++i) {
  1109. var seg = segList.getItem(i);
  1110. changes.d[i] = {
  1111. type: seg.pathSegType,
  1112. x: seg.x,
  1113. y: seg.y,
  1114. x1: seg.x1,
  1115. y1: seg.y1,
  1116. x2: seg.x2,
  1117. y2: seg.y2,
  1118. r1: seg.r1,
  1119. r2: seg.r2,
  1120. angle: seg.angle,
  1121. largeArcFlag: seg.largeArcFlag,
  1122. sweepFlag: seg.sweepFlag
  1123. };
  1124. }
  1125. var len = changes.d.length,
  1126. firstseg = changes.d[0],
  1127. currentpt = remap(firstseg.x,firstseg.y);
  1128. changes.d[0].x = currentpt.x;
  1129. changes.d[0].y = currentpt.y;
  1130. for (var i = 1; i < len; ++i) {
  1131. var seg = changes.d[i];
  1132. var type = seg.type;
  1133. // if absolute or first segment, we want to remap x, y, x1, y1, x2, y2
  1134. // if relative, we want to scalew, scaleh
  1135. if (type % 2 == 0) { // absolute
  1136. var thisx = (seg.x != undefined) ? seg.x : currentpt.x, // for V commands
  1137. thisy = (seg.y != undefined) ? seg.y : currentpt.y, // for H commands
  1138. pt = remap(thisx,thisy),
  1139. pt1 = remap(seg.x1,seg.y1),
  1140. pt2 = remap(seg.x2,seg.y2);
  1141. seg.x = pt.x;
  1142. seg.y = pt.y;
  1143. seg.x1 = pt1.x;
  1144. seg.y1 = pt1.y;
  1145. seg.x2 = pt2.x;
  1146. seg.y2 = pt2.y;
  1147. seg.r1 = scalew(seg.r1),
  1148. seg.r2 = scaleh(seg.r2);
  1149. }
  1150. else { // relative
  1151. seg.x = scalew(seg.x);
  1152. seg.y = scaleh(seg.y);
  1153. seg.x1 = scalew(seg.x1);
  1154. seg.y1 = scaleh(seg.y1);
  1155. seg.x2 = scalew(seg.x2);
  1156. seg.y2 = scaleh(seg.y2);
  1157. seg.r1 = scalew(seg.r1),
  1158. seg.r2 = scaleh(seg.r2);
  1159. }
  1160. } // for each segment
  1161. var dstr = "";
  1162. var len = changes.d.length;
  1163. for (var i = 0; i < len; ++i) {
  1164. var seg = changes.d[i];
  1165. var type = seg.type;
  1166. dstr += pathMap[type];
  1167. switch(type) {
  1168. case 13: // relative horizontal line (h)
  1169. case 12: // absolute horizontal line (H)
  1170. dstr += seg.x + " ";
  1171. break;
  1172. case 15: // relative vertical line (v)
  1173. case 14: // absolute vertical line (V)
  1174. dstr += seg.y + " ";
  1175. break;
  1176. case 3: // relative move (m)
  1177. case 5: // relative line (l)
  1178. case 19: // relative smooth quad (t)
  1179. case 2: // absolute move (M)
  1180. case 4: // absolute line (L)
  1181. case 18: // absolute smooth quad (T)
  1182. dstr += seg.x + "," + seg.y + " ";
  1183. break;
  1184. case 7: // relative cubic (c)
  1185. case 6: // absolute cubic (C)
  1186. dstr += seg.x1 + "," + seg.y1 + " " + seg.x2 + "," + seg.y2 + " " +
  1187. seg.x + "," + seg.y + " ";
  1188. break;
  1189. case 9: // relative quad (q)
  1190. case 8: // absolute quad (Q)
  1191. dstr += seg.x1 + "," + seg.y1 + " " + seg.x + "," + seg.y + " ";
  1192. break;
  1193. case 11: // relative elliptical arc (a)
  1194. case 10: // absolute elliptical arc (A)
  1195. dstr += seg.r1 + "," + seg.r2 + " " + seg.angle + " " + (+seg.largeArcFlag) +
  1196. " " + (+seg.sweepFlag) + " " + seg.x + "," + seg.y + " ";
  1197. break;
  1198. case 17: // relative smooth cubic (s)
  1199. case 16: // absolute smooth cubic (S)
  1200. dstr += seg.x2 + "," + seg.y2 + " " + seg.x + "," + seg.y + " ";
  1201. break;
  1202. }
  1203. }
  1204. selected.setAttribute("d", dstr);
  1205. break;
  1206. }
  1207. };
  1208. // Function: updateClipPath
  1209. // Updates a <clipPath>s values based on the given translation of an element
  1210. //
  1211. // Parameters:
  1212. // attr - The clip-path attribute value with the clipPath's ID
  1213. // tx - The translation's x value
  1214. // ty - The translation's y value
  1215. var updateClipPath = function(attr, tx, ty) {
  1216. var path = getRefElem(attr).firstChild;
  1217. var cp_xform = getTransformList(path);
  1218. var newxlate = svgroot.createSVGTransform();
  1219. newxlate.setTranslate(tx, ty);
  1220. cp_xform.appendItem(newxlate);
  1221. // Update clipPath's dimensions
  1222. recalculateDimensions(path);
  1223. }
  1224. // Function: recalculateDimensions
  1225. // Decides the course of action based on the element's transform list
  1226. //
  1227. // Parameters:
  1228. // selected - The DOM element to recalculate
  1229. //
  1230. // Returns:
  1231. // Undo command object with the resulting change
  1232. var recalculateDimensions = this.recalculateDimensions = function(selected) {
  1233. if (selected == null) return null;
  1234. var tlist = getTransformList(selected);
  1235. // remove any unnecessary transforms
  1236. if (tlist && tlist.numberOfItems > 0) {
  1237. var k = tlist.numberOfItems;
  1238. while (k--) {
  1239. var xform = tlist.getItem(k);
  1240. if (xform.type === 0) {
  1241. tlist.removeItem(k);
  1242. }
  1243. // remove identity matrices
  1244. else if (xform.type === 1) {
  1245. if (svgedit.math.isIdentity(xform.matrix)) {
  1246. tlist.removeItem(k);
  1247. }
  1248. }
  1249. // remove zero-degree rotations
  1250. else if (xform.type === 4) {
  1251. if (xform.angle === 0) {
  1252. tlist.removeItem(k);
  1253. }
  1254. }
  1255. }
  1256. // End here if all it has is a rotation
  1257. if(tlist.numberOfItems === 1 && getRotationAngle(selected)) return null;
  1258. }
  1259. // if this element had no transforms, we are done
  1260. if (!tlist || tlist.numberOfItems == 0) {
  1261. selected.removeAttribute("transform");
  1262. return null;
  1263. }
  1264. // TODO: Make this work for more than 2
  1265. if (tlist) {
  1266. var k = tlist.numberOfItems;
  1267. var mxs = [];
  1268. while (k--) {
  1269. var xform = tlist.getItem(k);
  1270. if (xform.type === 1) {
  1271. mxs.push([xform.matrix, k]);
  1272. } else if(mxs.length) {
  1273. mxs = [];
  1274. }
  1275. }
  1276. if(mxs.length === 2) {
  1277. var m_new = svgroot.createSVGTransformFromMatrix(matrixMultiply(mxs[1][0], mxs[0][0]));
  1278. tlist.removeItem(mxs[0][1]);
  1279. tlist.removeItem(mxs[1][1]);
  1280. tlist.insertItemBefore(m_new, mxs[1][1]);
  1281. }
  1282. // combine matrix + translate
  1283. k = tlist.numberOfItems;
  1284. if(k >= 2 && tlist.getItem(k-2).type === 1 && tlist.getItem(k-1).type === 2) {
  1285. var mt = svgroot.createSVGTransform();
  1286. var m = matrixMultiply(
  1287. tlist.getItem(k-2).matrix,
  1288. tlist.getItem(k-1).matrix
  1289. );
  1290. mt.setMatrix(m);
  1291. tlist.removeItem(k-2);
  1292. tlist.removeItem(k-2);
  1293. tlist.appendItem(mt);
  1294. }
  1295. }
  1296. // If it still has a single [M] or [R][M], return null too (prevents BatchCommand from being returned).
  1297. switch ( selected.tagName ) {
  1298. // Ignore these elements, as they can absorb the [M]
  1299. case 'line':
  1300. case 'polyline':
  1301. case 'polygon':
  1302. case 'path':
  1303. break;
  1304. default:
  1305. if(
  1306. (tlist.numberOfItems === 1 && tlist.getItem(0).type === 1)
  1307. || (tlist.numberOfItems === 2 && tlist.getItem(0).type === 1 && tlist.getItem(0).type === 4)
  1308. ) {
  1309. return null;
  1310. }
  1311. }
  1312. // Grouped SVG element
  1313. var gsvg = $(selected).data('gsvg');
  1314. // we know we have some transforms, so set up return variable
  1315. var batchCmd = new BatchCommand("Transform");
  1316. // store initial values that will be affected by reducing the transform list
  1317. var changes = {}, initial = null, attrs = [];
  1318. switch (selected.tagName)
  1319. {
  1320. case "line":
  1321. attrs = ["x1", "y1", "x2", "y2"];
  1322. break;
  1323. case "circle":
  1324. attrs = ["cx", "cy", "r"];
  1325. break;
  1326. case "ellipse":
  1327. attrs = ["cx", "cy", "rx", "ry"];
  1328. break;
  1329. case "foreignObject":
  1330. case "rect":
  1331. case "image":
  1332. attrs = ["width", "height", "x", "y"];
  1333. break;
  1334. case "use":
  1335. case "text":
  1336. attrs = ["x", "y"];
  1337. break;
  1338. case "polygon":
  1339. case "polyline":
  1340. initial = {};
  1341. initial["points"] = selected.getAttribute("points");
  1342. var list = selected.points;
  1343. var len = list.numberOfItems;
  1344. changes["points"] = new Array(len);
  1345. for (var i = 0; i < len; ++i) {
  1346. var pt = list.getItem(i);
  1347. changes["points"][i] = {x:pt.x,y:pt.y};
  1348. }
  1349. break;
  1350. case "path":
  1351. initial = {};
  1352. initial["d"] = selected.getAttribute("d");
  1353. changes["d"] = selected.getAttribute("d");
  1354. break;
  1355. } // switch on element type to get initial values
  1356. if(attrs.length) {
  1357. changes = $(selected).attr(attrs);
  1358. $.each(changes, function(attr, val) {
  1359. changes[attr] = convertToNum(attr, val);
  1360. });
  1361. } else if(gsvg) {
  1362. // GSVG exception
  1363. changes = {
  1364. x: $(gsvg).attr('x') || 0,
  1365. y: $(gsvg).attr('y') || 0
  1366. };
  1367. }
  1368. // if we haven't created an initial array in polygon/polyline/path, then
  1369. // make a copy of initial values and include the transform
  1370. if (initial == null) {
  1371. initial = $.extend(true, {}, changes);
  1372. $.each(initial, function(attr, val) {
  1373. initial[attr] = convertToNum(attr, val);
  1374. });
  1375. }
  1376. // save the start transform value too
  1377. initial["transform"] = start_transform ? start_transform : "";
  1378. // if it's a regular group, we have special processing to flatten transforms
  1379. if ((selected.tagName == "g" && !gsvg) || selected.tagName == "a") {
  1380. var box = svgedit.utilities.getBBox(selected),
  1381. oldcenter = {x: box.x+box.width/2, y: box.y+box.height/2},
  1382. newcenter = transformPoint(box.x+box.width/2, box.y+box.height/2,
  1383. transformListToTransform(tlist).matrix),
  1384. m = svgroot.createSVGMatrix();
  1385. // temporarily strip off the rotate and save the old center
  1386. var gangle = getRotationAngle(selected);
  1387. if (gangle) {
  1388. var a = gangle * Math.PI / 180;
  1389. if ( Math.abs(a) > (1.0e-10) ) {
  1390. var s = Math.sin(a)/(1 - Math.cos(a));
  1391. } else {
  1392. // FIXME: This blows up if the angle is exactly 0!
  1393. var s = 2/a;
  1394. }
  1395. for (var i = 0; i < tlist.numberOfItems; ++i) {
  1396. var xform = tlist.getItem(i);
  1397. if (xform.type == 4) {
  1398. // extract old center through mystical arts
  1399. var rm = xform.matrix;
  1400. oldcenter.y = (s*rm.e + rm.f)/2;
  1401. oldcenter.x = (rm.e - s*rm.f)/2;
  1402. tlist.removeItem(i);
  1403. break;
  1404. }
  1405. }
  1406. }
  1407. var tx = 0, ty = 0,
  1408. operation = 0,
  1409. N = tlist.numberOfItems;
  1410. if(N) {
  1411. var first_m = tlist.getItem(0).matrix;
  1412. }
  1413. // first, if it was a scale then the second-last transform will be it
  1414. if (N >= 3 && tlist.getItem(N-2).type == 3 &&
  1415. tlist.getItem(N-3).type == 2 && tlist.getItem(N-1).type == 2)
  1416. {
  1417. operation = 3; // scale
  1418. // if the children are unrotated, pass the scale down directly
  1419. // otherwise pass the equivalent matrix() down directly
  1420. var tm = tlist.getItem(N-3).matrix,
  1421. sm = tlist.getItem(N-2).matrix,
  1422. tmn = tlist.getItem(N-1).matrix;
  1423. var children = selected.childNodes;
  1424. var c = children.length;
  1425. while (c--) {
  1426. var child = children.item(c);
  1427. tx = 0;
  1428. ty = 0;
  1429. if (child.nodeType == 1) {
  1430. var childTlist = getTransformList(child);
  1431. // some children might not have a transform (<metadata>, <defs>, etc)
  1432. if (!childTlist) continue;
  1433. var m = transformListToTransform(childTlist).matrix;
  1434. // Convert a matrix to a scale if applicable
  1435. // if(hasMatrixTransform(childTlist) && childTlist.numberOfItems == 1) {
  1436. // if(m.b==0 && m.c==0 && m.e==0 && m.f==0) {
  1437. // childTlist.removeItem(0);
  1438. // var translateOrigin = svgroot.createSVGTransform(),
  1439. // scale = svgroot.createSVGTransform(),
  1440. // translateBack = svgroot.createSVGTransform();
  1441. // translateOrigin.setTranslate(0, 0);
  1442. // scale.setScale(m.a, m.d);
  1443. // translateBack.setTranslate(0, 0);
  1444. // childTlist.appendItem(translateBack);
  1445. // childTlist.appendItem(scale);
  1446. // childTlist.appendItem(translateOrigin);
  1447. // }
  1448. // }
  1449. var angle = getRotationAngle(child);
  1450. var old_start_transform = start_transform;
  1451. var childxforms = [];
  1452. start_transform = child.getAttribute("transform");
  1453. if(angle || hasMatrixTransform(childTlist)) {
  1454. var e2t = svgroot.createSVGTransform();
  1455. e2t.setMatrix(matrixMultiply(tm, sm, tmn, m));
  1456. childTlist.clear();
  1457. childTlist.appendItem(e2t);
  1458. childxforms.push(e2t);
  1459. }
  1460. // if not rotated or skewed, push the [T][S][-T] down to the child
  1461. else {
  1462. // update the transform list with translate,scale,translate
  1463. // slide the [T][S][-T] from the front to the back
  1464. // [T][S][-T][M] = [M][T2][S2][-T2]
  1465. // (only bringing [-T] to the right of [M])
  1466. // [T][S][-T][M] = [T][S][M][-T2]
  1467. // [-T2] = [M_inv][-T][M]
  1468. var t2n = matrixMultiply(m.inverse(), tmn, m);
  1469. // [T2] is always negative translation of [-T2]
  1470. var t2 = svgroot.createSVGMatrix();
  1471. t2.e = -t2n.e;
  1472. t2.f = -t2n.f;
  1473. // [T][S][-T][M] = [M][T2][S2][-T2]
  1474. // [S2] = [T2_inv][M_inv][T][S][-T][M][-T2_inv]
  1475. var s2 = matrixMultiply(t2.inverse(), m.inverse(), tm, sm, tmn, m, t2n.inverse());
  1476. var translateOrigin = svgroot.createSVGTransform(),
  1477. scale = svgroot.createSVGTransform(),
  1478. translateBack = svgroot.createSVGTransform();
  1479. translateOrigin.setTranslate(t2n.e, t2n.f);
  1480. scale.setScale(s2.a, s2.d);
  1481. translateBack.setTranslate(t2.e, t2.f);
  1482. childTlist.appendItem(translateBack);
  1483. childTlist.appendItem(scale);
  1484. childTlist.appendItem(translateOrigin);
  1485. childxforms.push(translateBack);
  1486. childxforms.push(scale);
  1487. childxforms.push(translateOrigin);
  1488. // logMatrix(translateBack.matrix);
  1489. // logMatrix(scale.matrix);
  1490. } // not rotated
  1491. batchCmd.addSubCommand( recalculateDimensions(child) );
  1492. // TODO: If any <use> have this group as a parent and are
  1493. // referencing this child, then we need to impose a reverse
  1494. // scale on it so that when it won't get double-translated
  1495. // var uses = selected.getElementsByTagNameNS(svgns, "use");
  1496. // var href = "#"+child.id;
  1497. // var u = uses.length;
  1498. // while (u--) {
  1499. // var useElem = uses.item(u);
  1500. // if(href == getHref(useElem)) {
  1501. // var usexlate = svgroot.createSVGTransform();
  1502. // usexlate.setTranslate(-tx,-ty);
  1503. // getTransformList(useElem).insertItemBefore(usexlate,0);
  1504. // batchCmd.addSubCommand( recalculateDimensions(useElem) );
  1505. // }
  1506. // }
  1507. start_transform = old_start_transform;
  1508. } // element
  1509. } // for each child
  1510. // Remove these transforms from group
  1511. tlist.removeItem(N-1);
  1512. tlist.removeItem(N-2);
  1513. tlist.removeItem(N-3);
  1514. }
  1515. else if (N >= 3 && tlist.getItem(N-1).type == 1)
  1516. {
  1517. operation = 3; // scale
  1518. m = transformListToTransform(tlist).matrix;
  1519. var e2t = svgroot.createSVGTransform();
  1520. e2t.setMatrix(m);
  1521. tlist.clear();
  1522. tlist.appendItem(e2t);
  1523. }
  1524. // next, check if the first transform was a translate
  1525. // if we had [ T1 ] [ M ] we want to transform this into [ M ] [ T2 ]
  1526. // therefore [ T2 ] = [ M_inv ] [ T1 ] [ M ]
  1527. else if ( (N == 1 || (N > 1 && tlist.getItem(1).type != 3)) &&
  1528. tlist.getItem(0).type == 2)
  1529. {
  1530. operation = 2; // translate
  1531. var T_M = transformListToTransform(tlist).matrix;
  1532. tlist.removeItem(0);
  1533. var M_inv = transformListToTransform(tlist).matrix.inverse();
  1534. var M2 = matrixMultiply( M_inv, T_M );
  1535. tx = M2.e;
  1536. ty = M2.f;
  1537. if (tx != 0 || ty != 0) {
  1538. // we pass the translates down to the individual children
  1539. var children = selected.childNodes;
  1540. var c = children.length;
  1541. var clipPaths_done = [];
  1542. while (c--) {
  1543. var child = children.item(c);
  1544. if (child.nodeType == 1) {
  1545. // Check if child has clip-path
  1546. if(child.getAttribute('clip-path')) {
  1547. // tx, ty
  1548. var attr = child.getAttribute('clip-path');
  1549. if(clipPaths_done.indexOf(attr) === -1) {
  1550. updateClipPath(attr, tx, ty);
  1551. clipPaths_done.push(attr);
  1552. }
  1553. }
  1554. var old_start_transform = start_transform;
  1555. start_transform = child.getAttribute("transform");
  1556. var childTlist = getTransformList(child);
  1557. // some children might not have a transform (<metadata>, <defs>, etc)
  1558. if (childTlist) {
  1559. var newxlate = svgroot.createSVGTransform();
  1560. newxlate.setTranslate(tx,ty);
  1561. if(childTlist.numberOfItems) {
  1562. childTlist.insertItemBefore(newxlate, 0);
  1563. } else {
  1564. childTlist.appendItem(newxlate);
  1565. }
  1566. batchCmd.addSubCommand( recalculateDimensions(child) );
  1567. // If any <use> have this group as a parent and are
  1568. // referencing this child, then impose a reverse translate on it
  1569. // so that when it won't get double-translated
  1570. var uses = selected.getElementsByTagNameNS(svgns, "use");
  1571. var href = "#"+child.id;
  1572. var u = uses.length;
  1573. while (u--) {
  1574. var useElem = uses.item(u);
  1575. if(href == getHref(useElem)) {
  1576. var usexlate = svgroot.createSVGTransform();
  1577. usexlate.setTranslate(-tx,-ty);
  1578. getTransformList(useElem).insertItemBefore(usexlate,0);
  1579. batchCmd.addSubCommand( recalculateDimensions(useElem) );
  1580. }
  1581. }
  1582. start_transform = old_start_transform;
  1583. }
  1584. }
  1585. }
  1586. clipPaths_done = [];
  1587. start_transform = old_start_transform;
  1588. }
  1589. }
  1590. // else, a matrix imposition from a parent group
  1591. // keep pushing it down to the children
  1592. else if (N == 1 && tlist.getItem(0).type == 1 && !gangle) {
  1593. operation = 1;
  1594. var m = tlist.getItem(0).matrix,
  1595. children = selected.childNodes,
  1596. c = children.length;
  1597. while (c--) {
  1598. var child = children.item(c);
  1599. if (child.nodeType == 1) {
  1600. var old_start_transform = start_transform;
  1601. start_transform = child.getAttribute("transform");
  1602. var childTlist = getTransformList(child);
  1603. if (!childTlist) continue;
  1604. var em = matrixMultiply(m, transformListToTransform(childTlist).matrix);
  1605. var e2m = svgroot.createSVGTransform();
  1606. e2m.setMatrix(em);
  1607. childTlist.clear();
  1608. childTlist.appendItem(e2m,0);
  1609. batchCmd.addSubCommand( recalculateDimensions(child) );
  1610. start_transform = old_start_transform;
  1611. // Convert stroke
  1612. // TODO: Find out if this should actually happen somewhere else
  1613. var sw = child.getAttribute("stroke-width");
  1614. if (child.getAttribute("stroke") !== "none" && !isNaN(sw)) {
  1615. var avg = (Math.abs(em.a) + Math.abs(em.d)) / 2;
  1616. child.setAttribute('stroke-width', sw * avg);
  1617. }
  1618. }
  1619. }
  1620. tlist.clear();
  1621. }
  1622. // else it was just a rotate
  1623. else {
  1624. if (gangle) {
  1625. var newRot = svgroot.createSVGTransform();
  1626. newRot.setRotate(gangle,newcenter.x,newcenter.y);
  1627. if(tlist.numberOfItems) {
  1628. tlist.insertItemBefore(newRot, 0);
  1629. } else {
  1630. tlist.appendItem(newRot);
  1631. }
  1632. }
  1633. if (tlist.numberOfItems == 0) {
  1634. selected.removeAttribute("transform");
  1635. }
  1636. return null;
  1637. }
  1638. // if it was a translate, put back the rotate at the new center
  1639. if (operation == 2) {
  1640. if (gangle) {
  1641. newcenter = {
  1642. x: oldcenter.x + first_m.e,
  1643. y: oldcenter.y + first_m.f
  1644. };
  1645. var newRot = svgroot.createSVGTransform();
  1646. newRot.setRotate(gangle,newcenter.x,newcenter.y);
  1647. if(tlist.numberOfItems) {
  1648. tlist.insertItemBefore(newRot, 0);
  1649. } else {
  1650. tlist.appendItem(newRot);
  1651. }
  1652. }
  1653. }
  1654. // if it was a resize
  1655. else if (operation == 3) {
  1656. var m = transformListToTransform(tlist).matrix;
  1657. var roldt = svgroot.createSVGTransform();
  1658. roldt.setRotate(gangle, oldcenter.x, oldcenter.y);
  1659. var rold = roldt.matrix;
  1660. var rnew = svgroot.createSVGTransform();
  1661. rnew.setRotate(gangle, newcenter.x, newcenter.y);
  1662. var rnew_inv = rnew.matrix.inverse(),
  1663. m_inv = m.inverse(),
  1664. extrat = matrixMultiply(m_inv, rnew_inv, rold, m);
  1665. tx = extrat.e;
  1666. ty = extrat.f;
  1667. if (tx != 0 || ty != 0) {
  1668. // now push this transform down to the children
  1669. // we pass the translates down to the individual children
  1670. var children = selected.childNodes;
  1671. var c = children.length;
  1672. while (c--) {
  1673. var child = children.item(c);
  1674. if (child.nodeType == 1) {
  1675. var old_start_transform = start_transform;
  1676. start_transform = child.getAttribute("transform");
  1677. var childTlist = getTransformList(child);
  1678. var newxlate = svgroot.createSVGTransform();
  1679. newxlate.setTranslate(tx,ty);
  1680. if(childTlist.numberOfItems) {
  1681. childTlist.insertItemBefore(newxlate, 0);
  1682. } else {
  1683. childTlist.appendItem(newxlate);
  1684. }
  1685. batchCmd.addSubCommand( recalculateDimensions(child) );
  1686. start_transform = old_start_transform;
  1687. }
  1688. }
  1689. }
  1690. if (gangle) {
  1691. if(tlist.numberOfItems) {
  1692. tlist.insertItemBefore(rnew, 0);
  1693. } else {
  1694. tlist.appendItem(rnew);
  1695. }
  1696. }
  1697. }
  1698. }
  1699. // else, it's a non-group
  1700. else {
  1701. // FIXME: box might be null for some elements (<metadata> etc), need to handle this
  1702. var box = svgedit.utilities.getBBox(selected);
  1703. // Paths (and possbly other shapes) will have no BBox while still in <defs>,
  1704. // but we still may need to recalculate them (see issue 595).
  1705. // TODO: Figure out how to get BBox from these elements in case they
  1706. // have a rotation transform
  1707. if(!box && selected.tagName != 'path') return null;
  1708. var m = svgroot.createSVGMatrix(),
  1709. // temporarily strip off the rotate and save the old center
  1710. angle = getRotationAngle(selected);
  1711. if (angle) {
  1712. var oldcenter = {x: box.x+box.width/2, y: box.y+box.height/2},
  1713. newcenter = transformPoint(box.x+box.width/2, box.y+box.height/2,
  1714. transformListToTransform(tlist).matrix);
  1715. var a = angle * Math.PI / 180;
  1716. if ( Math.abs(a) > (1.0e-10) ) {
  1717. var s = Math.sin(a)/(1 - Math.cos(a));
  1718. } else {
  1719. // FIXME: This blows up if the angle is exactly 0!
  1720. var s = 2/a;
  1721. }
  1722. for (var i = 0; i < tlist.numberOfItems; ++i) {
  1723. var xform = tlist.getItem(i);
  1724. if (xform.type == 4) {
  1725. // extract old center through mystical arts
  1726. var rm = xform.matrix;
  1727. oldcenter.y = (s*rm.e + rm.f)/2;
  1728. oldcenter.x = (rm.e - s*rm.f)/2;
  1729. tlist.removeItem(i);
  1730. break;
  1731. }
  1732. }
  1733. }
  1734. // 2 = translate, 3 = scale, 4 = rotate, 1 = matrix imposition
  1735. var operation = 0;
  1736. var N = tlist.numberOfItems;
  1737. // Check if it has a gradient with userSpaceOnUse, in which case
  1738. // adjust it by recalculating the matrix transform.
  1739. // TODO: Make this work in Webkit using svgedit.transformlist.SVGTransformList
  1740. if(!svgedit.browser.isWebkit()) {
  1741. var fill = selected.getAttribute('fill');
  1742. if(fill && fill.indexOf('url(') === 0) {
  1743. var paint = getRefElem(fill);
  1744. var type = 'pattern';
  1745. if(paint.tagName !== type) type = 'gradient';
  1746. var attrVal = paint.getAttribute(type + 'Units');
  1747. if(attrVal === 'userSpaceOnUse') {
  1748. //Update the userSpaceOnUse element
  1749. m = transformListToTransform(tlist).matrix;
  1750. var gtlist = getTransformList(paint);
  1751. var gmatrix = transformListToTransform(gtlist).matrix;
  1752. m = matrixMultiply(m, gmatrix);
  1753. var m_str = "matrix(" + [m.a,m.b,m.c,m.d,m.e,m.f].join(",") + ")";
  1754. paint.setAttribute(type + 'Transform', m_str);
  1755. }
  1756. }
  1757. }
  1758. // first, if it was a scale of a non-skewed element, then the second-last
  1759. // transform will be the [S]
  1760. // if we had [M][T][S][T] we want to extract the matrix equivalent of
  1761. // [T][S][T] and push it down to the element
  1762. if (N >= 3 && tlist.getItem(N-2).type == 3 &&
  1763. tlist.getItem(N-3).type == 2 && tlist.getItem(N-1).type == 2)
  1764. // Removed this so a <use> with a given [T][S][T] would convert to a matrix.
  1765. // Is that bad?
  1766. // && selected.nodeName != "use"
  1767. {
  1768. operation = 3; // scale
  1769. m = transformListToTransform(tlist,N-3,N-1).matrix;
  1770. tlist.removeItem(N-1);
  1771. tlist.removeItem(N-2);
  1772. tlist.removeItem(N-3);
  1773. } // if we had [T][S][-T][M], then this was a skewed element being resized
  1774. // Thus, we simply combine it all into one matrix
  1775. else if(N == 4 && tlist.getItem(N-1).type == 1) {
  1776. operation = 3; // scale
  1777. m = transformListToTransform(tlist).matrix;
  1778. var e2t = svgroot.createSVGTransform();
  1779. e2t.setMatrix(m);
  1780. tlist.clear();
  1781. tlist.appendItem(e2t);
  1782. // reset the matrix so that the element is not re-mapped
  1783. m = svgroot.createSVGMatrix();
  1784. } // if we had [R][T][S][-T][M], then this was a rotated matrix-element
  1785. // if we had [T1][M] we want to transform this into [M][T2]
  1786. // therefore [ T2 ] = [ M_inv ] [ T1 ] [ M ] and we can push [T2]
  1787. // down to the element
  1788. else if ( (N == 1 || (N > 1 && tlist.getItem(1).type != 3)) &&
  1789. tlist.getItem(0).type == 2)
  1790. {
  1791. operation = 2; // translate
  1792. var oldxlate = tlist.getItem(0).matrix,
  1793. meq = transformListToTransform(tlist,1).matrix,
  1794. meq_inv = meq.inverse();
  1795. m = matrixMultiply( meq_inv, oldxlate, meq );
  1796. tlist.removeItem(0);
  1797. }
  1798. // else if this child now has a matrix imposition (from a parent group)
  1799. // we might be able to simplify
  1800. else if (N == 1 && tlist.getItem(0).type == 1 && !angle) {
  1801. // Remap all point-based elements
  1802. m = transformListToTransform(tlist).matrix;
  1803. switch (selected.tagName) {
  1804. case 'line':
  1805. changes = $(selected).attr(["x1","y1","x2","y2"]);
  1806. case 'polyline':
  1807. case 'polygon':
  1808. changes.points = selected.getAttribute("points");
  1809. if(changes.points) {
  1810. var list = selected.points;
  1811. var len = list.numberOfItems;
  1812. changes.points = new Array(len);
  1813. for (var i = 0; i < len; ++i) {
  1814. var pt = list.getItem(i);
  1815. changes.points[i] = {x:pt.x,y:pt.y};
  1816. }
  1817. }
  1818. case 'path':
  1819. changes.d = selected.getAttribute("d");
  1820. operation = 1;
  1821. tlist.clear();
  1822. break;
  1823. default:
  1824. break;
  1825. }
  1826. }
  1827. // if it was a rotation, put the rotate back and return without a command
  1828. // (this function has zero work to do for a rotate())
  1829. else {
  1830. operation = 4; // rotation
  1831. if (angle) {
  1832. var newRot = svgroot.createSVGTransform();
  1833. newRot.setRotate(angle,newcenter.x,newcenter.y);
  1834. if(tlist.numberOfItems) {
  1835. tlist.insertItemBefore(newRot, 0);
  1836. } else {
  1837. tlist.appendItem(newRot);
  1838. }
  1839. }
  1840. if (tlist.numberOfItems == 0) {
  1841. selected.removeAttribute("transform");
  1842. }
  1843. return null;
  1844. }
  1845. // if it was a translate or resize, we need to remap the element and absorb the xform
  1846. if (operation == 1 || operation == 2 || operation == 3) {
  1847. remapElement(selected,changes,m);
  1848. } // if we are remapping
  1849. // if it was a translate, put back the rotate at the new center
  1850. if (operation == 2) {
  1851. if (angle) {
  1852. if(!hasMatrixTransform(tlist)) {
  1853. newcenter = {
  1854. x: oldcenter.x + m.e,
  1855. y: oldcenter.y + m.f
  1856. };
  1857. }
  1858. var newRot = svgroot.createSVGTransform();
  1859. newRot.setRotate(angle, newcenter.x, newcenter.y);
  1860. if(tlist.numberOfItems) {
  1861. tlist.insertItemBefore(newRot, 0);
  1862. } else {
  1863. tlist.appendItem(newRot);
  1864. }
  1865. }
  1866. }
  1867. // [Rold][M][T][S][-T] became [Rold][M]
  1868. // we want it to be [Rnew][M][Tr] where Tr is the
  1869. // translation required to re-center it
  1870. // Therefore, [Tr] = [M_inv][Rnew_inv][Rold][M]
  1871. else if (operation == 3 && angle) {
  1872. var m = transformListToTransform(tlist).matrix;
  1873. var roldt = svgroot.createSVGTransform();
  1874. roldt.setRotate(angle, oldcenter.x, oldcenter.y);
  1875. var rold = roldt.matrix;
  1876. var rnew = svgroot.createSVGTransform();
  1877. rnew.setRotate(angle, newcenter.x, newcenter.y);
  1878. var rnew_inv = rnew.matrix.inverse();
  1879. var m_inv = m.inverse();
  1880. var extrat = matrixMultiply(m_inv, rnew_inv, rold, m);
  1881. remapElement(selected,changes,extrat);
  1882. if (angle) {
  1883. if(tlist.numberOfItems) {
  1884. tlist.insertItemBefore(rnew, 0);
  1885. } else {
  1886. tlist.appendItem(rnew);
  1887. }
  1888. }
  1889. }
  1890. } // a non-group
  1891. // if the transform list has been emptied, remove it
  1892. if (tlist.numberOfItems == 0) {
  1893. selected.removeAttribute("transform");
  1894. }
  1895. batchCmd.addSubCommand(new ChangeElementCommand(selected, initial));
  1896. return batchCmd;
  1897. };
  1898. // Root Current Transformation Matrix in user units
  1899. var root_sctm = null;
  1900. // Group: Selection
  1901. // Function: clearSelection
  1902. // Clears the selection. The 'selected' handler is then called.
  1903. // Parameters:
  1904. // noCall - Optional boolean that when true does not call the "selected" handler
  1905. var clearSelection = this.clearSelection = function(noCall) {
  1906. if (selectedElements[0] != null) {
  1907. var len = selectedElements.length;
  1908. for (var i = 0; i < len; ++i) {
  1909. var elem = selectedElements[i];
  1910. if (elem == null) break;
  1911. selectorManager.releaseSelector(elem);
  1912. selectedElements[i] = null;
  1913. }
  1914. // selectedBBoxes[0] = null;
  1915. }
  1916. if(!noCall) call("selected", selectedElements);
  1917. };
  1918. // TODO: do we need to worry about selectedBBoxes here?
  1919. // Function: addToSelection
  1920. // Adds a list of elements to the selection. The 'selected' handler is then called.
  1921. //
  1922. // Parameters:
  1923. // elemsToAdd - an array of DOM elements to add to the selection
  1924. // showGrips - a boolean flag indicating whether the resize grips should be shown
  1925. var addToSelection = this.addToSelection = function(elemsToAdd, showGrips) {
  1926. if (elemsToAdd.length == 0) { return; }
  1927. // find the first null in our selectedElements array
  1928. var j = 0;
  1929. while (j < selectedElements.length) {
  1930. if (selectedElements[j] == null) {
  1931. break;
  1932. }
  1933. ++j;
  1934. }
  1935. // now add each element consecutively
  1936. var i = elemsToAdd.length;
  1937. while (i--) {
  1938. var elem = elemsToAdd[i];
  1939. if (!elem || !svgedit.utilities.getBBox(elem)) continue;
  1940. if(elem.tagName === 'a' && elem.childNodes.length === 1) {
  1941. // Make "a" element's child be the selected element
  1942. elem = elem.firstChild;
  1943. }
  1944. // if it's not already there, add it
  1945. if (selectedElements.indexOf(elem) == -1) {
  1946. selectedElements[j] = elem;
  1947. // only the first selectedBBoxes element is ever used in the codebase these days
  1948. // if (j == 0) selectedBBoxes[0] = svgedit.utilities.getBBox(elem);
  1949. j++;
  1950. var sel = selectorManager.requestSelector(elem);
  1951. if (selectedElements.length > 1) {
  1952. sel.showGrips(false);
  1953. }
  1954. }
  1955. }
  1956. call("selected", selectedElements);
  1957. if (showGrips || selectedElements.length == 1) {
  1958. selectorManager.requestSelector(selectedElements[0]).showGrips(true);
  1959. }
  1960. else {
  1961. selectorManager.requestSelector(selectedElements[0]).showGrips(false);
  1962. }
  1963. // make sure the elements are in the correct order
  1964. // See: http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-compareDocumentPosition
  1965. selectedElements.sort(function(a,b) {
  1966. if(a && b && a.compareDocumentPosition) {
  1967. return 3 - (b.compareDocumentPosition(a) & 6);
  1968. } else if(a == null) {
  1969. return 1;
  1970. }
  1971. });
  1972. // Make sure first elements are not null
  1973. while(selectedElements[0] == null) selectedElements.shift(0);
  1974. };
  1975. // Function: selectOnly()
  1976. // Selects only the given elements, shortcut for clearSelection(); addToSelection()
  1977. //
  1978. // Parameters:
  1979. // elems - an array of DOM elements to be selected
  1980. var selectOnly = this.selectOnly = function(elems, showGrips) {
  1981. clearSelection(true);
  1982. addToSelection(elems, showGrips);
  1983. }
  1984. // TODO: could use slice here to make this faster?
  1985. // TODO: should the 'selected' handler
  1986. // Function: removeFromSelection
  1987. // Removes elements from the selection.
  1988. //
  1989. // Parameters:
  1990. // elemsToRemove - an array of elements to remove from selection
  1991. var removeFromSelection = this.removeFromSelection = function(elemsToRemove) {
  1992. if (selectedElements[0] == null) { return; }
  1993. if (elemsToRemove.length == 0) { return; }
  1994. // find every element and remove it from our array copy
  1995. var newSelectedItems = new Array(selectedElements.length);
  1996. j = 0,
  1997. len = selectedElements.length;
  1998. for (var i = 0; i < len; ++i) {
  1999. var elem = selectedElements[i];
  2000. if (elem) {
  2001. // keep the item
  2002. if (elemsToRemove.indexOf(elem) == -1) {
  2003. newSelectedItems[j] = elem;
  2004. j++;
  2005. }
  2006. else { // remove the item and its selector
  2007. selectorManager.releaseSelector(elem);
  2008. }
  2009. }
  2010. }
  2011. // the copy becomes the master now
  2012. selectedElements = newSelectedItems;
  2013. };
  2014. // Function: selectAllInCurrentLayer
  2015. // Clears the selection, then adds all elements in the current layer to the selection.
  2016. this.selectAllInCurrentLayer = function() {
  2017. var current_layer = getCurrentDrawing().getCurrentLayer();
  2018. if (current_layer) {
  2019. current_mode = "select";
  2020. selectOnly($(current_group || current_layer).children());
  2021. }
  2022. };
  2023. // Function: getMouseTarget
  2024. // Gets the desired element from a mouse event
  2025. //
  2026. // Parameters:
  2027. // evt - Event object from the mouse event
  2028. //
  2029. // Returns:
  2030. // DOM element we want
  2031. var getMouseTarget = this.getMouseTarget = function(evt) {
  2032. if (evt == null) {
  2033. return null;
  2034. }
  2035. var mouse_target = evt.target;
  2036. // if it was a <use>, Opera and WebKit return the SVGElementInstance
  2037. if (mouse_target.correspondingUseElement) mouse_target = mouse_target.correspondingUseElement;
  2038. // for foreign content, go up until we find the foreignObject
  2039. // WebKit browsers set the mouse target to the svgcanvas div
  2040. if ([mathns, htmlns].indexOf(mouse_target.namespaceURI) >= 0 &&
  2041. mouse_target.id != "svgcanvas")
  2042. {
  2043. while (mouse_target.nodeName != "foreignObject") {
  2044. mouse_target = mouse_target.parentNode;
  2045. if(!mouse_target) return svgroot;
  2046. }
  2047. }
  2048. // Get the desired mouse_target with jQuery selector-fu
  2049. // If it's root-like, select the root
  2050. var current_layer = getCurrentDrawing().getCurrentLayer();
  2051. if([svgroot, container, svgcontent, current_layer].indexOf(mouse_target) >= 0) {
  2052. return svgroot;
  2053. }
  2054. var $target = $(mouse_target);
  2055. // If it's a selection grip, return the grip parent
  2056. if($target.closest('#selectorParentGroup').length) {
  2057. // While we could instead have just returned mouse_target,
  2058. // this makes it easier to indentify as being a selector grip
  2059. return selectorManager.selectorParentGroup;
  2060. }
  2061. while (mouse_target.parentNode !== (current_group || current_layer)) {
  2062. mouse_target = mouse_target.parentNode;
  2063. }
  2064. //
  2065. // // go up until we hit a child of a layer
  2066. // while (mouse_target.parentNode.parentNode.tagName == 'g') {
  2067. // mouse_target = mouse_target.parentNode;
  2068. // }
  2069. // Webkit bubbles the mouse event all the way up to the div, so we
  2070. // set the mouse_target to the svgroot like the other browsers
  2071. // if (mouse_target.nodeName.toLowerCase() == "div") {
  2072. // mouse_target = svgroot;
  2073. // }
  2074. return mouse_target;
  2075. };
  2076. // Mouse events
  2077. (function() {
  2078. var d_attr = null,
  2079. start_x = null,
  2080. start_y = null,
  2081. r_start_x = null,
  2082. r_start_y = null,
  2083. init_bbox = {},
  2084. freehand = {
  2085. minx: null,
  2086. miny: null,
  2087. maxx: null,
  2088. maxy: null
  2089. };
  2090. // - when we are in a create mode, the element is added to the canvas
  2091. // but the action is not recorded until mousing up
  2092. // - when we are in select mode, select the element, remember the position
  2093. // and do nothing else
  2094. var mouseDown = function(evt)
  2095. {
  2096. if(canvas.spaceKey || evt.button === 1) return;
  2097. var right_click = evt.button === 2;
  2098. if(evt.altKey) { // duplicate when dragging
  2099. svgCanvas.cloneSelectedElements(0,0);
  2100. }
  2101. root_sctm = svgcontent.getScreenCTM().inverse();
  2102. var pt = transformPoint( evt.pageX, evt.pageY, root_sctm ),
  2103. mouse_x = pt.x * current_zoom,
  2104. mouse_y = pt.y * current_zoom;
  2105. evt.preventDefault();
  2106. if(right_click) {
  2107. current_mode = "select";
  2108. lastClickPoint = pt;
  2109. }
  2110. // This would seem to be unnecessary...
  2111. // if(['select', 'resize'].indexOf(current_mode) == -1) {
  2112. // setGradient();
  2113. // }
  2114. var x = mouse_x / current_zoom,
  2115. y = mouse_y / current_zoom,
  2116. mouse_target = getMouseTarget(evt);
  2117. if(mouse_target.tagName === 'a' && mouse_target.childNodes.length === 1) {
  2118. mouse_target = mouse_target.firstChild;
  2119. }
  2120. // real_x/y ignores grid-snap value
  2121. var real_x = r_start_x = start_x = x;
  2122. var real_y = r_start_y = start_y = y;
  2123. if(curConfig.gridSnapping){
  2124. x = snapToGrid(x);
  2125. y = snapToGrid(y);
  2126. start_x = snapToGrid(start_x);
  2127. start_y = snapToGrid(start_y);
  2128. }
  2129. // if it is a selector grip, then it must be a single element selected,
  2130. // set the mouse_target to that and update the mode to rotate/resize
  2131. if (mouse_target == selectorManager.selectorParentGroup && selectedElements[0] != null) {
  2132. var grip = evt.target;
  2133. var griptype = elData(grip, "type");
  2134. // rotating
  2135. if (griptype == "rotate") {
  2136. current_mode = "rotate";
  2137. }
  2138. // resizing
  2139. else if(griptype == "resize") {
  2140. current_mode = "resize";
  2141. current_resize_mode = elData(grip, "dir");
  2142. }
  2143. mouse_target = selectedElements[0];
  2144. }
  2145. start_transform = mouse_target.getAttribute("transform");
  2146. var tlist = getTransformList(mouse_target);
  2147. switch (current_mode) {
  2148. case "select":
  2149. started = true;
  2150. current_resize_mode = "none";
  2151. if(right_click) started = false;
  2152. if (mouse_target != svgroot) {
  2153. // if this element is not yet selected, clear selection and select it
  2154. if (selectedElements.indexOf(mouse_target) == -1) {
  2155. // only clear selection if shift is not pressed (otherwise, add
  2156. // element to selection)
  2157. if (!evt.shiftKey) {
  2158. // No need to do the call here as it will be done on addToSelection
  2159. clearSelection(true);
  2160. }
  2161. addToSelection([mouse_target]);
  2162. justSelected = mouse_target;
  2163. pathActions.clear();
  2164. }
  2165. // else if it's a path, go into pathedit mode in mouseup
  2166. if(!right_click) {
  2167. // insert a dummy transform so if the element(s) are moved it will have
  2168. // a transform to use for its translate
  2169. for (var i = 0; i < selectedElements.length; ++i) {
  2170. if(selectedElements[i] == null) continue;
  2171. var slist = getTransformList(selectedElements[i]);
  2172. if(slist.numberOfItems) {
  2173. slist.insertItemBefore(svgroot.createSVGTransform(), 0);
  2174. } else {
  2175. slist.appendItem(svgroot.createSVGTransform());
  2176. }
  2177. }
  2178. }
  2179. }
  2180. else if(!right_click){
  2181. clearSelection();
  2182. current_mode = "multiselect";
  2183. if (rubberBox == null) {
  2184. rubberBox = selectorManager.getRubberBandBox();
  2185. }
  2186. r_start_x *= current_zoom;
  2187. r_start_y *= current_zoom;
  2188. // console.log('p',[evt.pageX, evt.pageY]);
  2189. // console.log('c',[evt.clientX, evt.clientY]);
  2190. // console.log('o',[evt.offsetX, evt.offsetY]);
  2191. // console.log('s',[start_x, start_y]);
  2192. assignAttributes(rubberBox, {
  2193. 'x': r_start_x,
  2194. 'y': r_start_y,
  2195. 'width': 0,
  2196. 'height': 0,
  2197. 'display': 'inline'
  2198. }, 100);
  2199. }
  2200. break;
  2201. case "zoom":
  2202. started = true;
  2203. if (rubberBox == null) {
  2204. rubberBox = selectorManager.getRubberBandBox();
  2205. }
  2206. assignAttributes(rubberBox, {
  2207. 'x': real_x * current_zoom,
  2208. 'y': real_x * current_zoom,
  2209. 'width': 0,
  2210. 'height': 0,
  2211. 'display': 'inline'
  2212. }, 100);
  2213. break;
  2214. case "resize":
  2215. started = true;
  2216. start_x = x;
  2217. start_y = y;
  2218. // Getting the BBox from the selection box, since we know we
  2219. // want to orient around it
  2220. init_bbox = svgedit.utilities.getBBox($('#selectedBox0')[0]);
  2221. var bb = {};
  2222. $.each(init_bbox, function(key, val) {
  2223. bb[key] = val/current_zoom;
  2224. });
  2225. init_bbox = bb;
  2226. // append three dummy transforms to the tlist so that
  2227. // we can translate,scale,translate in mousemove
  2228. var pos = getRotationAngle(mouse_target)?1:0;
  2229. if(hasMatrixTransform(tlist)) {
  2230. tlist.insertItemBefore(svgroot.createSVGTransform(), pos);
  2231. tlist.insertItemBefore(svgroot.createSVGTransform(), pos);
  2232. tlist.insertItemBefore(svgroot.createSVGTransform(), pos);
  2233. } else {
  2234. tlist.appendItem(svgroot.createSVGTransform());
  2235. tlist.appendItem(svgroot.createSVGTransform());
  2236. tlist.appendItem(svgroot.createSVGTransform());
  2237. if(svgedit.browser.supportsNonScalingStroke()) {
  2238. //Handle crash for newer Chrome + Windows: https://code.google.com/p/svg-edit/issues/detail?id=904
  2239. // TODO: Remove this workaround (all isChromeWindows blocks) once vendor fixes the issue
  2240. var isChromeWindows = svgedit.browser.isChrome() && svgedit.browser.isWindows();
  2241. if(isChromeWindows) {
  2242. var delayedStroke = function(ele) {
  2243. var _stroke = ele.getAttributeNS(null, 'stroke');
  2244. ele.removeAttributeNS(null, 'stroke');
  2245. //Re-apply stroke after delay. Anything higher than 1 seems to cause flicker
  2246. setTimeout(function() { ele.setAttributeNS(null, 'stroke', _stroke) }, 1);
  2247. }
  2248. }
  2249. mouse_target.style.vectorEffect = 'non-scaling-stroke';
  2250. if(isChromeWindows) delayedStroke(mouse_target);
  2251. var all = mouse_target.getElementsByTagName('*'),
  2252. len = all.length;
  2253. for(var i = 0; i < len; i++) {
  2254. all[i].style.vectorEffect = 'non-scaling-stroke';
  2255. if(isChromeWindows) delayedStroke(all[i]);
  2256. }
  2257. }
  2258. }
  2259. break;
  2260. case "fhellipse":
  2261. case "fhrect":
  2262. case "fhpath":
  2263. started = true;
  2264. d_attr = real_x + "," + real_y + " ";
  2265. var stroke_w = cur_shape.stroke_width == 0?1:cur_shape.stroke_width;
  2266. addSvgElementFromJson({
  2267. "element": "polyline",
  2268. "curStyles": true,
  2269. "attr": {
  2270. "points": d_attr,
  2271. "id": getNextId(),
  2272. "fill": "none",
  2273. "opacity": cur_shape.opacity / 2,
  2274. "stroke-linecap": "round",
  2275. "style": "pointer-events:none"
  2276. }
  2277. });
  2278. freehand.minx = real_x;
  2279. freehand.maxx = real_x;
  2280. freehand.miny = real_y;
  2281. freehand.maxy = real_y;
  2282. break;
  2283. case "image":
  2284. started = true;
  2285. var newImage = addSvgElementFromJson({
  2286. "element": "image",
  2287. "attr": {
  2288. "x": x,
  2289. "y": y,
  2290. "width": 0,
  2291. "height": 0,
  2292. "id": getNextId(),
  2293. "opacity": cur_shape.opacity / 2,
  2294. "style": "pointer-events:inherit"
  2295. }
  2296. });
  2297. setHref(newImage, last_good_img_url);
  2298. preventClickDefault(newImage);
  2299. break;
  2300. case "square":
  2301. // FIXME: once we create the rect, we lose information that this was a square
  2302. // (for resizing purposes this could be important)
  2303. case "rect":
  2304. started = true;
  2305. start_x = x;
  2306. start_y = y;
  2307. addSvgElementFromJson({
  2308. "element": "rect",
  2309. "curStyles": true,
  2310. "attr": {
  2311. "x": x,
  2312. "y": y,
  2313. "width": 0,
  2314. "height": 0,
  2315. "id": getNextId(),
  2316. "opacity": cur_shape.opacity / 2
  2317. }
  2318. });
  2319. break;
  2320. case "line":
  2321. started = true;
  2322. var stroke_w = cur_shape.stroke_width == 0?1:cur_shape.stroke_width;
  2323. addSvgElementFromJson({
  2324. "element": "line",
  2325. "curStyles": true,
  2326. "attr": {
  2327. "x1": x,
  2328. "y1": y,
  2329. "x2": x,
  2330. "y2": y,
  2331. "id": getNextId(),
  2332. "stroke": cur_shape.stroke,
  2333. "stroke-width": stroke_w,
  2334. "stroke-dasharray": cur_shape.stroke_dasharray,
  2335. "stroke-linejoin": cur_shape.stroke_linejoin,
  2336. "stroke-linecap": cur_shape.stroke_linecap,
  2337. "stroke-opacity": cur_shape.stroke_opacity,
  2338. "fill": "none",
  2339. "opacity": cur_shape.opacity / 2,
  2340. "style": "pointer-events:none"
  2341. }
  2342. });
  2343. break;
  2344. case "circle":
  2345. started = true;
  2346. addSvgElementFromJson({
  2347. "element": "circle",
  2348. "curStyles": true,
  2349. "attr": {
  2350. "cx": x,
  2351. "cy": y,
  2352. "r": 0,
  2353. "id": getNextId(),
  2354. "opacity": cur_shape.opacity / 2
  2355. }
  2356. });
  2357. break;
  2358. case "ellipse":
  2359. started = true;
  2360. addSvgElementFromJson({
  2361. "element": "ellipse",
  2362. "curStyles": true,
  2363. "attr": {
  2364. "cx": x,
  2365. "cy": y,
  2366. "rx": 0,
  2367. "ry": 0,
  2368. "id": getNextId(),
  2369. "opacity": cur_shape.opacity / 2
  2370. }
  2371. });
  2372. break;
  2373. case "text":
  2374. started = true;
  2375. var newText = addSvgElementFromJson({
  2376. "element": "text",
  2377. "curStyles": true,
  2378. "attr": {
  2379. "x": x,
  2380. "y": y,
  2381. "id": getNextId(),
  2382. "fill": cur_text.fill,
  2383. "stroke-width": cur_text.stroke_width,
  2384. "font-size": cur_text.font_size,
  2385. "font-family": cur_text.font_family,
  2386. "text-anchor": "middle",
  2387. "xml:space": "preserve",
  2388. "opacity": cur_shape.opacity
  2389. }
  2390. });
  2391. // newText.textContent = "text";
  2392. break;
  2393. case "path":
  2394. // Fall through
  2395. case "pathedit":
  2396. start_x *= current_zoom;
  2397. start_y *= current_zoom;
  2398. pathActions.mouseDown(evt, mouse_target, start_x, start_y);
  2399. started = true;
  2400. break;
  2401. case "textedit":
  2402. start_x *= current_zoom;
  2403. start_y *= current_zoom;
  2404. textActions.mouseDown(evt, mouse_target, start_x, start_y);
  2405. started = true;
  2406. break;
  2407. case "rotate":
  2408. started = true;
  2409. // we are starting an undoable change (a drag-rotation)
  2410. canvas.undoMgr.beginUndoableChange("transform", selectedElements);
  2411. break;
  2412. default:
  2413. // This could occur in an extension
  2414. break;
  2415. }
  2416. var ext_result = runExtensions("mouseDown", {
  2417. event: evt,
  2418. start_x: start_x,
  2419. start_y: start_y,
  2420. selectedElements: selectedElements
  2421. }, true);
  2422. $.each(ext_result, function(i, r) {
  2423. if(r && r.started) {
  2424. started = true;
  2425. }
  2426. });
  2427. };
  2428. // in this function we do not record any state changes yet (but we do update
  2429. // any elements that are still being created, moved or resized on the canvas)
  2430. var mouseMove = function(evt)
  2431. {
  2432. if (!started) return;
  2433. if(evt.button === 1 || canvas.spaceKey) return;
  2434. var selected = selectedElements[0],
  2435. pt = transformPoint( evt.pageX, evt.pageY, root_sctm ),
  2436. mouse_x = pt.x * current_zoom,
  2437. mouse_y = pt.y * current_zoom,
  2438. shape = getElem(getId());
  2439. var real_x = x = mouse_x / current_zoom;
  2440. var real_y = y = mouse_y / current_zoom;
  2441. if(curConfig.gridSnapping){
  2442. x = snapToGrid(x);
  2443. y = snapToGrid(y);
  2444. }
  2445. evt.preventDefault();
  2446. switch (current_mode)
  2447. {
  2448. case "select":
  2449. // we temporarily use a translate on the element(s) being dragged
  2450. // this transform is removed upon mousing up and the element is
  2451. // relocated to the new location
  2452. if (selectedElements[0] !== null) {
  2453. var dx = x - start_x;
  2454. var dy = y - start_y;
  2455. if(curConfig.gridSnapping){
  2456. dx = snapToGrid(dx);
  2457. dy = snapToGrid(dy);
  2458. }
  2459. if(evt.shiftKey) { var xya = snapToAngle(start_x,start_y,x,y); x=xya.x; y=xya.y; }
  2460. if (dx != 0 || dy != 0) {
  2461. var len = selectedElements.length;
  2462. for (var i = 0; i < len; ++i) {
  2463. var selected = selectedElements[i];
  2464. if (selected == null) break;
  2465. // if (i==0) {
  2466. // var box = svgedit.utilities.getBBox(selected);
  2467. // selectedBBoxes[i].x = box.x + dx;
  2468. // selectedBBoxes[i].y = box.y + dy;
  2469. // }
  2470. // update the dummy transform in our transform list
  2471. // to be a translate
  2472. var xform = svgroot.createSVGTransform();
  2473. var tlist = getTransformList(selected);
  2474. // Note that if Webkit and there's no ID for this
  2475. // element, the dummy transform may have gotten lost.
  2476. // This results in unexpected behaviour
  2477. xform.setTranslate(dx,dy);
  2478. if(tlist.numberOfItems) {
  2479. tlist.replaceItem(xform, 0);
  2480. } else {
  2481. tlist.appendItem(xform);
  2482. }
  2483. // update our internal bbox that we're tracking while dragging
  2484. selectorManager.requestSelector(selected).resize();
  2485. }
  2486. call("transition", selectedElements);
  2487. }
  2488. }
  2489. break;
  2490. case "multiselect":
  2491. real_x *= current_zoom;
  2492. real_y *= current_zoom;
  2493. assignAttributes(rubberBox, {
  2494. 'x': Math.min(r_start_x, real_x),
  2495. 'y': Math.min(r_start_y, real_y),
  2496. 'width': Math.abs(real_x - r_start_x),
  2497. 'height': Math.abs(real_y - r_start_y)
  2498. },100);
  2499. // for each selected:
  2500. // - if newList contains selected, do nothing
  2501. // - if newList doesn't contain selected, remove it from selected
  2502. // - for any newList that was not in selectedElements, add it to selected
  2503. var elemsToRemove = [], elemsToAdd = [],
  2504. newList = getIntersectionList(),
  2505. len = selectedElements.length;
  2506. for (var i = 0; i < len; ++i) {
  2507. var ind = newList.indexOf(selectedElements[i]);
  2508. if (ind == -1) {
  2509. elemsToRemove.push(selectedElements[i]);
  2510. }
  2511. else {
  2512. newList[ind] = null;
  2513. }
  2514. }
  2515. len = newList.length;
  2516. for (i = 0; i < len; ++i) { if (newList[i]) elemsToAdd.push(newList[i]); }
  2517. if (elemsToRemove.length > 0)
  2518. canvas.removeFromSelection(elemsToRemove);
  2519. if (elemsToAdd.length > 0)
  2520. addToSelection(elemsToAdd);
  2521. break;
  2522. case "resize":
  2523. // we track the resize bounding box and translate/scale the selected element
  2524. // while the mouse is down, when mouse goes up, we use this to recalculate
  2525. // the shape's coordinates
  2526. var tlist = getTransformList(selected),
  2527. hasMatrix = hasMatrixTransform(tlist),
  2528. box = hasMatrix ? init_bbox : svgedit.utilities.getBBox(selected),
  2529. left=box.x, top=box.y, width=box.width,
  2530. height=box.height, dx=(x-start_x), dy=(y-start_y);
  2531. if(curConfig.gridSnapping){
  2532. dx = snapToGrid(dx);
  2533. dy = snapToGrid(dy);
  2534. height = snapToGrid(height);
  2535. width = snapToGrid(width);
  2536. }
  2537. // if rotated, adjust the dx,dy values
  2538. var angle = getRotationAngle(selected);
  2539. if (angle) {
  2540. var r = Math.sqrt( dx*dx + dy*dy ),
  2541. theta = Math.atan2(dy,dx) - angle * Math.PI / 180.0;
  2542. dx = r * Math.cos(theta);
  2543. dy = r * Math.sin(theta);
  2544. }
  2545. // if not stretching in y direction, set dy to 0
  2546. // if not stretching in x direction, set dx to 0
  2547. if(current_resize_mode.indexOf("n")==-1 && current_resize_mode.indexOf("s")==-1) {
  2548. dy = 0;
  2549. }
  2550. if(current_resize_mode.indexOf("e")==-1 && current_resize_mode.indexOf("w")==-1) {
  2551. dx = 0;
  2552. }
  2553. var ts = null,
  2554. tx = 0, ty = 0,
  2555. sy = height ? (height+dy)/height : 1,
  2556. sx = width ? (width+dx)/width : 1;
  2557. // if we are dragging on the north side, then adjust the scale factor and ty
  2558. if(current_resize_mode.indexOf("n") >= 0) {
  2559. sy = height ? (height-dy)/height : 1;
  2560. ty = height;
  2561. }
  2562. // if we dragging on the east side, then adjust the scale factor and tx
  2563. if(current_resize_mode.indexOf("w") >= 0) {
  2564. sx = width ? (width-dx)/width : 1;
  2565. tx = width;
  2566. }
  2567. // update the transform list with translate,scale,translate
  2568. var translateOrigin = svgroot.createSVGTransform(),
  2569. scale = svgroot.createSVGTransform(),
  2570. translateBack = svgroot.createSVGTransform();
  2571. if(curConfig.gridSnapping){
  2572. left = snapToGrid(left);
  2573. tx = snapToGrid(tx);
  2574. top = snapToGrid(top);
  2575. ty = snapToGrid(ty);
  2576. }
  2577. translateOrigin.setTranslate(-(left+tx),-(top+ty));
  2578. if(evt.shiftKey) {
  2579. if(sx == 1) sx = sy
  2580. else sy = sx;
  2581. }
  2582. scale.setScale(sx,sy);
  2583. translateBack.setTranslate(left+tx,top+ty);
  2584. if(hasMatrix) {
  2585. var diff = angle?1:0;
  2586. tlist.replaceItem(translateOrigin, 2+diff);
  2587. tlist.replaceItem(scale, 1+diff);
  2588. tlist.replaceItem(translateBack, 0+diff);
  2589. } else {
  2590. var N = tlist.numberOfItems;
  2591. tlist.replaceItem(translateBack, N-3);
  2592. tlist.replaceItem(scale, N-2);
  2593. tlist.replaceItem(translateOrigin, N-1);
  2594. }
  2595. selectorManager.requestSelector(selected).resize();
  2596. call("transition", selectedElements);
  2597. break;
  2598. case "zoom":
  2599. real_x *= current_zoom;
  2600. real_y *= current_zoom;
  2601. assignAttributes(rubberBox, {
  2602. 'x': Math.min(r_start_x*current_zoom, real_x),
  2603. 'y': Math.min(r_start_y*current_zoom, real_y),
  2604. 'width': Math.abs(real_x - r_start_x*current_zoom),
  2605. 'height': Math.abs(real_y - r_start_y*current_zoom)
  2606. },100);
  2607. break;
  2608. case "text":
  2609. assignAttributes(shape,{
  2610. 'x': x,
  2611. 'y': y
  2612. },1000);
  2613. break;
  2614. case "line":
  2615. // Opera has a problem with suspendRedraw() apparently
  2616. var handle = null;
  2617. if (!window.opera) svgroot.suspendRedraw(1000);
  2618. if(curConfig.gridSnapping){
  2619. x = snapToGrid(x);
  2620. y = snapToGrid(y);
  2621. }
  2622. var x2 = x;
  2623. var y2 = y;
  2624. if(evt.shiftKey) { var xya = snapToAngle(start_x,start_y,x2,y2); x2=xya.x; y2=xya.y; }
  2625. shape.setAttributeNS(null, "x2", x2);
  2626. shape.setAttributeNS(null, "y2", y2);
  2627. if (!window.opera) svgroot.unsuspendRedraw(handle);
  2628. break;
  2629. case "foreignObject":
  2630. // fall through
  2631. case "square":
  2632. // fall through
  2633. case "rect":
  2634. // fall through
  2635. case "image":
  2636. var square = (current_mode == 'square') || evt.shiftKey,
  2637. w = Math.abs(x - start_x),
  2638. h = Math.abs(y - start_y),
  2639. new_x, new_y;
  2640. if(square) {
  2641. w = h = Math.max(w, h);
  2642. new_x = start_x < x ? start_x : start_x - w;
  2643. new_y = start_y < y ? start_y : start_y - h;
  2644. } else {
  2645. new_x = Math.min(start_x,x);
  2646. new_y = Math.min(start_y,y);
  2647. }
  2648. if(curConfig.gridSnapping){
  2649. w = snapToGrid(w);
  2650. h = snapToGrid(h);
  2651. new_x = snapToGrid(new_x);
  2652. new_y = snapToGrid(new_y);
  2653. }
  2654. assignAttributes(shape,{
  2655. 'width': w,
  2656. 'height': h,
  2657. 'x': new_x,
  2658. 'y': new_y
  2659. },1000);
  2660. break;
  2661. case "circle":
  2662. var c = $(shape).attr(["cx", "cy"]);
  2663. var cx = c.cx, cy = c.cy,
  2664. rad = Math.sqrt( (x-cx)*(x-cx) + (y-cy)*(y-cy) );
  2665. if(curConfig.gridSnapping){
  2666. rad = snapToGrid(rad);
  2667. }
  2668. shape.setAttributeNS(null, "r", rad);
  2669. break;
  2670. case "ellipse":
  2671. var c = $(shape).attr(["cx", "cy"]);
  2672. var cx = c.cx, cy = c.cy;
  2673. // Opera has a problem with suspendRedraw() apparently
  2674. handle = null;
  2675. if (!window.opera) svgroot.suspendRedraw(1000);
  2676. if(curConfig.gridSnapping){
  2677. x = snapToGrid(x);
  2678. cx = snapToGrid(cx);
  2679. y = snapToGrid(y);
  2680. cy = snapToGrid(cy);
  2681. }
  2682. shape.setAttributeNS(null, "rx", Math.abs(x - cx) );
  2683. var ry = Math.abs(evt.shiftKey?(x - cx):(y - cy));
  2684. shape.setAttributeNS(null, "ry", ry );
  2685. if (!window.opera) svgroot.unsuspendRedraw(handle);
  2686. break;
  2687. case "fhellipse":
  2688. case "fhrect":
  2689. freehand.minx = Math.min(real_x, freehand.minx);
  2690. freehand.maxx = Math.max(real_x, freehand.maxx);
  2691. freehand.miny = Math.min(real_y, freehand.miny);
  2692. freehand.maxy = Math.max(real_y, freehand.maxy);
  2693. // break; missing on purpose
  2694. case "fhpath":
  2695. d_attr += + real_x + "," + real_y + " ";
  2696. shape.setAttributeNS(null, "points", d_attr);
  2697. break;
  2698. // update path stretch line coordinates
  2699. case "path":
  2700. // fall through
  2701. case "pathedit":
  2702. x *= current_zoom;
  2703. y *= current_zoom;
  2704. if(curConfig.gridSnapping){
  2705. x = snapToGrid(x);
  2706. y = snapToGrid(y);
  2707. start_x = snapToGrid(start_x);
  2708. start_y = snapToGrid(start_y);
  2709. }
  2710. if(evt.shiftKey) {
  2711. var path = svgedit.path.path;
  2712. if(path) {
  2713. var x1 = path.dragging?path.dragging[0]:start_x;
  2714. var y1 = path.dragging?path.dragging[1]:start_y;
  2715. } else {
  2716. var x1 = start_x;
  2717. var y1 = start_y;
  2718. }
  2719. var xya = snapToAngle(x1,y1,x,y);
  2720. x=xya.x; y=xya.y;
  2721. }
  2722. if(rubberBox && rubberBox.getAttribute('display') !== 'none') {
  2723. real_x *= current_zoom;
  2724. real_y *= current_zoom;
  2725. assignAttributes(rubberBox, {
  2726. 'x': Math.min(r_start_x*current_zoom, real_x),
  2727. 'y': Math.min(r_start_y*current_zoom, real_y),
  2728. 'width': Math.abs(real_x - r_start_x*current_zoom),
  2729. 'height': Math.abs(real_y - r_start_y*current_zoom)
  2730. },100);
  2731. }
  2732. pathActions.mouseMove(x, y);
  2733. break;
  2734. case "textedit":
  2735. x *= current_zoom;
  2736. y *= current_zoom;
  2737. // if(rubberBox && rubberBox.getAttribute('display') != 'none') {
  2738. // assignAttributes(rubberBox, {
  2739. // 'x': Math.min(start_x,x),
  2740. // 'y': Math.min(start_y,y),
  2741. // 'width': Math.abs(x-start_x),
  2742. // 'height': Math.abs(y-start_y)
  2743. // },100);
  2744. // }
  2745. textActions.mouseMove(mouse_x, mouse_y);
  2746. break;
  2747. case "rotate":
  2748. var box = svgedit.utilities.getBBox(selected),
  2749. cx = box.x + box.width/2,
  2750. cy = box.y + box.height/2,
  2751. m = getMatrix(selected),
  2752. center = transformPoint(cx,cy,m);
  2753. cx = center.x;
  2754. cy = center.y;
  2755. var angle = ((Math.atan2(cy-y,cx-x) * (180/Math.PI))-90) % 360;
  2756. if(curConfig.gridSnapping){
  2757. angle = snapToGrid(angle);
  2758. }
  2759. if(evt.shiftKey) { // restrict rotations to nice angles (WRS)
  2760. var snap = 45;
  2761. angle= Math.round(angle/snap)*snap;
  2762. }
  2763. canvas.setRotationAngle(angle<-180?(360+angle):angle, true);
  2764. call("transition", selectedElements);
  2765. break;
  2766. default:
  2767. break;
  2768. }
  2769. runExtensions("mouseMove", {
  2770. event: evt,
  2771. mouse_x: mouse_x,
  2772. mouse_y: mouse_y,
  2773. selected: selected
  2774. });
  2775. }; // mouseMove()
  2776. // - in create mode, the element's opacity is set properly, we create an InsertElementCommand
  2777. // and store it on the Undo stack
  2778. // - in move/resize mode, the element's attributes which were affected by the move/resize are
  2779. // identified, a ChangeElementCommand is created and stored on the stack for those attrs
  2780. // this is done in when we recalculate the selected dimensions()
  2781. var mouseUp = function(evt)
  2782. {
  2783. if(evt.button === 2) return;
  2784. var tempJustSelected = justSelected;
  2785. justSelected = null;
  2786. if (!started) return;
  2787. var pt = transformPoint( evt.pageX, evt.pageY, root_sctm ),
  2788. mouse_x = pt.x * current_zoom,
  2789. mouse_y = pt.y * current_zoom,
  2790. x = mouse_x / current_zoom,
  2791. y = mouse_y / current_zoom,
  2792. element = getElem(getId()),
  2793. keep = false;
  2794. var real_x = x;
  2795. var real_y = y;
  2796. // TODO: Make true when in multi-unit mode
  2797. var useUnit = false; // (curConfig.baseUnit !== 'px');
  2798. started = false;
  2799. switch (current_mode)
  2800. {
  2801. // intentionally fall-through to select here
  2802. case "resize":
  2803. case "multiselect":
  2804. if (rubberBox != null) {
  2805. rubberBox.setAttribute("display", "none");
  2806. curBBoxes = [];
  2807. }
  2808. current_mode = "select";
  2809. case "select":
  2810. if (selectedElements[0] != null) {
  2811. // if we only have one selected element
  2812. if (selectedElements[1] == null) {
  2813. // set our current stroke/fill properties to the element's
  2814. var selected = selectedElements[0];
  2815. switch ( selected.tagName ) {
  2816. case "g":
  2817. case "use":
  2818. case "image":
  2819. case "foreignObject":
  2820. break;
  2821. default:
  2822. cur_properties.fill = selected.getAttribute("fill");
  2823. cur_properties.fill_opacity = selected.getAttribute("fill-opacity");
  2824. cur_properties.stroke = selected.getAttribute("stroke");
  2825. cur_properties.stroke_opacity = selected.getAttribute("stroke-opacity");
  2826. cur_properties.stroke_width = selected.getAttribute("stroke-width");
  2827. cur_properties.stroke_dasharray = selected.getAttribute("stroke-dasharray");
  2828. cur_properties.stroke_linejoin = selected.getAttribute("stroke-linejoin");
  2829. cur_properties.stroke_linecap = selected.getAttribute("stroke-linecap");
  2830. }
  2831. if (selected.tagName == "text") {
  2832. cur_text.font_size = selected.getAttribute("font-size");
  2833. cur_text.font_family = selected.getAttribute("font-family");
  2834. }
  2835. selectorManager.requestSelector(selected).showGrips(true);
  2836. // This shouldn't be necessary as it was done on mouseDown...
  2837. // call("selected", [selected]);
  2838. }
  2839. // always recalculate dimensions to strip off stray identity transforms
  2840. recalculateAllSelectedDimensions();
  2841. // if it was being dragged/resized
  2842. if (real_x != r_start_x || real_y != r_start_y) {
  2843. var len = selectedElements.length;
  2844. for (var i = 0; i < len; ++i) {
  2845. if (selectedElements[i] == null) break;
  2846. if(!selectedElements[i].firstChild) {
  2847. // Not needed for groups (incorrectly resizes elems), possibly not needed at all?
  2848. selectorManager.requestSelector(selectedElements[i]).resize();
  2849. }
  2850. }
  2851. }
  2852. // no change in position/size, so maybe we should move to pathedit
  2853. else {
  2854. var t = evt.target;
  2855. if (selectedElements[0].nodeName === "path" && selectedElements[1] == null) {
  2856. pathActions.select(selectedElements[0]);
  2857. } // if it was a path
  2858. // else, if it was selected and this is a shift-click, remove it from selection
  2859. else if (evt.shiftKey) {
  2860. if(tempJustSelected != t) {
  2861. canvas.removeFromSelection([t]);
  2862. }
  2863. }
  2864. } // no change in mouse position
  2865. // Remove non-scaling stroke
  2866. if(svgedit.browser.supportsNonScalingStroke()) {
  2867. var elem = selectedElements[0];
  2868. if (elem) {
  2869. elem.removeAttribute('style');
  2870. svgedit.utilities.walkTree(elem, function(elem) {
  2871. elem.removeAttribute('style');
  2872. });
  2873. }
  2874. }
  2875. }
  2876. return;
  2877. break;
  2878. case "zoom":
  2879. if (rubberBox != null) {
  2880. rubberBox.setAttribute("display", "none");
  2881. }
  2882. var factor = evt.shiftKey?.5:2;
  2883. call("zoomed", {
  2884. 'x': Math.min(r_start_x, real_x),
  2885. 'y': Math.min(r_start_y, real_y),
  2886. 'width': Math.abs(real_x - r_start_x),
  2887. 'height': Math.abs(real_y - r_start_y),
  2888. 'factor': factor
  2889. });
  2890. return;
  2891. case "fhpath":
  2892. // Check that the path contains at least 2 points; a degenerate one-point path
  2893. // causes problems.
  2894. // Webkit ignores how we set the points attribute with commas and uses space
  2895. // to separate all coordinates, see https://bugs.webkit.org/show_bug.cgi?id=29870
  2896. var coords = element.getAttribute('points');
  2897. var commaIndex = coords.indexOf(',');
  2898. if (commaIndex >= 0) {
  2899. keep = coords.indexOf(',', commaIndex+1) >= 0;
  2900. } else {
  2901. keep = coords.indexOf(' ', coords.indexOf(' ')+1) >= 0;
  2902. }
  2903. if (keep) {
  2904. element = pathActions.smoothPolylineIntoPath(element);
  2905. }
  2906. break;
  2907. case "line":
  2908. var attrs = $(element).attr(["x1", "x2", "y1", "y2"]);
  2909. keep = (attrs.x1 != attrs.x2 || attrs.y1 != attrs.y2);
  2910. break;
  2911. case "foreignObject":
  2912. case "square":
  2913. case "rect":
  2914. case "image":
  2915. var attrs = $(element).attr(["width", "height"]);
  2916. // Image should be kept regardless of size (use inherit dimensions later)
  2917. keep = (attrs.width != 0 || attrs.height != 0) || current_mode === "image";
  2918. break;
  2919. case "circle":
  2920. keep = (element.getAttribute('r') != 0);
  2921. break;
  2922. case "ellipse":
  2923. var attrs = $(element).attr(["rx", "ry"]);
  2924. keep = (attrs.rx != null || attrs.ry != null);
  2925. break;
  2926. case "fhellipse":
  2927. if ((freehand.maxx - freehand.minx) > 0 &&
  2928. (freehand.maxy - freehand.miny) > 0) {
  2929. element = addSvgElementFromJson({
  2930. "element": "ellipse",
  2931. "curStyles": true,
  2932. "attr": {
  2933. "cx": (freehand.minx + freehand.maxx) / 2,
  2934. "cy": (freehand.miny + freehand.maxy) / 2,
  2935. "rx": (freehand.maxx - freehand.minx) / 2,
  2936. "ry": (freehand.maxy - freehand.miny) / 2,
  2937. "id": getId()
  2938. }
  2939. });
  2940. call("changed",[element]);
  2941. keep = true;
  2942. }
  2943. break;
  2944. case "fhrect":
  2945. if ((freehand.maxx - freehand.minx) > 0 &&
  2946. (freehand.maxy - freehand.miny) > 0) {
  2947. element = addSvgElementFromJson({
  2948. "element": "rect",
  2949. "curStyles": true,
  2950. "attr": {
  2951. "x": freehand.minx,
  2952. "y": freehand.miny,
  2953. "width": (freehand.maxx - freehand.minx),
  2954. "height": (freehand.maxy - freehand.miny),
  2955. "id": getId()
  2956. }
  2957. });
  2958. call("changed",[element]);
  2959. keep = true;
  2960. }
  2961. break;
  2962. case "text":
  2963. keep = true;
  2964. selectOnly([element]);
  2965. textActions.start(element);
  2966. break;
  2967. case "path":
  2968. // set element to null here so that it is not removed nor finalized
  2969. element = null;
  2970. // continue to be set to true so that mouseMove happens
  2971. started = true;
  2972. var res = pathActions.mouseUp(evt, element, mouse_x, mouse_y);
  2973. element = res.element
  2974. keep = res.keep;
  2975. break;
  2976. case "pathedit":
  2977. keep = true;
  2978. element = null;
  2979. pathActions.mouseUp(evt);
  2980. break;
  2981. case "textedit":
  2982. keep = false;
  2983. element = null;
  2984. textActions.mouseUp(evt, mouse_x, mouse_y);
  2985. break;
  2986. case "rotate":
  2987. keep = true;
  2988. element = null;
  2989. current_mode = "select";
  2990. var batchCmd = canvas.undoMgr.finishUndoableChange();
  2991. if (!batchCmd.isEmpty()) {
  2992. addCommandToHistory(batchCmd);
  2993. }
  2994. // perform recalculation to weed out any stray identity transforms that might get stuck
  2995. recalculateAllSelectedDimensions();
  2996. call("changed", selectedElements);
  2997. break;
  2998. default:
  2999. // This could occur in an extension
  3000. break;
  3001. }
  3002. var ext_result = runExtensions("mouseUp", {
  3003. event: evt,
  3004. mouse_x: mouse_x,
  3005. mouse_y: mouse_y
  3006. }, true);
  3007. $.each(ext_result, function(i, r) {
  3008. if(r) {
  3009. keep = r.keep || keep;
  3010. element = r.element;
  3011. started = r.started || started;
  3012. }
  3013. });
  3014. if (!keep && element != null) {
  3015. getCurrentDrawing().releaseId(getId());
  3016. element.parentNode.removeChild(element);
  3017. element = null;
  3018. var t = evt.target;
  3019. // if this element is in a group, go up until we reach the top-level group
  3020. // just below the layer groups
  3021. // TODO: once we implement links, we also would have to check for <a> elements
  3022. while (t.parentNode.parentNode.tagName == "g") {
  3023. t = t.parentNode;
  3024. }
  3025. // if we are not in the middle of creating a path, and we've clicked on some shape,
  3026. // then go to Select mode.
  3027. // WebKit returns <div> when the canvas is clicked, Firefox/Opera return <svg>
  3028. if ( (current_mode != "path" || !drawn_path) &&
  3029. t.parentNode.id != "selectorParentGroup" &&
  3030. t.id != "svgcanvas" && t.id != "svgroot")
  3031. {
  3032. // switch into "select" mode if we've clicked on an element
  3033. canvas.setMode("select");
  3034. selectOnly([t], true);
  3035. }
  3036. } else if (element != null) {
  3037. canvas.addedNew = true;
  3038. if(useUnit) svgedit.units.convertAttrs(element);
  3039. var ani_dur = .2, c_ani;
  3040. if(opac_ani.beginElement && element.getAttribute('opacity') != cur_shape.opacity) {
  3041. c_ani = $(opac_ani).clone().attr({
  3042. to: cur_shape.opacity,
  3043. dur: ani_dur
  3044. }).appendTo(element);
  3045. try {
  3046. // Fails in FF4 on foreignObject
  3047. c_ani[0].beginElement();
  3048. } catch(e){}
  3049. } else {
  3050. ani_dur = 0;
  3051. }
  3052. // Ideally this would be done on the endEvent of the animation,
  3053. // but that doesn't seem to be supported in Webkit
  3054. setTimeout(function() {
  3055. if(c_ani) c_ani.remove();
  3056. element.setAttribute("opacity", cur_shape.opacity);
  3057. element.setAttribute("style", "pointer-events:inherit");
  3058. cleanupElement(element);
  3059. if(current_mode === "path") {
  3060. pathActions.toEditMode(element);
  3061. } else {
  3062. if(curConfig.selectNew) {
  3063. selectOnly([element], true);
  3064. }
  3065. }
  3066. // we create the insert command that is stored on the stack
  3067. // undo means to call cmd.unapply(), redo means to call cmd.apply()
  3068. addCommandToHistory(new InsertElementCommand(element));
  3069. call("changed",[element]);
  3070. }, ani_dur * 1000);
  3071. }
  3072. start_transform = null;
  3073. };
  3074. var dblClick = function(evt) {
  3075. var evt_target = evt.target;
  3076. var parent = evt_target.parentNode;
  3077. // Do nothing if already in current group
  3078. if(parent === current_group) return;
  3079. var mouse_target = getMouseTarget(evt);
  3080. var tagName = mouse_target.tagName;
  3081. if(tagName === 'text' && current_mode !== 'textedit') {
  3082. var pt = transformPoint( evt.pageX, evt.pageY, root_sctm );
  3083. textActions.select(mouse_target, pt.x, pt.y);
  3084. }
  3085. if((tagName === "g" || tagName === "a") && getRotationAngle(mouse_target)) {
  3086. // TODO: Allow method of in-group editing without having to do
  3087. // this (similar to editing rotated paths)
  3088. // Ungroup and regroup
  3089. pushGroupProperties(mouse_target);
  3090. mouse_target = selectedElements[0];
  3091. clearSelection(true);
  3092. }
  3093. // Reset context
  3094. if(current_group) {
  3095. leaveContext();
  3096. }
  3097. if((parent.tagName !== 'g' && parent.tagName !== 'a') ||
  3098. parent === getCurrentDrawing().getCurrentLayer() ||
  3099. mouse_target === selectorManager.selectorParentGroup)
  3100. {
  3101. // Escape from in-group edit
  3102. return;
  3103. }
  3104. setContext(mouse_target);
  3105. }
  3106. // prevent links from being followed in the canvas
  3107. var handleLinkInCanvas = function(e) {
  3108. e.preventDefault();
  3109. return false;
  3110. };
  3111. // Added mouseup to the container here.
  3112. // TODO(codedread): Figure out why after the Closure compiler, the window mouseup is ignored.
  3113. $(container).mousedown(mouseDown).mousemove(mouseMove).click(handleLinkInCanvas).dblclick(dblClick).mouseup(mouseUp);
  3114. // $(window).mouseup(mouseUp);
  3115. $(container).bind("mousewheel DOMMouseScroll", function(e){
  3116. if(!e.shiftKey) return;
  3117. e.preventDefault();
  3118. root_sctm = svgcontent.getScreenCTM().inverse();
  3119. var pt = transformPoint( e.pageX, e.pageY, root_sctm );
  3120. var bbox = {
  3121. 'x': pt.x,
  3122. 'y': pt.y,
  3123. 'width': 0,
  3124. 'height': 0
  3125. };
  3126. // Respond to mouse wheel in IE/Webkit/Opera.
  3127. // (It returns up/dn motion in multiples of 120)
  3128. if(e.wheelDelta) {
  3129. if (e.wheelDelta >= 120) {
  3130. bbox.factor = 2;
  3131. } else if (e.wheelDelta <= -120) {
  3132. bbox.factor = .5;
  3133. }
  3134. } else if(e.detail) {
  3135. if (e.detail > 0) {
  3136. bbox.factor = .5;
  3137. } else if (e.detail < 0) {
  3138. bbox.factor = 2;
  3139. }
  3140. }
  3141. if(!bbox.factor) return;
  3142. call("zoomed", bbox);
  3143. });
  3144. }());
  3145. // Function: preventClickDefault
  3146. // Prevents default browser click behaviour on the given element
  3147. //
  3148. // Parameters:
  3149. // img - The DOM element to prevent the cilck on
  3150. var preventClickDefault = function(img) {
  3151. $(img).click(function(e){e.preventDefault()});
  3152. }
  3153. // Group: Text edit functions
  3154. // Functions relating to editing text elements
  3155. var textActions = canvas.textActions = function() {
  3156. var curtext;
  3157. var textinput;
  3158. var cursor;
  3159. var selblock;
  3160. var blinker;
  3161. var chardata = [];
  3162. var textbb, transbb;
  3163. var matrix;
  3164. var last_x, last_y;
  3165. var allow_dbl;
  3166. function setCursor(index) {
  3167. var empty = (textinput.value === "");
  3168. $(textinput).focus();
  3169. if(!arguments.length) {
  3170. if(empty) {
  3171. index = 0;
  3172. } else {
  3173. if(textinput.selectionEnd !== textinput.selectionStart) return;
  3174. index = textinput.selectionEnd;
  3175. }
  3176. }
  3177. var charbb;
  3178. charbb = chardata[index];
  3179. if(!empty) {
  3180. textinput.setSelectionRange(index, index);
  3181. }
  3182. cursor = getElem("text_cursor");
  3183. if (!cursor) {
  3184. cursor = document.createElementNS(svgns, "line");
  3185. assignAttributes(cursor, {
  3186. 'id': "text_cursor",
  3187. 'stroke': "#333",
  3188. 'stroke-width': 1
  3189. });
  3190. cursor = getElem("selectorParentGroup").appendChild(cursor);
  3191. }
  3192. if(!blinker) {
  3193. blinker = setInterval(function() {
  3194. var show = (cursor.getAttribute('display') === 'none');
  3195. cursor.setAttribute('display', show?'inline':'none');
  3196. }, 600);
  3197. }
  3198. var start_pt = ptToScreen(charbb.x, textbb.y);
  3199. var end_pt = ptToScreen(charbb.x, (textbb.y + textbb.height));
  3200. assignAttributes(cursor, {
  3201. x1: start_pt.x,
  3202. y1: start_pt.y,
  3203. x2: end_pt.x,
  3204. y2: end_pt.y,
  3205. visibility: 'visible',
  3206. display: 'inline'
  3207. });
  3208. if(selblock) selblock.setAttribute('d', '');
  3209. }
  3210. function setSelection(start, end, skipInput) {
  3211. if(start === end) {
  3212. setCursor(end);
  3213. return;
  3214. }
  3215. if(!skipInput) {
  3216. textinput.setSelectionRange(start, end);
  3217. }
  3218. selblock = getElem("text_selectblock");
  3219. if (!selblock) {
  3220. selblock = document.createElementNS(svgns, "path");
  3221. assignAttributes(selblock, {
  3222. 'id': "text_selectblock",
  3223. 'fill': "green",
  3224. 'opacity': .5,
  3225. 'style': "pointer-events:none"
  3226. });
  3227. getElem("selectorParentGroup").appendChild(selblock);
  3228. }
  3229. var startbb = chardata[start];
  3230. var endbb = chardata[end];
  3231. cursor.setAttribute('visibility', 'hidden');
  3232. var tl = ptToScreen(startbb.x, textbb.y),
  3233. tr = ptToScreen(startbb.x + (endbb.x - startbb.x), textbb.y),
  3234. bl = ptToScreen(startbb.x, textbb.y + textbb.height),
  3235. br = ptToScreen(startbb.x + (endbb.x - startbb.x), textbb.y + textbb.height);
  3236. var dstr = "M" + tl.x + "," + tl.y
  3237. + " L" + tr.x + "," + tr.y
  3238. + " " + br.x + "," + br.y
  3239. + " " + bl.x + "," + bl.y + "z";
  3240. assignAttributes(selblock, {
  3241. d: dstr,
  3242. 'display': 'inline'
  3243. });
  3244. }
  3245. function getIndexFromPoint(mouse_x, mouse_y) {
  3246. // Position cursor here
  3247. var pt = svgroot.createSVGPoint();
  3248. pt.x = mouse_x;
  3249. pt.y = mouse_y;
  3250. // No content, so return 0
  3251. if(chardata.length == 1) return 0;
  3252. // Determine if cursor should be on left or right of character
  3253. var charpos = curtext.getCharNumAtPosition(pt);
  3254. if(charpos < 0) {
  3255. // Out of text range, look at mouse coords
  3256. charpos = chardata.length - 2;
  3257. if(mouse_x <= chardata[0].x) {
  3258. charpos = 0;
  3259. }
  3260. } else if(charpos >= chardata.length - 2) {
  3261. charpos = chardata.length - 2;
  3262. }
  3263. var charbb = chardata[charpos];
  3264. var mid = charbb.x + (charbb.width/2);
  3265. if(mouse_x > mid) {
  3266. charpos++;
  3267. }
  3268. return charpos;
  3269. }
  3270. function setCursorFromPoint(mouse_x, mouse_y) {
  3271. setCursor(getIndexFromPoint(mouse_x, mouse_y));
  3272. }
  3273. function setEndSelectionFromPoint(x, y, apply) {
  3274. var i1 = textinput.selectionStart;
  3275. var i2 = getIndexFromPoint(x, y);
  3276. var start = Math.min(i1, i2);
  3277. var end = Math.max(i1, i2);
  3278. setSelection(start, end, !apply);
  3279. }
  3280. function screenToPt(x_in, y_in) {
  3281. var out = {
  3282. x: x_in,
  3283. y: y_in
  3284. }
  3285. out.x /= current_zoom;
  3286. out.y /= current_zoom;
  3287. if(matrix) {
  3288. var pt = transformPoint(out.x, out.y, matrix.inverse());
  3289. out.x = pt.x;
  3290. out.y = pt.y;
  3291. }
  3292. return out;
  3293. }
  3294. function ptToScreen(x_in, y_in) {
  3295. var out = {
  3296. x: x_in,
  3297. y: y_in
  3298. }
  3299. if(matrix) {
  3300. var pt = transformPoint(out.x, out.y, matrix);
  3301. out.x = pt.x;
  3302. out.y = pt.y;
  3303. }
  3304. out.x *= current_zoom;
  3305. out.y *= current_zoom;
  3306. return out;
  3307. }
  3308. function hideCursor() {
  3309. if(cursor) {
  3310. cursor.setAttribute('visibility', 'hidden');
  3311. }
  3312. }
  3313. function selectAll(evt) {
  3314. setSelection(0, curtext.textContent.length);
  3315. $(this).unbind(evt);
  3316. }
  3317. function selectWord(evt) {
  3318. if(!allow_dbl || !curtext) return;
  3319. var ept = transformPoint( evt.pageX, evt.pageY, root_sctm ),
  3320. mouse_x = ept.x * current_zoom,
  3321. mouse_y = ept.y * current_zoom;
  3322. var pt = screenToPt(mouse_x, mouse_y);
  3323. var index = getIndexFromPoint(pt.x, pt.y);
  3324. var str = curtext.textContent;
  3325. var first = str.substr(0, index).replace(/[a-z0-9]+$/i, '').length;
  3326. var m = str.substr(index).match(/^[a-z0-9]+/i);
  3327. var last = (m?m[0].length:0) + index;
  3328. setSelection(first, last);
  3329. // Set tripleclick
  3330. $(evt.target).click(selectAll);
  3331. setTimeout(function() {
  3332. $(evt.target).unbind('click', selectAll);
  3333. }, 300);
  3334. }
  3335. return {
  3336. select: function(target, x, y) {
  3337. curtext = target;
  3338. textActions.toEditMode(x, y);
  3339. },
  3340. start: function(elem) {
  3341. curtext = elem;
  3342. textActions.toEditMode();
  3343. },
  3344. mouseDown: function(evt, mouse_target, start_x, start_y) {
  3345. var pt = screenToPt(start_x, start_y);
  3346. textinput.focus();
  3347. setCursorFromPoint(pt.x, pt.y);
  3348. last_x = start_x;
  3349. last_y = start_y;
  3350. // TODO: Find way to block native selection
  3351. },
  3352. mouseMove: function(mouse_x, mouse_y) {
  3353. var pt = screenToPt(mouse_x, mouse_y);
  3354. setEndSelectionFromPoint(pt.x, pt.y);
  3355. },
  3356. mouseUp: function(evt, mouse_x, mouse_y) {
  3357. var pt = screenToPt(mouse_x, mouse_y);
  3358. setEndSelectionFromPoint(pt.x, pt.y, true);
  3359. // TODO: Find a way to make this work: Use transformed BBox instead of evt.target
  3360. // if(last_x === mouse_x && last_y === mouse_y
  3361. // && !svgedit.math.rectsIntersect(transbb, {x: pt.x, y: pt.y, width:0, height:0})) {
  3362. // textActions.toSelectMode(true);
  3363. // }
  3364. if(
  3365. evt.target !== curtext
  3366. && mouse_x < last_x + 2
  3367. && mouse_x > last_x - 2
  3368. && mouse_y < last_y + 2
  3369. && mouse_y > last_y - 2) {
  3370. textActions.toSelectMode(true);
  3371. }
  3372. },
  3373. setCursor: setCursor,
  3374. toEditMode: function(x, y) {
  3375. allow_dbl = false;
  3376. current_mode = "textedit";
  3377. selectorManager.requestSelector(curtext).showGrips(false);
  3378. // Make selector group accept clicks
  3379. var sel = selectorManager.requestSelector(curtext).selectorRect;
  3380. textActions.init();
  3381. $(curtext).css('cursor', 'text');
  3382. // if(svgedit.browser.supportsEditableText()) {
  3383. // curtext.setAttribute('editable', 'simple');
  3384. // return;
  3385. // }
  3386. if(!arguments.length) {
  3387. setCursor();
  3388. } else {
  3389. var pt = screenToPt(x, y);
  3390. setCursorFromPoint(pt.x, pt.y);
  3391. }
  3392. setTimeout(function() {
  3393. allow_dbl = true;
  3394. }, 300);
  3395. },
  3396. toSelectMode: function(selectElem) {
  3397. current_mode = "select";
  3398. clearInterval(blinker);
  3399. blinker = null;
  3400. if(selblock) $(selblock).attr('display','none');
  3401. if(cursor) $(cursor).attr('visibility','hidden');
  3402. $(curtext).css('cursor', 'move');
  3403. if(selectElem) {
  3404. clearSelection();
  3405. $(curtext).css('cursor', 'move');
  3406. call("selected", [curtext]);
  3407. addToSelection([curtext], true);
  3408. }
  3409. if(curtext && !curtext.textContent.length) {
  3410. // No content, so delete
  3411. canvas.deleteSelectedElements();
  3412. }
  3413. $(textinput).blur();
  3414. curtext = false;
  3415. // if(svgedit.browser.supportsEditableText()) {
  3416. // curtext.removeAttribute('editable');
  3417. // }
  3418. },
  3419. setInputElem: function(elem) {
  3420. textinput = elem;
  3421. // $(textinput).blur(hideCursor);
  3422. },
  3423. clear: function() {
  3424. if(current_mode == "textedit") {
  3425. textActions.toSelectMode();
  3426. }
  3427. },
  3428. init: function(inputElem) {
  3429. if(!curtext) return;
  3430. // if(svgedit.browser.supportsEditableText()) {
  3431. // curtext.select();
  3432. // return;
  3433. // }
  3434. if(!curtext.parentNode) {
  3435. // Result of the ffClone, need to get correct element
  3436. curtext = selectedElements[0];
  3437. selectorManager.requestSelector(curtext).showGrips(false);
  3438. }
  3439. var str = curtext.textContent;
  3440. var len = str.length;
  3441. var xform = curtext.getAttribute('transform');
  3442. textbb = svgedit.utilities.getBBox(curtext);
  3443. matrix = xform?getMatrix(curtext):null;
  3444. chardata = Array(len);
  3445. textinput.focus();
  3446. $(curtext).unbind('dblclick', selectWord).dblclick(selectWord);
  3447. if(!len) {
  3448. var end = {x: textbb.x + (textbb.width/2), width: 0};
  3449. }
  3450. for(var i=0; i<len; i++) {
  3451. var start = curtext.getStartPositionOfChar(i);
  3452. var end = curtext.getEndPositionOfChar(i);
  3453. if(!svgedit.browser.supportsGoodTextCharPos()) {
  3454. var offset = canvas.contentW * current_zoom;
  3455. start.x -= offset;
  3456. end.x -= offset;
  3457. start.x /= current_zoom;
  3458. end.x /= current_zoom;
  3459. }
  3460. // Get a "bbox" equivalent for each character. Uses the
  3461. // bbox data of the actual text for y, height purposes
  3462. // TODO: Decide if y, width and height are actually necessary
  3463. chardata[i] = {
  3464. x: start.x,
  3465. y: textbb.y, // start.y?
  3466. width: end.x - start.x,
  3467. height: textbb.height
  3468. };
  3469. }
  3470. // Add a last bbox for cursor at end of text
  3471. chardata.push({
  3472. x: end.x,
  3473. width: 0
  3474. });
  3475. setSelection(textinput.selectionStart, textinput.selectionEnd, true);
  3476. }
  3477. }
  3478. }();
  3479. // TODO: Migrate all of this code into path.js
  3480. // Group: Path edit functions
  3481. // Functions relating to editing path elements
  3482. var pathActions = canvas.pathActions = function() {
  3483. var subpath = false;
  3484. var current_path;
  3485. var newPoint, firstCtrl;
  3486. function resetD(p) {
  3487. p.setAttribute("d", pathActions.convertPath(p));
  3488. }
  3489. // TODO: Move into path.js
  3490. svgedit.path.Path.prototype.endChanges = function(text) {
  3491. if(svgedit.browser.isWebkit()) resetD(this.elem);
  3492. var cmd = new ChangeElementCommand(this.elem, {d: this.last_d}, text);
  3493. addCommandToHistory(cmd);
  3494. call("changed", [this.elem]);
  3495. }
  3496. svgedit.path.Path.prototype.addPtsToSelection = function(indexes) {
  3497. if(!$.isArray(indexes)) indexes = [indexes];
  3498. for(var i=0; i< indexes.length; i++) {
  3499. var index = indexes[i];
  3500. var seg = this.segs[index];
  3501. if(seg.ptgrip) {
  3502. if(this.selected_pts.indexOf(index) == -1 && index >= 0) {
  3503. this.selected_pts.push(index);
  3504. }
  3505. }
  3506. };
  3507. this.selected_pts.sort();
  3508. var i = this.selected_pts.length,
  3509. grips = new Array(i);
  3510. // Loop through points to be selected and highlight each
  3511. while(i--) {
  3512. var pt = this.selected_pts[i];
  3513. var seg = this.segs[pt];
  3514. seg.select(true);
  3515. grips[i] = seg.ptgrip;
  3516. }
  3517. // TODO: Correct this:
  3518. pathActions.canDeleteNodes = true;
  3519. pathActions.closed_subpath = this.subpathIsClosed(this.selected_pts[0]);
  3520. call("selected", grips);
  3521. }
  3522. var current_path = null,
  3523. drawn_path = null,
  3524. hasMoved = false;
  3525. // This function converts a polyline (created by the fh_path tool) into
  3526. // a path element and coverts every three line segments into a single bezier
  3527. // curve in an attempt to smooth out the free-hand
  3528. var smoothPolylineIntoPath = function(element) {
  3529. var points = element.points;
  3530. var N = points.numberOfItems;
  3531. if (N >= 4) {
  3532. // loop through every 3 points and convert to a cubic bezier curve segment
  3533. //
  3534. // NOTE: this is cheating, it means that every 3 points has the potential to
  3535. // be a corner instead of treating each point in an equal manner. In general,
  3536. // this technique does not look that good.
  3537. //
  3538. // I am open to better ideas!
  3539. //
  3540. // Reading:
  3541. // - http://www.efg2.com/Lab/Graphics/Jean-YvesQueinecBezierCurves.htm
  3542. // - http://www.codeproject.com/KB/graphics/BezierSpline.aspx?msg=2956963
  3543. // - http://www.ian-ko.com/ET_GeoWizards/UserGuide/smooth.htm
  3544. // - http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/bezier-der.html
  3545. var curpos = points.getItem(0), prevCtlPt = null;
  3546. var d = [];
  3547. d.push(["M",curpos.x,",",curpos.y," C"].join(""));
  3548. for (var i = 1; i <= (N-4); i += 3) {
  3549. var ct1 = points.getItem(i);
  3550. var ct2 = points.getItem(i+1);
  3551. var end = points.getItem(i+2);
  3552. // if the previous segment had a control point, we want to smooth out
  3553. // the control points on both sides
  3554. if (prevCtlPt) {
  3555. var newpts = svgedit.path.smoothControlPoints( prevCtlPt, ct1, curpos );
  3556. if (newpts && newpts.length == 2) {
  3557. var prevArr = d[d.length-1].split(',');
  3558. prevArr[2] = newpts[0].x;
  3559. prevArr[3] = newpts[0].y;
  3560. d[d.length-1] = prevArr.join(',');
  3561. ct1 = newpts[1];
  3562. }
  3563. }
  3564. d.push([ct1.x,ct1.y,ct2.x,ct2.y,end.x,end.y].join(','));
  3565. curpos = end;
  3566. prevCtlPt = ct2;
  3567. }
  3568. // handle remaining line segments
  3569. d.push("L");
  3570. for(;i < N;++i) {
  3571. var pt = points.getItem(i);
  3572. d.push([pt.x,pt.y].join(","));
  3573. }
  3574. d = d.join(" ");
  3575. // create new path element
  3576. element = addSvgElementFromJson({
  3577. "element": "path",
  3578. "curStyles": true,
  3579. "attr": {
  3580. "id": getId(),
  3581. "d": d,
  3582. "fill": "none"
  3583. }
  3584. });
  3585. // No need to call "changed", as this is already done under mouseUp
  3586. }
  3587. return element;
  3588. };
  3589. return {
  3590. mouseDown: function(evt, mouse_target, start_x, start_y) {
  3591. if(current_mode === "path") {
  3592. mouse_x = start_x;
  3593. mouse_y = start_y;
  3594. var x = mouse_x/current_zoom,
  3595. y = mouse_y/current_zoom,
  3596. stretchy = getElem("path_stretch_line");
  3597. newPoint = [x, y];
  3598. if(curConfig.gridSnapping){
  3599. x = snapToGrid(x);
  3600. y = snapToGrid(y);
  3601. mouse_x = snapToGrid(mouse_x);
  3602. mouse_y = snapToGrid(mouse_y);
  3603. }
  3604. if (!stretchy) {
  3605. stretchy = document.createElementNS(svgns, "path");
  3606. assignAttributes(stretchy, {
  3607. 'id': "path_stretch_line",
  3608. 'stroke': "#22C",
  3609. 'stroke-width': "0.5",
  3610. 'fill': 'none'
  3611. });
  3612. stretchy = getElem("selectorParentGroup").appendChild(stretchy);
  3613. }
  3614. stretchy.setAttribute("display", "inline");
  3615. var keep = null;
  3616. // if pts array is empty, create path element with M at current point
  3617. if (!drawn_path) {
  3618. d_attr = "M" + x + "," + y + " ";
  3619. drawn_path = addSvgElementFromJson({
  3620. "element": "path",
  3621. "curStyles": true,
  3622. "attr": {
  3623. "d": d_attr,
  3624. "id": getNextId(),
  3625. "opacity": cur_shape.opacity / 2
  3626. }
  3627. });
  3628. // set stretchy line to first point
  3629. stretchy.setAttribute('d', ['M', mouse_x, mouse_y, mouse_x, mouse_y].join(' '));
  3630. var index = subpath ? svgedit.path.path.segs.length : 0;
  3631. svgedit.path.addPointGrip(index, mouse_x, mouse_y);
  3632. }
  3633. else {
  3634. // determine if we clicked on an existing point
  3635. var seglist = drawn_path.pathSegList;
  3636. var i = seglist.numberOfItems;
  3637. var FUZZ = 6/current_zoom;
  3638. var clickOnPoint = false;
  3639. while(i) {
  3640. i --;
  3641. var item = seglist.getItem(i);
  3642. var px = item.x, py = item.y;
  3643. // found a matching point
  3644. if ( x >= (px-FUZZ) && x <= (px+FUZZ) && y >= (py-FUZZ) && y <= (py+FUZZ) ) {
  3645. clickOnPoint = true;
  3646. break;
  3647. }
  3648. }
  3649. // get path element that we are in the process of creating
  3650. var id = getId();
  3651. // Remove previous path object if previously created
  3652. svgedit.path.removePath_(id);
  3653. var newpath = getElem(id);
  3654. var len = seglist.numberOfItems;
  3655. // if we clicked on an existing point, then we are done this path, commit it
  3656. // (i,i+1) are the x,y that were clicked on
  3657. if (clickOnPoint) {
  3658. // if clicked on any other point but the first OR
  3659. // the first point was clicked on and there are less than 3 points
  3660. // then leave the path open
  3661. // otherwise, close the path
  3662. if (i <= 1 && len >= 2) {
  3663. // Create end segment
  3664. var abs_x = seglist.getItem(0).x;
  3665. var abs_y = seglist.getItem(0).y;
  3666. var s_seg = stretchy.pathSegList.getItem(1);
  3667. if(s_seg.pathSegType === 4) {
  3668. var newseg = drawn_path.createSVGPathSegLinetoAbs(abs_x, abs_y);
  3669. } else {
  3670. var newseg = drawn_path.createSVGPathSegCurvetoCubicAbs(
  3671. abs_x,
  3672. abs_y,
  3673. s_seg.x1 / current_zoom,
  3674. s_seg.y1 / current_zoom,
  3675. abs_x,
  3676. abs_y
  3677. );
  3678. }
  3679. var endseg = drawn_path.createSVGPathSegClosePath();
  3680. seglist.appendItem(newseg);
  3681. seglist.appendItem(endseg);
  3682. } else if(len < 3) {
  3683. keep = false;
  3684. return keep;
  3685. }
  3686. $(stretchy).remove();
  3687. // this will signal to commit the path
  3688. element = newpath;
  3689. drawn_path = null;
  3690. started = false;
  3691. if(subpath) {
  3692. if(svgedit.path.path.matrix) {
  3693. remapElement(newpath, {}, svgedit.path.path.matrix.inverse());
  3694. }
  3695. var new_d = newpath.getAttribute("d");
  3696. var orig_d = $(svgedit.path.path.elem).attr("d");
  3697. $(svgedit.path.path.elem).attr("d", orig_d + new_d);
  3698. $(newpath).remove();
  3699. if(svgedit.path.path.matrix) {
  3700. svgedit.path.recalcRotatedPath();
  3701. }
  3702. svgedit.path.path.init();
  3703. pathActions.toEditMode(svgedit.path.path.elem);
  3704. svgedit.path.path.selectPt();
  3705. return false;
  3706. }
  3707. }
  3708. // else, create a new point, update path element
  3709. else {
  3710. // Checks if current target or parents are #svgcontent
  3711. if(!$.contains(container, getMouseTarget(evt))) {
  3712. // Clicked outside canvas, so don't make point
  3713. console.log("Clicked outside canvas");
  3714. return false;
  3715. }
  3716. var num = drawn_path.pathSegList.numberOfItems;
  3717. var last = drawn_path.pathSegList.getItem(num -1);
  3718. var lastx = last.x, lasty = last.y;
  3719. if(evt.shiftKey) { var xya = snapToAngle(lastx,lasty,x,y); x=xya.x; y=xya.y; }
  3720. // Use the segment defined by stretchy
  3721. var s_seg = stretchy.pathSegList.getItem(1);
  3722. if(s_seg.pathSegType === 4) {
  3723. var newseg = drawn_path.createSVGPathSegLinetoAbs(round(x), round(y));
  3724. } else {
  3725. var newseg = drawn_path.createSVGPathSegCurvetoCubicAbs(
  3726. round(x),
  3727. round(y),
  3728. s_seg.x1 / current_zoom,
  3729. s_seg.y1 / current_zoom,
  3730. s_seg.x2 / current_zoom,
  3731. s_seg.y2 / current_zoom
  3732. );
  3733. }
  3734. drawn_path.pathSegList.appendItem(newseg);
  3735. x *= current_zoom;
  3736. y *= current_zoom;
  3737. // set stretchy line to latest point
  3738. stretchy.setAttribute('d', ['M', x, y, x, y].join(' '));
  3739. var index = num;
  3740. if(subpath) index += svgedit.path.path.segs.length;
  3741. svgedit.path.addPointGrip(index, x, y);
  3742. }
  3743. // keep = true;
  3744. }
  3745. return;
  3746. }
  3747. // TODO: Make sure current_path isn't null at this point
  3748. if(!svgedit.path.path) return;
  3749. svgedit.path.path.storeD();
  3750. var id = evt.target.id;
  3751. if (id.substr(0,14) == "pathpointgrip_") {
  3752. // Select this point
  3753. var cur_pt = svgedit.path.path.cur_pt = parseInt(id.substr(14));
  3754. svgedit.path.path.dragging = [start_x, start_y];
  3755. var seg = svgedit.path.path.segs[cur_pt];
  3756. // only clear selection if shift is not pressed (otherwise, add
  3757. // node to selection)
  3758. if (!evt.shiftKey) {
  3759. if(svgedit.path.path.selected_pts.length <= 1 || !seg.selected) {
  3760. svgedit.path.path.clearSelection();
  3761. }
  3762. svgedit.path.path.addPtsToSelection(cur_pt);
  3763. } else if(seg.selected) {
  3764. svgedit.path.path.removePtFromSelection(cur_pt);
  3765. } else {
  3766. svgedit.path.path.addPtsToSelection(cur_pt);
  3767. }
  3768. } else if(id.indexOf("ctrlpointgrip_") == 0) {
  3769. svgedit.path.path.dragging = [start_x, start_y];
  3770. var parts = id.split('_')[1].split('c');
  3771. var cur_pt = parts[0]-0;
  3772. var ctrl_num = parts[1]-0;
  3773. svgedit.path.path.selectPt(cur_pt, ctrl_num);
  3774. }
  3775. // Start selection box
  3776. if(!svgedit.path.path.dragging) {
  3777. if (rubberBox == null) {
  3778. rubberBox = selectorManager.getRubberBandBox();
  3779. }
  3780. assignAttributes(rubberBox, {
  3781. 'x': start_x * current_zoom,
  3782. 'y': start_y * current_zoom,
  3783. 'width': 0,
  3784. 'height': 0,
  3785. 'display': 'inline'
  3786. }, 100);
  3787. }
  3788. },
  3789. mouseMove: function(mouse_x, mouse_y) {
  3790. hasMoved = true;
  3791. if(current_mode === "path") {
  3792. if(!drawn_path) return;
  3793. var seglist = drawn_path.pathSegList;
  3794. var index = seglist.numberOfItems - 1;
  3795. if(newPoint) {
  3796. // First point
  3797. // if(!index) return;
  3798. // Set control points
  3799. var pointGrip1 = svgedit.path.addCtrlGrip('1c1');
  3800. var pointGrip2 = svgedit.path.addCtrlGrip('0c2');
  3801. // dragging pointGrip1
  3802. pointGrip1.setAttribute('cx', mouse_x);
  3803. pointGrip1.setAttribute('cy', mouse_y);
  3804. pointGrip1.setAttribute('display', 'inline');
  3805. var pt_x = newPoint[0];
  3806. var pt_y = newPoint[1];
  3807. // set curve
  3808. var seg = seglist.getItem(index);
  3809. var cur_x = mouse_x / current_zoom;
  3810. var cur_y = mouse_y / current_zoom;
  3811. var alt_x = (pt_x + (pt_x - cur_x));
  3812. var alt_y = (pt_y + (pt_y - cur_y));
  3813. pointGrip2.setAttribute('cx', alt_x * current_zoom);
  3814. pointGrip2.setAttribute('cy', alt_y * current_zoom);
  3815. pointGrip2.setAttribute('display', 'inline');
  3816. var ctrlLine = svgedit.path.getCtrlLine(1);
  3817. assignAttributes(ctrlLine, {
  3818. x1: mouse_x,
  3819. y1: mouse_y,
  3820. x2: alt_x * current_zoom,
  3821. y2: alt_y * current_zoom,
  3822. display: 'inline'
  3823. });
  3824. if(index === 0) {
  3825. firstCtrl = [mouse_x, mouse_y];
  3826. } else {
  3827. var last_x, last_y;
  3828. var last = seglist.getItem(index - 1);
  3829. var last_x = last.x;
  3830. var last_y = last.y
  3831. if(last.pathSegType === 6) {
  3832. last_x += (last_x - last.x2);
  3833. last_y += (last_y - last.y2);
  3834. } else if(firstCtrl) {
  3835. last_x = firstCtrl[0]/current_zoom;
  3836. last_y = firstCtrl[1]/current_zoom;
  3837. }
  3838. svgedit.path.replacePathSeg(6, index, [pt_x, pt_y, last_x, last_y, alt_x, alt_y], drawn_path);
  3839. }
  3840. } else {
  3841. var stretchy = getElem("path_stretch_line");
  3842. if (stretchy) {
  3843. var prev = seglist.getItem(index);
  3844. if(prev.pathSegType === 6) {
  3845. var prev_x = prev.x + (prev.x - prev.x2);
  3846. var prev_y = prev.y + (prev.y - prev.y2);
  3847. svgedit.path.replacePathSeg(6, 1, [mouse_x, mouse_y, prev_x * current_zoom, prev_y * current_zoom, mouse_x, mouse_y], stretchy);
  3848. } else if(firstCtrl) {
  3849. svgedit.path.replacePathSeg(6, 1, [mouse_x, mouse_y, firstCtrl[0], firstCtrl[1], mouse_x, mouse_y], stretchy);
  3850. } else {
  3851. svgedit.path.replacePathSeg(4, 1, [mouse_x, mouse_y], stretchy);
  3852. }
  3853. }
  3854. }
  3855. return;
  3856. }
  3857. // if we are dragging a point, let's move it
  3858. if (svgedit.path.path.dragging) {
  3859. var pt = svgedit.path.getPointFromGrip({
  3860. x: svgedit.path.path.dragging[0],
  3861. y: svgedit.path.path.dragging[1]
  3862. }, svgedit.path.path);
  3863. var mpt = svgedit.path.getPointFromGrip({
  3864. x: mouse_x,
  3865. y: mouse_y
  3866. }, svgedit.path.path);
  3867. var diff_x = mpt.x - pt.x;
  3868. var diff_y = mpt.y - pt.y;
  3869. svgedit.path.path.dragging = [mouse_x, mouse_y];
  3870. if(svgedit.path.path.dragctrl) {
  3871. svgedit.path.path.moveCtrl(diff_x, diff_y);
  3872. } else {
  3873. svgedit.path.path.movePts(diff_x, diff_y);
  3874. }
  3875. } else {
  3876. svgedit.path.path.selected_pts = [];
  3877. svgedit.path.path.eachSeg(function(i) {
  3878. var seg = this;
  3879. if(!seg.next && !seg.prev) return;
  3880. var item = seg.item;
  3881. var rbb = rubberBox.getBBox();
  3882. var pt = svgedit.path.getGripPt(seg);
  3883. var pt_bb = {
  3884. x: pt.x,
  3885. y: pt.y,
  3886. width: 0,
  3887. height: 0
  3888. };
  3889. var sel = svgedit.math.rectsIntersect(rbb, pt_bb);
  3890. this.select(sel);
  3891. //Note that addPtsToSelection is not being run
  3892. if(sel) svgedit.path.path.selected_pts.push(seg.index);
  3893. });
  3894. }
  3895. },
  3896. mouseUp: function(evt, element, mouse_x, mouse_y) {
  3897. // Create mode
  3898. if(current_mode === "path") {
  3899. newPoint = null;
  3900. if(!drawn_path) {
  3901. element = getElem(getId());
  3902. started = false;
  3903. firstCtrl = null;
  3904. }
  3905. return {
  3906. keep: true,
  3907. element: element
  3908. }
  3909. }
  3910. // Edit mode
  3911. if (svgedit.path.path.dragging) {
  3912. var last_pt = svgedit.path.path.cur_pt;
  3913. svgedit.path.path.dragging = false;
  3914. svgedit.path.path.dragctrl = false;
  3915. svgedit.path.path.update();
  3916. if(hasMoved) {
  3917. svgedit.path.path.endChanges("Move path point(s)");
  3918. }
  3919. if(!evt.shiftKey && !hasMoved) {
  3920. svgedit.path.path.selectPt(last_pt);
  3921. }
  3922. }
  3923. else if(rubberBox && rubberBox.getAttribute('display') != 'none') {
  3924. // Done with multi-node-select
  3925. rubberBox.setAttribute("display", "none");
  3926. if(rubberBox.getAttribute('width') <= 2 && rubberBox.getAttribute('height') <= 2) {
  3927. pathActions.toSelectMode(evt.target);
  3928. }
  3929. // else, move back to select mode
  3930. } else {
  3931. pathActions.toSelectMode(evt.target);
  3932. }
  3933. hasMoved = false;
  3934. },
  3935. toEditMode: function(element) {
  3936. svgedit.path.path = svgedit.path.getPath_(element);
  3937. current_mode = "pathedit";
  3938. clearSelection();
  3939. svgedit.path.path.show(true).update();
  3940. svgedit.path.path.oldbbox = svgedit.utilities.getBBox(svgedit.path.path.elem);
  3941. subpath = false;
  3942. },
  3943. toSelectMode: function(elem) {
  3944. var selPath = (elem == svgedit.path.path.elem);
  3945. current_mode = "select";
  3946. svgedit.path.path.show(false);
  3947. current_path = false;
  3948. clearSelection();
  3949. if(svgedit.path.path.matrix) {
  3950. // Rotated, so may need to re-calculate the center
  3951. svgedit.path.recalcRotatedPath();
  3952. }
  3953. if(selPath) {
  3954. call("selected", [elem]);
  3955. addToSelection([elem], true);
  3956. }
  3957. },
  3958. addSubPath: function(on) {
  3959. if(on) {
  3960. // Internally we go into "path" mode, but in the UI it will
  3961. // still appear as if in "pathedit" mode.
  3962. current_mode = "path";
  3963. subpath = true;
  3964. } else {
  3965. pathActions.clear(true);
  3966. pathActions.toEditMode(svgedit.path.path.elem);
  3967. }
  3968. },
  3969. select: function(target) {
  3970. if (current_path === target) {
  3971. pathActions.toEditMode(target);
  3972. current_mode = "pathedit";
  3973. } // going into pathedit mode
  3974. else {
  3975. current_path = target;
  3976. }
  3977. },
  3978. reorient: function() {
  3979. var elem = selectedElements[0];
  3980. if(!elem) return;
  3981. var angle = getRotationAngle(elem);
  3982. if(angle == 0) return;
  3983. var batchCmd = new BatchCommand("Reorient path");
  3984. var changes = {
  3985. d: elem.getAttribute('d'),
  3986. transform: elem.getAttribute('transform')
  3987. };
  3988. batchCmd.addSubCommand(new ChangeElementCommand(elem, changes));
  3989. clearSelection();
  3990. this.resetOrientation(elem);
  3991. addCommandToHistory(batchCmd);
  3992. // Set matrix to null
  3993. svgedit.path.getPath_(elem).show(false).matrix = null;
  3994. this.clear();
  3995. addToSelection([elem], true);
  3996. call("changed", selectedElements);
  3997. },
  3998. clear: function(remove) {
  3999. current_path = null;
  4000. if (drawn_path) {
  4001. var elem = getElem(getId());
  4002. $(getElem("path_stretch_line")).remove();
  4003. $(elem).remove();
  4004. $(getElem("pathpointgrip_container")).find('*').attr('display', 'none');
  4005. drawn_path = firstCtrl = null;
  4006. started = false;
  4007. } else if (current_mode == "pathedit") {
  4008. this.toSelectMode();
  4009. }
  4010. if(svgedit.path.path) svgedit.path.path.init().show(false);
  4011. },
  4012. resetOrientation: function(path) {
  4013. if(path == null || path.nodeName != 'path') return false;
  4014. var tlist = getTransformList(path);
  4015. var m = transformListToTransform(tlist).matrix;
  4016. tlist.clear();
  4017. path.removeAttribute("transform");
  4018. var segList = path.pathSegList;
  4019. // Opera/win/non-EN throws an error here.
  4020. // TODO: Find out why!
  4021. // Presumed fixed in Opera 10.5, so commented out for now
  4022. // try {
  4023. var len = segList.numberOfItems;
  4024. // } catch(err) {
  4025. // var fixed_d = pathActions.convertPath(path);
  4026. // path.setAttribute('d', fixed_d);
  4027. // segList = path.pathSegList;
  4028. // var len = segList.numberOfItems;
  4029. // }
  4030. var last_x, last_y;
  4031. for (var i = 0; i < len; ++i) {
  4032. var seg = segList.getItem(i);
  4033. var type = seg.pathSegType;
  4034. if(type == 1) continue;
  4035. var pts = [];
  4036. $.each(['',1,2], function(j, n) {
  4037. var x = seg['x'+n], y = seg['y'+n];
  4038. if(x !== undefined && y !== undefined) {
  4039. var pt = transformPoint(x, y, m);
  4040. pts.splice(pts.length, 0, pt.x, pt.y);
  4041. }
  4042. });
  4043. svgedit.path.replacePathSeg(type, i, pts, path);
  4044. }
  4045. reorientGrads(path, m);
  4046. },
  4047. zoomChange: function() {
  4048. if(current_mode == "pathedit") {
  4049. svgedit.path.path.update();
  4050. }
  4051. },
  4052. getNodePoint: function() {
  4053. var sel_pt = svgedit.path.path.selected_pts.length ? svgedit.path.path.selected_pts[0] : 1;
  4054. var seg = svgedit.path.path.segs[sel_pt];
  4055. return {
  4056. x: seg.item.x,
  4057. y: seg.item.y,
  4058. type: seg.type
  4059. };
  4060. },
  4061. linkControlPoints: function(linkPoints) {
  4062. svgedit.path.setLinkControlPoints(linkPoints);
  4063. },
  4064. clonePathNode: function() {
  4065. svgedit.path.path.storeD();
  4066. var sel_pts = svgedit.path.path.selected_pts;
  4067. var segs = svgedit.path.path.segs;
  4068. var i = sel_pts.length;
  4069. var nums = [];
  4070. while(i--) {
  4071. var pt = sel_pts[i];
  4072. svgedit.path.path.addSeg(pt);
  4073. nums.push(pt + i);
  4074. nums.push(pt + i + 1);
  4075. }
  4076. svgedit.path.path.init().addPtsToSelection(nums);
  4077. svgedit.path.path.endChanges("Clone path node(s)");
  4078. },
  4079. opencloseSubPath: function() {
  4080. var sel_pts = svgedit.path.path.selected_pts;
  4081. // Only allow one selected node for now
  4082. if(sel_pts.length !== 1) return;
  4083. var elem = svgedit.path.path.elem;
  4084. var list = elem.pathSegList;
  4085. var len = list.numberOfItems;
  4086. var index = sel_pts[0];
  4087. var open_pt = null;
  4088. var start_item = null;
  4089. // Check if subpath is already open
  4090. svgedit.path.path.eachSeg(function(i) {
  4091. if(this.type === 2 && i <= index) {
  4092. start_item = this.item;
  4093. }
  4094. if(i <= index) return true;
  4095. if(this.type === 2) {
  4096. // Found M first, so open
  4097. open_pt = i;
  4098. return false;
  4099. } else if(this.type === 1) {
  4100. // Found Z first, so closed
  4101. open_pt = false;
  4102. return false;
  4103. }
  4104. });
  4105. if(open_pt == null) {
  4106. // Single path, so close last seg
  4107. open_pt = svgedit.path.path.segs.length - 1;
  4108. }
  4109. if(open_pt !== false) {
  4110. // Close this path
  4111. // Create a line going to the previous "M"
  4112. var newseg = elem.createSVGPathSegLinetoAbs(start_item.x, start_item.y);
  4113. var closer = elem.createSVGPathSegClosePath();
  4114. if(open_pt == svgedit.path.path.segs.length - 1) {
  4115. list.appendItem(newseg);
  4116. list.appendItem(closer);
  4117. } else {
  4118. svgedit.path.insertItemBefore(elem, closer, open_pt);
  4119. svgedit.path.insertItemBefore(elem, newseg, open_pt);
  4120. }
  4121. svgedit.path.path.init().selectPt(open_pt+1);
  4122. return;
  4123. }
  4124. // M 1,1 L 2,2 L 3,3 L 1,1 z // open at 2,2
  4125. // M 2,2 L 3,3 L 1,1
  4126. // M 1,1 L 2,2 L 1,1 z M 4,4 L 5,5 L6,6 L 5,5 z
  4127. // M 1,1 L 2,2 L 1,1 z [M 4,4] L 5,5 L(M)6,6 L 5,5 z
  4128. var seg = svgedit.path.path.segs[index];
  4129. if(seg.mate) {
  4130. list.removeItem(index); // Removes last "L"
  4131. list.removeItem(index); // Removes the "Z"
  4132. svgedit.path.path.init().selectPt(index - 1);
  4133. return;
  4134. }
  4135. var last_m, z_seg;
  4136. // Find this sub-path's closing point and remove
  4137. for(var i=0; i<list.numberOfItems; i++) {
  4138. var item = list.getItem(i);
  4139. if(item.pathSegType === 2) {
  4140. // Find the preceding M
  4141. last_m = i;
  4142. } else if(i === index) {
  4143. // Remove it
  4144. list.removeItem(last_m);
  4145. // index--;
  4146. } else if(item.pathSegType === 1 && index < i) {
  4147. // Remove the closing seg of this subpath
  4148. z_seg = i-1;
  4149. list.removeItem(i);
  4150. break;
  4151. }
  4152. }
  4153. var num = (index - last_m) - 1;
  4154. while(num--) {
  4155. svgedit.path.insertItemBefore(elem, list.getItem(last_m), z_seg);
  4156. }
  4157. var pt = list.getItem(last_m);
  4158. // Make this point the new "M"
  4159. svgedit.path.replacePathSeg(2, last_m, [pt.x, pt.y]);
  4160. var i = index
  4161. svgedit.path.path.init().selectPt(0);
  4162. },
  4163. deletePathNode: function() {
  4164. if(!pathActions.canDeleteNodes) return;
  4165. svgedit.path.path.storeD();
  4166. var sel_pts = svgedit.path.path.selected_pts;
  4167. var i = sel_pts.length;
  4168. while(i--) {
  4169. var pt = sel_pts[i];
  4170. svgedit.path.path.deleteSeg(pt);
  4171. }
  4172. // Cleanup
  4173. var cleanup = function() {
  4174. var segList = svgedit.path.path.elem.pathSegList;
  4175. var len = segList.numberOfItems;
  4176. var remItems = function(pos, count) {
  4177. while(count--) {
  4178. segList.removeItem(pos);
  4179. }
  4180. }
  4181. if(len <= 1) return true;
  4182. while(len--) {
  4183. var item = segList.getItem(len);
  4184. if(item.pathSegType === 1) {
  4185. var prev = segList.getItem(len-1);
  4186. var nprev = segList.getItem(len-2);
  4187. if(prev.pathSegType === 2) {
  4188. remItems(len-1, 2);
  4189. cleanup();
  4190. break;
  4191. } else if(nprev.pathSegType === 2) {
  4192. remItems(len-2, 3);
  4193. cleanup();
  4194. break;
  4195. }
  4196. } else if(item.pathSegType === 2) {
  4197. if(len > 0) {
  4198. var prev_type = segList.getItem(len-1).pathSegType;
  4199. // Path has M M
  4200. if(prev_type === 2) {
  4201. remItems(len-1, 1);
  4202. cleanup();
  4203. break;
  4204. // Entire path ends with Z M
  4205. } else if(prev_type === 1 && segList.numberOfItems-1 === len) {
  4206. remItems(len, 1);
  4207. cleanup();
  4208. break;
  4209. }
  4210. }
  4211. }
  4212. }
  4213. return false;
  4214. }
  4215. cleanup();
  4216. // Completely delete a path with 1 or 0 segments
  4217. if(svgedit.path.path.elem.pathSegList.numberOfItems <= 1) {
  4218. pathActions.toSelectMode(svgedit.path.path.elem);
  4219. canvas.deleteSelectedElements();
  4220. return;
  4221. }
  4222. svgedit.path.path.init();
  4223. svgedit.path.path.clearSelection();
  4224. // TODO: Find right way to select point now
  4225. // path.selectPt(sel_pt);
  4226. if(window.opera) { // Opera repaints incorrectly
  4227. var cp = $(svgedit.path.path.elem); cp.attr('d',cp.attr('d'));
  4228. }
  4229. svgedit.path.path.endChanges("Delete path node(s)");
  4230. },
  4231. smoothPolylineIntoPath: smoothPolylineIntoPath,
  4232. setSegType: function(v) {
  4233. svgedit.path.path.setSegType(v);
  4234. },
  4235. moveNode: function(attr, newValue) {
  4236. var sel_pts = svgedit.path.path.selected_pts;
  4237. if(!sel_pts.length) return;
  4238. svgedit.path.path.storeD();
  4239. // Get first selected point
  4240. var seg = svgedit.path.path.segs[sel_pts[0]];
  4241. var diff = {x:0, y:0};
  4242. diff[attr] = newValue - seg.item[attr];
  4243. seg.move(diff.x, diff.y);
  4244. svgedit.path.path.endChanges("Move path point");
  4245. },
  4246. fixEnd: function(elem) {
  4247. // Adds an extra segment if the last seg before a Z doesn't end
  4248. // at its M point
  4249. // M0,0 L0,100 L100,100 z
  4250. var segList = elem.pathSegList;
  4251. var len = segList.numberOfItems;
  4252. var last_m;
  4253. for (var i = 0; i < len; ++i) {
  4254. var item = segList.getItem(i);
  4255. if(item.pathSegType === 2) {
  4256. last_m = item;
  4257. }
  4258. if(item.pathSegType === 1) {
  4259. var prev = segList.getItem(i-1);
  4260. if(prev.x != last_m.x || prev.y != last_m.y) {
  4261. // Add an L segment here
  4262. var newseg = elem.createSVGPathSegLinetoAbs(last_m.x, last_m.y);
  4263. svgedit.path.insertItemBefore(elem, newseg, i);
  4264. // Can this be done better?
  4265. pathActions.fixEnd(elem);
  4266. break;
  4267. }
  4268. }
  4269. }
  4270. if(svgedit.browser.isWebkit()) resetD(elem);
  4271. },
  4272. // Convert a path to one with only absolute or relative values
  4273. convertPath: function(path, toRel) {
  4274. var segList = path.pathSegList;
  4275. var len = segList.numberOfItems;
  4276. var curx = 0, cury = 0;
  4277. var d = "";
  4278. var last_m = null;
  4279. for (var i = 0; i < len; ++i) {
  4280. var seg = segList.getItem(i);
  4281. // if these properties are not in the segment, set them to zero
  4282. var x = seg.x || 0,
  4283. y = seg.y || 0,
  4284. x1 = seg.x1 || 0,
  4285. y1 = seg.y1 || 0,
  4286. x2 = seg.x2 || 0,
  4287. y2 = seg.y2 || 0;
  4288. var type = seg.pathSegType;
  4289. var letter = pathMap[type]['to'+(toRel?'Lower':'Upper')+'Case']();
  4290. var addToD = function(pnts, more, last) {
  4291. var str = '';
  4292. var more = more?' '+more.join(' '):'';
  4293. var last = last?' '+svgedit.units.shortFloat(last):'';
  4294. $.each(pnts, function(i, pnt) {
  4295. pnts[i] = svgedit.units.shortFloat(pnt);
  4296. });
  4297. d += letter + pnts.join(' ') + more + last;
  4298. }
  4299. switch (type) {
  4300. case 1: // z,Z closepath (Z/z)
  4301. d += "z";
  4302. break;
  4303. case 12: // absolute horizontal line (H)
  4304. x -= curx;
  4305. case 13: // relative horizontal line (h)
  4306. if(toRel) {
  4307. curx += x;
  4308. letter = 'l';
  4309. } else {
  4310. x += curx;
  4311. curx = x;
  4312. letter = 'L';
  4313. }
  4314. // Convert to "line" for easier editing
  4315. addToD([[x, cury]]);
  4316. break;
  4317. case 14: // absolute vertical line (V)
  4318. y -= cury;
  4319. case 15: // relative vertical line (v)
  4320. if(toRel) {
  4321. cury += y;
  4322. letter = 'l';
  4323. } else {
  4324. y += cury;
  4325. cury = y;
  4326. letter = 'L';
  4327. }
  4328. // Convert to "line" for easier editing
  4329. addToD([[curx, y]]);
  4330. break;
  4331. case 2: // absolute move (M)
  4332. case 4: // absolute line (L)
  4333. case 18: // absolute smooth quad (T)
  4334. x -= curx;
  4335. y -= cury;
  4336. case 5: // relative line (l)
  4337. case 3: // relative move (m)
  4338. // If the last segment was a "z", this must be relative to
  4339. if(last_m && segList.getItem(i-1).pathSegType === 1 && !toRel) {
  4340. curx = last_m[0];
  4341. cury = last_m[1];
  4342. }
  4343. case 19: // relative smooth quad (t)
  4344. if(toRel) {
  4345. curx += x;
  4346. cury += y;
  4347. } else {
  4348. x += curx;
  4349. y += cury;
  4350. curx = x;
  4351. cury = y;
  4352. }
  4353. if(type === 3) last_m = [curx, cury];
  4354. addToD([[x,y]]);
  4355. break;
  4356. case 6: // absolute cubic (C)
  4357. x -= curx; x1 -= curx; x2 -= curx;
  4358. y -= cury; y1 -= cury; y2 -= cury;
  4359. case 7: // relative cubic (c)
  4360. if(toRel) {
  4361. curx += x;
  4362. cury += y;
  4363. } else {
  4364. x += curx; x1 += curx; x2 += curx;
  4365. y += cury; y1 += cury; y2 += cury;
  4366. curx = x;
  4367. cury = y;
  4368. }
  4369. addToD([[x1,y1],[x2,y2],[x,y]]);
  4370. break;
  4371. case 8: // absolute quad (Q)
  4372. x -= curx; x1 -= curx;
  4373. y -= cury; y1 -= cury;
  4374. case 9: // relative quad (q)
  4375. if(toRel) {
  4376. curx += x;
  4377. cury += y;
  4378. } else {
  4379. x += curx; x1 += curx;
  4380. y += cury; y1 += cury;
  4381. curx = x;
  4382. cury = y;
  4383. }
  4384. addToD([[x1,y1],[x,y]]);
  4385. break;
  4386. case 10: // absolute elliptical arc (A)
  4387. x -= curx;
  4388. y -= cury;
  4389. case 11: // relative elliptical arc (a)
  4390. if(toRel) {
  4391. curx += x;
  4392. cury += y;
  4393. } else {
  4394. x += curx;
  4395. y += cury;
  4396. curx = x;
  4397. cury = y;
  4398. }
  4399. addToD([[seg.r1,seg.r2]], [
  4400. seg.angle,
  4401. (seg.largeArcFlag ? 1 : 0),
  4402. (seg.sweepFlag ? 1 : 0)
  4403. ],[x,y]
  4404. );
  4405. break;
  4406. case 16: // absolute smooth cubic (S)
  4407. x -= curx; x2 -= curx;
  4408. y -= cury; y2 -= cury;
  4409. case 17: // relative smooth cubic (s)
  4410. if(toRel) {
  4411. curx += x;
  4412. cury += y;
  4413. } else {
  4414. x += curx; x2 += curx;
  4415. y += cury; y2 += cury;
  4416. curx = x;
  4417. cury = y;
  4418. }
  4419. addToD([[x2,y2],[x,y]]);
  4420. break;
  4421. } // switch on path segment type
  4422. } // for each segment
  4423. return d;
  4424. }
  4425. }
  4426. }();
  4427. // end pathActions
  4428. // Group: Serialization
  4429. // Function: removeUnusedDefElems
  4430. // Looks at DOM elements inside the <defs> to see if they are referred to,
  4431. // removes them from the DOM if they are not.
  4432. //
  4433. // Returns:
  4434. // The amount of elements that were removed
  4435. var removeUnusedDefElems = this.removeUnusedDefElems = function() {
  4436. var defs = svgcontent.getElementsByTagNameNS(svgns, "defs");
  4437. if(!defs || !defs.length) return 0;
  4438. // if(!defs.firstChild) return;
  4439. var defelem_uses = [],
  4440. numRemoved = 0;
  4441. var attrs = ['fill', 'stroke', 'filter', 'marker-start', 'marker-mid', 'marker-end'];
  4442. var alen = attrs.length;
  4443. var all_els = svgcontent.getElementsByTagNameNS(svgns, '*');
  4444. var all_len = all_els.length;
  4445. for(var i=0; i<all_len; i++) {
  4446. var el = all_els[i];
  4447. for(var j = 0; j < alen; j++) {
  4448. var ref = getUrlFromAttr(el.getAttribute(attrs[j]));
  4449. if(ref) {
  4450. defelem_uses.push(ref.substr(1));
  4451. }
  4452. }
  4453. // gradients can refer to other gradients
  4454. var href = getHref(el);
  4455. if (href && href.indexOf('#') === 0) {
  4456. defelem_uses.push(href.substr(1));
  4457. }
  4458. };
  4459. var defelems = $(svgcontent).find("linearGradient, radialGradient, filter, marker, svg, symbol");
  4460. defelem_ids = [],
  4461. i = defelems.length;
  4462. while (i--) {
  4463. var defelem = defelems[i];
  4464. var id = defelem.id;
  4465. if(defelem_uses.indexOf(id) < 0) {
  4466. // Not found, so remove (but remember)
  4467. removedElements[id] = defelem;
  4468. defelem.parentNode.removeChild(defelem);
  4469. numRemoved++;
  4470. }
  4471. }
  4472. return numRemoved;
  4473. }
  4474. // Function: svgCanvasToString
  4475. // Main function to set up the SVG content for output
  4476. //
  4477. // Returns:
  4478. // String containing the SVG image for output
  4479. this.svgCanvasToString = function() {
  4480. // keep calling it until there are none to remove
  4481. while (removeUnusedDefElems() > 0) {};
  4482. pathActions.clear(true);
  4483. // Keep SVG-Edit comment on top
  4484. $.each(svgcontent.childNodes, function(i, node) {
  4485. if(i && node.nodeType === 8 && node.data.indexOf('Created with') >= 0) {
  4486. svgcontent.insertBefore(node, svgcontent.firstChild);
  4487. }
  4488. });
  4489. // Move out of in-group editing mode
  4490. if(current_group) {
  4491. leaveContext();
  4492. selectOnly([current_group]);
  4493. }
  4494. var naked_svgs = [];
  4495. // Unwrap gsvg if it has no special attributes (only id and style)
  4496. $(svgcontent).find('g:data(gsvg)').each(function() {
  4497. var attrs = this.attributes;
  4498. var len = attrs.length;
  4499. for(var i=0; i<len; i++) {
  4500. if(attrs[i].nodeName == 'id' || attrs[i].nodeName == 'style') {
  4501. len--;
  4502. }
  4503. }
  4504. // No significant attributes, so ungroup
  4505. if(len <= 0) {
  4506. var svg = this.firstChild;
  4507. naked_svgs.push(svg);
  4508. $(this).replaceWith(svg);
  4509. }
  4510. });
  4511. var output = this.svgToString(svgcontent, 0);
  4512. // Rewrap gsvg
  4513. if(naked_svgs.length) {
  4514. $(naked_svgs).each(function() {
  4515. groupSvgElem(this);
  4516. });
  4517. }
  4518. return output;
  4519. };
  4520. // Function: svgToString
  4521. // Sub function ran on each SVG element to convert it to a string as desired
  4522. //
  4523. // Parameters:
  4524. // elem - The SVG element to convert
  4525. // indent - Integer with the amount of spaces to indent this tag
  4526. //
  4527. // Returns:
  4528. // String with the given element as an SVG tag
  4529. this.svgToString = function(elem, indent) {
  4530. var out = new Array(), toXml = svgedit.utilities.toXml;
  4531. var unit = curConfig.baseUnit;
  4532. var unit_re = new RegExp('^-?[\\d\\.]+' + unit + '$');
  4533. if (elem) {
  4534. cleanupElement(elem);
  4535. var attrs = elem.attributes,
  4536. attr,
  4537. i,
  4538. childs = elem.childNodes;
  4539. for (var i=0; i<indent; i++) out.push(" ");
  4540. out.push("<"); out.push(elem.nodeName);
  4541. if(elem.id === 'svgcontent') {
  4542. // Process root element separately
  4543. var res = getResolution();
  4544. var vb = "";
  4545. // TODO: Allow this by dividing all values by current baseVal
  4546. // Note that this also means we should properly deal with this on import
  4547. // if(curConfig.baseUnit !== "px") {
  4548. // var unit = curConfig.baseUnit;
  4549. // var unit_m = svgedit.units.getTypeMap()[unit];
  4550. // res.w = svgedit.units.shortFloat(res.w / unit_m)
  4551. // res.h = svgedit.units.shortFloat(res.h / unit_m)
  4552. // vb = ' viewBox="' + [0, 0, res.w, res.h].join(' ') + '"';
  4553. // res.w += unit;
  4554. // res.h += unit;
  4555. // }
  4556. if(unit !== "px") {
  4557. res.w = svgedit.units.convertUnit(res.w, unit) + unit;
  4558. res.h = svgedit.units.convertUnit(res.h, unit) + unit;
  4559. }
  4560. out.push(' width="' + res.w + '" height="' + res.h + '"' + vb + ' xmlns="'+svgns+'"');
  4561. var nsuris = {};
  4562. // Check elements for namespaces, add if found
  4563. $(elem).find('*').andSelf().each(function() {
  4564. var el = this;
  4565. $.each(this.attributes, function(i, attr) {
  4566. var uri = attr.namespaceURI;
  4567. if(uri && !nsuris[uri] && nsMap[uri] !== 'xmlns' && nsMap[uri] !== 'xml' ) {
  4568. nsuris[uri] = true;
  4569. out.push(" xmlns:" + nsMap[uri] + '="' + uri +'"');
  4570. }
  4571. });
  4572. });
  4573. var i = attrs.length;
  4574. var attr_names = ['width','height','xmlns','x','y','viewBox','id','overflow'];
  4575. while (i--) {
  4576. attr = attrs.item(i);
  4577. var attrVal = toXml(attr.nodeValue);
  4578. // Namespaces have already been dealt with, so skip
  4579. if(attr.nodeName.indexOf('xmlns:') === 0) continue;
  4580. // only serialize attributes we don't use internally
  4581. if (attrVal != "" && attr_names.indexOf(attr.localName) == -1)
  4582. {
  4583. if(!attr.namespaceURI || nsMap[attr.namespaceURI]) {
  4584. out.push(' ');
  4585. out.push(attr.nodeName); out.push("=\"");
  4586. out.push(attrVal); out.push("\"");
  4587. }
  4588. }
  4589. }
  4590. } else {
  4591. // Skip empty defs
  4592. if(elem.nodeName === 'defs' && !elem.firstChild) return;
  4593. var moz_attrs = ['-moz-math-font-style', '_moz-math-font-style'];
  4594. for (var i=attrs.length-1; i>=0; i--) {
  4595. attr = attrs.item(i);
  4596. var attrVal = toXml(attr.nodeValue);
  4597. //remove bogus attributes added by Gecko
  4598. if (moz_attrs.indexOf(attr.localName) >= 0) continue;
  4599. if (attrVal != "") {
  4600. if(attrVal.indexOf('pointer-events') === 0) continue;
  4601. if(attr.localName === "class" && attrVal.indexOf('se_') === 0) continue;
  4602. out.push(" ");
  4603. if(attr.localName === 'd') attrVal = pathActions.convertPath(elem, true);
  4604. if(!isNaN(attrVal)) {
  4605. attrVal = svgedit.units.shortFloat(attrVal);
  4606. } else if(unit_re.test(attrVal)) {
  4607. attrVal = svgedit.units.shortFloat(attrVal) + unit;
  4608. }
  4609. // Embed images when saving
  4610. if(save_options.apply
  4611. && elem.nodeName === 'image'
  4612. && attr.localName === 'href'
  4613. && save_options.images
  4614. && save_options.images === 'embed')
  4615. {
  4616. var img = encodableImages[attrVal];
  4617. if(img) attrVal = img;
  4618. }
  4619. // map various namespaces to our fixed namespace prefixes
  4620. // (the default xmlns attribute itself does not get a prefix)
  4621. if(!attr.namespaceURI || attr.namespaceURI == svgns || nsMap[attr.namespaceURI]) {
  4622. out.push(attr.nodeName); out.push("=\"");
  4623. out.push(attrVal); out.push("\"");
  4624. }
  4625. }
  4626. }
  4627. }
  4628. if (elem.hasChildNodes()) {
  4629. out.push(">");
  4630. indent++;
  4631. var bOneLine = false;
  4632. for (var i=0; i<childs.length; i++)
  4633. {
  4634. var child = childs.item(i);
  4635. switch(child.nodeType) {
  4636. case 1: // element node
  4637. out.push("\n");
  4638. out.push(this.svgToString(childs.item(i), indent));
  4639. break;
  4640. case 3: // text node
  4641. var str = child.nodeValue.replace(/^\s+|\s+$/g, "");
  4642. if (str != "") {
  4643. bOneLine = true;
  4644. out.push(toXml(str) + "");
  4645. }
  4646. break;
  4647. case 4: // cdata node
  4648. out.push("\n");
  4649. out.push(new Array(indent+1).join(" "));
  4650. out.push("<![CDATA[");
  4651. out.push(child.nodeValue);
  4652. out.push("]]>");
  4653. break;
  4654. case 8: // comment
  4655. out.push("\n");
  4656. out.push(new Array(indent+1).join(" "));
  4657. out.push("<!--");
  4658. out.push(child.data);
  4659. out.push("-->");
  4660. break;
  4661. } // switch on node type
  4662. }
  4663. indent--;
  4664. if (!bOneLine) {
  4665. out.push("\n");
  4666. for (var i=0; i<indent; i++) out.push(" ");
  4667. }
  4668. out.push("</"); out.push(elem.nodeName); out.push(">");
  4669. } else {
  4670. out.push("/>");
  4671. }
  4672. }
  4673. return out.join('');
  4674. }; // end svgToString()
  4675. // Function: embedImage
  4676. // Converts a given image file to a data URL when possible, then runs a given callback
  4677. //
  4678. // Parameters:
  4679. // val - String with the path/URL of the image
  4680. // callback - Optional function to run when image data is found, supplies the
  4681. // result (data URL or false) as first parameter.
  4682. this.embedImage = function(val, callback) {
  4683. // load in the image and once it's loaded, get the dimensions
  4684. $(new Image()).load(function() {
  4685. // create a canvas the same size as the raster image
  4686. var canvas = document.createElement("canvas");
  4687. canvas.width = this.width;
  4688. canvas.height = this.height;
  4689. // load the raster image into the canvas
  4690. canvas.getContext("2d").drawImage(this,0,0);
  4691. // retrieve the data: URL
  4692. try {
  4693. var urldata = ';svgedit_url=' + encodeURIComponent(val);
  4694. urldata = canvas.toDataURL().replace(';base64',urldata+';base64');
  4695. encodableImages[val] = urldata;
  4696. } catch(e) {
  4697. encodableImages[val] = false;
  4698. }
  4699. last_good_img_url = val;
  4700. if(callback) callback(encodableImages[val]);
  4701. }).attr('src',val);
  4702. }
  4703. // Function: setGoodImage
  4704. // Sets a given URL to be a "last good image" URL
  4705. this.setGoodImage = function(val) {
  4706. last_good_img_url = val;
  4707. }
  4708. this.open = function() {
  4709. // Nothing by default, handled by optional widget/extension
  4710. };
  4711. // Function: save
  4712. // Serializes the current drawing into SVG XML text and returns it to the 'saved' handler.
  4713. // This function also includes the XML prolog. Clients of the SvgCanvas bind their save
  4714. // function to the 'saved' event.
  4715. //
  4716. // Returns:
  4717. // Nothing
  4718. this.save = function(opts) {
  4719. // remove the selected outline before serializing
  4720. clearSelection();
  4721. // Update save options if provided
  4722. if(opts) $.extend(save_options, opts);
  4723. save_options.apply = true;
  4724. // no need for doctype, see http://jwatt.org/svg/authoring/#doctype-declaration
  4725. var str = this.svgCanvasToString();
  4726. call("saved", str);
  4727. };
  4728. // Function: rasterExport
  4729. // Generates a PNG Data URL based on the current image, then calls "exported"
  4730. // with an object including the string and any issues found
  4731. this.rasterExport = function() {
  4732. // remove the selected outline before serializing
  4733. clearSelection();
  4734. // Check for known CanVG issues
  4735. var issues = [];
  4736. // Selector and notice
  4737. var issue_list = {
  4738. 'feGaussianBlur': uiStrings.exportNoBlur,
  4739. 'foreignObject': uiStrings.exportNoforeignObject,
  4740. '[stroke-dasharray]': uiStrings.exportNoDashArray
  4741. };
  4742. var content = $(svgcontent);
  4743. // Add font/text check if Canvas Text API is not implemented
  4744. if(!("font" in $('<canvas>')[0].getContext('2d'))) {
  4745. issue_list['text'] = uiStrings.exportNoText;
  4746. }
  4747. $.each(issue_list, function(sel, descr) {
  4748. if(content.find(sel).length) {
  4749. issues.push(descr);
  4750. }
  4751. });
  4752. var str = this.svgCanvasToString();
  4753. call("exported", {svg: str, issues: issues});
  4754. };
  4755. // Function: getSvgString
  4756. // Returns the current drawing as raw SVG XML text.
  4757. //
  4758. // Returns:
  4759. // The current drawing as raw SVG XML text.
  4760. this.getSvgString = function() {
  4761. save_options.apply = false;
  4762. return this.svgCanvasToString();
  4763. };
  4764. // Function: randomizeIds
  4765. // This function determines whether to use a nonce in the prefix, when
  4766. // generating IDs for future documents in SVG-Edit.
  4767. //
  4768. // Parameters:
  4769. // an opional boolean, which, if true, adds a nonce to the prefix. Thus
  4770. // svgCanvas.randomizeIds() <==> svgCanvas.randomizeIds(true)
  4771. //
  4772. // if you're controlling SVG-Edit externally, and want randomized IDs, call
  4773. // this BEFORE calling svgCanvas.setSvgString
  4774. //
  4775. this.randomizeIds = function() {
  4776. if (arguments.length > 0 && arguments[0] == false) {
  4777. svgedit.draw.randomizeIds(false, getCurrentDrawing());
  4778. } else {
  4779. svgedit.draw.randomizeIds(true, getCurrentDrawing());
  4780. }
  4781. };
  4782. // Function: uniquifyElems
  4783. // Ensure each element has a unique ID
  4784. //
  4785. // Parameters:
  4786. // g - The parent element of the tree to give unique IDs
  4787. var uniquifyElems = this.uniquifyElems = function(g) {
  4788. var ids = {};
  4789. // TODO: Handle markers and connectors. These are not yet re-identified properly
  4790. // as their referring elements do not get remapped.
  4791. //
  4792. // <marker id='se_marker_end_svg_7'/>
  4793. // <polyline id='svg_7' se:connector='svg_1 svg_6' marker-end='url(#se_marker_end_svg_7)'/>
  4794. //
  4795. // Problem #1: if svg_1 gets renamed, we do not update the polyline's se:connector attribute
  4796. // Problem #2: if the polyline svg_7 gets renamed, we do not update the marker id nor the polyline's marker-end attribute
  4797. var ref_elems = ["filter", "linearGradient", "pattern", "radialGradient", "symbol", "textPath", "use"];
  4798. svgedit.utilities.walkTree(g, function(n) {
  4799. // if it's an element node
  4800. if (n.nodeType == 1) {
  4801. // and the element has an ID
  4802. if (n.id) {
  4803. // and we haven't tracked this ID yet
  4804. if (!(n.id in ids)) {
  4805. // add this id to our map
  4806. ids[n.id] = {elem:null, attrs:[], hrefs:[]};
  4807. }
  4808. ids[n.id]["elem"] = n;
  4809. }
  4810. // now search for all attributes on this element that might refer
  4811. // to other elements
  4812. $.each(ref_attrs,function(i,attr) {
  4813. var attrnode = n.getAttributeNode(attr);
  4814. if (attrnode) {
  4815. // the incoming file has been sanitized, so we should be able to safely just strip off the leading #
  4816. var url = svgedit.utilities.getUrlFromAttr(attrnode.value),
  4817. refid = url ? url.substr(1) : null;
  4818. if (refid) {
  4819. if (!(refid in ids)) {
  4820. // add this id to our map
  4821. ids[refid] = {elem:null, attrs:[], hrefs:[]};
  4822. }
  4823. ids[refid]["attrs"].push(attrnode);
  4824. }
  4825. }
  4826. });
  4827. // check xlink:href now
  4828. var href = svgedit.utilities.getHref(n);
  4829. // TODO: what if an <image> or <a> element refers to an element internally?
  4830. if(href && ref_elems.indexOf(n.nodeName) >= 0)
  4831. {
  4832. var refid = href.substr(1);
  4833. if (refid) {
  4834. if (!(refid in ids)) {
  4835. // add this id to our map
  4836. ids[refid] = {elem:null, attrs:[], hrefs:[]};
  4837. }
  4838. ids[refid]["hrefs"].push(n);
  4839. }
  4840. }
  4841. }
  4842. });
  4843. // in ids, we now have a map of ids, elements and attributes, let's re-identify
  4844. for (var oldid in ids) {
  4845. if (!oldid) continue;
  4846. var elem = ids[oldid]["elem"];
  4847. if (elem) {
  4848. var newid = getNextId();
  4849. // assign element its new id
  4850. elem.id = newid;
  4851. // remap all url() attributes
  4852. var attrs = ids[oldid]["attrs"];
  4853. var j = attrs.length;
  4854. while (j--) {
  4855. var attr = attrs[j];
  4856. attr.ownerElement.setAttribute(attr.name, "url(#" + newid + ")");
  4857. }
  4858. // remap all href attributes
  4859. var hreffers = ids[oldid]["hrefs"];
  4860. var k = hreffers.length;
  4861. while (k--) {
  4862. var hreffer = hreffers[k];
  4863. svgedit.utilities.setHref(hreffer, "#"+newid);
  4864. }
  4865. }
  4866. }
  4867. }
  4868. // Function setUseData
  4869. // Assigns reference data for each use element
  4870. var setUseData = this.setUseData = function(parent) {
  4871. var elems = $(parent);
  4872. if(parent.tagName !== 'use') {
  4873. elems = elems.find('use');
  4874. }
  4875. elems.each(function() {
  4876. var id = getHref(this).substr(1);
  4877. var ref_elem = getElem(id);
  4878. if(!ref_elem) return;
  4879. $(this).data('ref', ref_elem);
  4880. if(ref_elem.tagName == 'symbol' || ref_elem.tagName == 'svg') {
  4881. $(this).data('symbol', ref_elem).data('ref', ref_elem);
  4882. }
  4883. });
  4884. }
  4885. // Function convertGradients
  4886. // Converts gradients from userSpaceOnUse to objectBoundingBox
  4887. var convertGradients = this.convertGradients = function(elem) {
  4888. var elems = $(elem).find('linearGradient, radialGradient');
  4889. if(!elems.length && svgedit.browser.isWebkit()) {
  4890. // Bug in webkit prevents regular *Gradient selector search
  4891. elems = $(elem).find('*').filter(function() {
  4892. return (this.tagName.indexOf('Gradient') >= 0);
  4893. });
  4894. }
  4895. elems.each(function() {
  4896. var grad = this;
  4897. if($(grad).attr('gradientUnits') === 'userSpaceOnUse') {
  4898. // TODO: Support more than one element with this ref by duplicating parent grad
  4899. var elems = $(svgcontent).find('[fill=url(#' + grad.id + ')],[stroke=url(#' + grad.id + ')]');
  4900. if(!elems.length) return;
  4901. // get object's bounding box
  4902. var bb = svgedit.utilities.getBBox(elems[0]);
  4903. // This will occur if the element is inside a <defs> or a <symbol>,
  4904. // in which we shouldn't need to convert anyway.
  4905. if(!bb) return;
  4906. if(grad.tagName === 'linearGradient') {
  4907. var g_coords = $(grad).attr(['x1', 'y1', 'x2', 'y2']);
  4908. // If has transform, convert
  4909. var tlist = grad.gradientTransform.baseVal;
  4910. if(tlist && tlist.numberOfItems > 0) {
  4911. var m = transformListToTransform(tlist).matrix;
  4912. var pt1 = transformPoint(g_coords.x1, g_coords.y1, m);
  4913. var pt2 = transformPoint(g_coords.x2, g_coords.y2, m);
  4914. g_coords.x1 = pt1.x;
  4915. g_coords.y1 = pt1.y;
  4916. g_coords.x2 = pt2.x;
  4917. g_coords.y2 = pt2.y;
  4918. grad.removeAttribute('gradientTransform');
  4919. }
  4920. $(grad).attr({
  4921. x1: (g_coords.x1 - bb.x) / bb.width,
  4922. y1: (g_coords.y1 - bb.y) / bb.height,
  4923. x2: (g_coords.x2 - bb.x) / bb.width,
  4924. y2: (g_coords.y2 - bb.y) / bb.height
  4925. });
  4926. grad.removeAttribute('gradientUnits');
  4927. } else {
  4928. // Note: radialGradient elements cannot be easily converted
  4929. // because userSpaceOnUse will keep circular gradients, while
  4930. // objectBoundingBox will x/y scale the gradient according to
  4931. // its bbox.
  4932. // For now we'll do nothing, though we should probably have
  4933. // the gradient be updated as the element is moved, as
  4934. // inkscape/illustrator do.
  4935. // var g_coords = $(grad).attr(['cx', 'cy', 'r']);
  4936. //
  4937. // $(grad).attr({
  4938. // cx: (g_coords.cx - bb.x) / bb.width,
  4939. // cy: (g_coords.cy - bb.y) / bb.height,
  4940. // r: g_coords.r
  4941. // });
  4942. //
  4943. // grad.removeAttribute('gradientUnits');
  4944. }
  4945. }
  4946. });
  4947. }
  4948. // Function: convertToGroup
  4949. // Converts selected/given <use> or child SVG element to a group
  4950. var convertToGroup = this.convertToGroup = function(elem) {
  4951. if(!elem) {
  4952. elem = selectedElements[0];
  4953. }
  4954. var $elem = $(elem);
  4955. var batchCmd = new BatchCommand();
  4956. var ts;
  4957. if($elem.data('gsvg')) {
  4958. // Use the gsvg as the new group
  4959. var svg = elem.firstChild;
  4960. var pt = $(svg).attr(['x', 'y']);
  4961. $(elem.firstChild.firstChild).unwrap();
  4962. $(elem).removeData('gsvg');
  4963. var tlist = getTransformList(elem);
  4964. var xform = svgroot.createSVGTransform();
  4965. xform.setTranslate(pt.x, pt.y);
  4966. tlist.appendItem(xform);
  4967. recalculateDimensions(elem);
  4968. call("selected", [elem]);
  4969. } else if($elem.data('symbol')) {
  4970. elem = $elem.data('symbol');
  4971. ts = $elem.attr('transform');
  4972. var pos = $elem.attr(['x','y']);
  4973. var vb = elem.getAttribute('viewBox');
  4974. if(vb) {
  4975. var nums = vb.split(' ');
  4976. pos.x -= +nums[0];
  4977. pos.y -= +nums[1];
  4978. }
  4979. // Not ideal, but works
  4980. ts += " translate(" + (pos.x || 0) + "," + (pos.y || 0) + ")";
  4981. var prev = $elem.prev();
  4982. // Remove <use> element
  4983. batchCmd.addSubCommand(new RemoveElementCommand($elem[0], $elem[0].nextSibling, $elem[0].parentNode));
  4984. $elem.remove();
  4985. // See if other elements reference this symbol
  4986. var has_more = $(svgcontent).find('use:data(symbol)').length;
  4987. var g = svgdoc.createElementNS(svgns, "g");
  4988. var childs = elem.childNodes;
  4989. for(var i = 0; i < childs.length; i++) {
  4990. g.appendChild(childs[i].cloneNode(true));
  4991. }
  4992. // Duplicate the gradients for Gecko, since they weren't included in the <symbol>
  4993. if(svgedit.browser.isGecko()) {
  4994. var dupeGrads = $(findDefs()).children('linearGradient,radialGradient,pattern').clone();
  4995. $(g).append(dupeGrads);
  4996. }
  4997. if (ts) {
  4998. g.setAttribute("transform", ts);
  4999. }
  5000. var parent = elem.parentNode;
  5001. uniquifyElems(g);
  5002. // Put the dupe gradients back into <defs> (after uniquifying them)
  5003. if(svgedit.browser.isGecko()) {
  5004. $(findDefs()).append( $(g).find('linearGradient,radialGradient,pattern') );
  5005. }
  5006. // now give the g itself a new id
  5007. g.id = getNextId();
  5008. prev.after(g);
  5009. if(parent) {
  5010. if(!has_more) {
  5011. // remove symbol/svg element
  5012. var nextSibling = elem.nextSibling;
  5013. parent.removeChild(elem);
  5014. batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, parent));
  5015. }
  5016. batchCmd.addSubCommand(new InsertElementCommand(g));
  5017. }
  5018. setUseData(g);
  5019. if(svgedit.browser.isGecko()) {
  5020. convertGradients(findDefs());
  5021. } else {
  5022. convertGradients(g);
  5023. }
  5024. // recalculate dimensions on the top-level children so that unnecessary transforms
  5025. // are removed
  5026. svgedit.utilities.walkTreePost(g, function(n){try{recalculateDimensions(n)}catch(e){console.log(e)}});
  5027. // Give ID for any visible element missing one
  5028. $(g).find(visElems).each(function() {
  5029. if(!this.id) this.id = getNextId();
  5030. });
  5031. selectOnly([g]);
  5032. var cm = pushGroupProperties(g, true);
  5033. if(cm) {
  5034. batchCmd.addSubCommand(cm);
  5035. }
  5036. addCommandToHistory(batchCmd);
  5037. } else {
  5038. console.log('Unexpected element to ungroup:', elem);
  5039. }
  5040. }
  5041. //
  5042. // Function: setSvgString
  5043. // This function sets the current drawing as the input SVG XML.
  5044. //
  5045. // Parameters:
  5046. // xmlString - The SVG as XML text.
  5047. //
  5048. // Returns:
  5049. // This function returns false if the set was unsuccessful, true otherwise.
  5050. this.setSvgString = function(xmlString) {
  5051. try {
  5052. // convert string into XML document
  5053. var newDoc = svgedit.utilities.text2xml(xmlString);
  5054. this.prepareSvg(newDoc);
  5055. var batchCmd = new BatchCommand("Change Source");
  5056. // remove old svg document
  5057. var nextSibling = svgcontent.nextSibling;
  5058. var oldzoom = svgroot.removeChild(svgcontent);
  5059. batchCmd.addSubCommand(new RemoveElementCommand(oldzoom, nextSibling, svgroot));
  5060. // set new svg document
  5061. // If DOM3 adoptNode() available, use it. Otherwise fall back to DOM2 importNode()
  5062. if(svgdoc.adoptNode) {
  5063. svgcontent = svgdoc.adoptNode(newDoc.documentElement);
  5064. }
  5065. else {
  5066. svgcontent = svgdoc.importNode(newDoc.documentElement, true);
  5067. }
  5068. svgroot.appendChild(svgcontent);
  5069. var content = $(svgcontent);
  5070. canvas.current_drawing_ = new svgedit.draw.Drawing(svgcontent, idprefix);
  5071. // retrieve or set the nonce
  5072. var nonce = getCurrentDrawing().getNonce();
  5073. if (nonce) {
  5074. call("setnonce", nonce);
  5075. } else {
  5076. call("unsetnonce");
  5077. }
  5078. // change image href vals if possible
  5079. content.find('image').each(function() {
  5080. var image = this;
  5081. preventClickDefault(image);
  5082. var val = getHref(this);
  5083. if(val.indexOf('data:') === 0) {
  5084. // Check if an SVG-edit data URI
  5085. var m = val.match(/svgedit_url=(.*?);/);
  5086. if(m) {
  5087. var url = decodeURIComponent(m[1]);
  5088. $(new Image()).load(function() {
  5089. image.setAttributeNS(xlinkns,'xlink:href',url);
  5090. }).attr('src',url);
  5091. }
  5092. }
  5093. // Add to encodableImages if it loads
  5094. canvas.embedImage(val);
  5095. });
  5096. // Wrap child SVGs in group elements
  5097. content.find('svg').each(function() {
  5098. // Skip if it's in a <defs>
  5099. if($(this).closest('defs').length) return;
  5100. uniquifyElems(this);
  5101. // Check if it already has a gsvg group
  5102. var pa = this.parentNode;
  5103. if(pa.childNodes.length === 1 && pa.nodeName === 'g') {
  5104. $(pa).data('gsvg', this);
  5105. pa.id = pa.id || getNextId();
  5106. } else {
  5107. groupSvgElem(this);
  5108. }
  5109. });
  5110. // For Firefox: Put all paint elems in defs
  5111. if(svgedit.browser.isGecko()) {
  5112. content.find('linearGradient, radialGradient, pattern').appendTo(findDefs());
  5113. }
  5114. // Set ref element for <use> elements
  5115. // TODO: This should also be done if the object is re-added through "redo"
  5116. setUseData(content);
  5117. convertGradients(content[0]);
  5118. // recalculate dimensions on the top-level children so that unnecessary transforms
  5119. // are removed
  5120. svgedit.utilities.walkTreePost(svgcontent, function(n){try{recalculateDimensions(n)}catch(e){console.log(e)}});
  5121. var attrs = {
  5122. id: 'svgcontent',
  5123. overflow: curConfig.show_outside_canvas?'visible':'hidden'
  5124. };
  5125. var percs = false;
  5126. // determine proper size
  5127. if (content.attr("viewBox")) {
  5128. var vb = content.attr("viewBox").split(' ');
  5129. attrs.width = vb[2];
  5130. attrs.height = vb[3];
  5131. }
  5132. // handle content that doesn't have a viewBox
  5133. else {
  5134. $.each(['width', 'height'], function(i, dim) {
  5135. // Set to 100 if not given
  5136. var val = content.attr(dim);
  5137. if(!val) val = '100%';
  5138. if((val+'').substr(-1) === "%") {
  5139. // Use user units if percentage given
  5140. percs = true;
  5141. } else {
  5142. attrs[dim] = convertToNum(dim, val);
  5143. }
  5144. });
  5145. }
  5146. // identify layers
  5147. identifyLayers();
  5148. // Give ID for any visible layer children missing one
  5149. content.children().find(visElems).each(function() {
  5150. if(!this.id) this.id = getNextId();
  5151. });
  5152. // Percentage width/height, so let's base it on visible elements
  5153. if(percs) {
  5154. var bb = getStrokedBBox();
  5155. attrs.width = bb.width + bb.x;
  5156. attrs.height = bb.height + bb.y;
  5157. }
  5158. // Just in case negative numbers are given or
  5159. // result from the percs calculation
  5160. if(attrs.width <= 0) attrs.width = 100;
  5161. if(attrs.height <= 0) attrs.height = 100;
  5162. content.attr(attrs);
  5163. this.contentW = attrs['width'];
  5164. this.contentH = attrs['height'];
  5165. batchCmd.addSubCommand(new InsertElementCommand(svgcontent));
  5166. // update root to the correct size
  5167. var changes = content.attr(["width", "height"]);
  5168. batchCmd.addSubCommand(new ChangeElementCommand(svgroot, changes));
  5169. // reset zoom
  5170. current_zoom = 1;
  5171. // reset transform lists
  5172. svgedit.transformlist.resetListMap();
  5173. clearSelection();
  5174. svgedit.path.clearData();
  5175. svgroot.appendChild(selectorManager.selectorParentGroup);
  5176. addCommandToHistory(batchCmd);
  5177. call("changed", [svgcontent]);
  5178. } catch(e) {
  5179. console.log(e);
  5180. return false;
  5181. }
  5182. return true;
  5183. };
  5184. // Function: importSvgString
  5185. // This function imports the input SVG XML as a <symbol> in the <defs>, then adds a
  5186. // <use> to the current layer.
  5187. //
  5188. // Parameters:
  5189. // xmlString - The SVG as XML text.
  5190. //
  5191. // Returns:
  5192. // This function returns false if the import was unsuccessful, true otherwise.
  5193. // TODO:
  5194. // * properly handle if namespace is introduced by imported content (must add to svgcontent
  5195. // and update all prefixes in the imported node)
  5196. // * properly handle recalculating dimensions, recalculateDimensions() doesn't handle
  5197. // arbitrary transform lists, but makes some assumptions about how the transform list
  5198. // was obtained
  5199. // * import should happen in top-left of current zoomed viewport
  5200. this.importSvgString = function(xmlString) {
  5201. try {
  5202. // Get unique ID
  5203. var uid = svgedit.utilities.encode64(xmlString.length + xmlString).substr(0,32);
  5204. var useExisting = false;
  5205. // Look for symbol and make sure symbol exists in image
  5206. if(import_ids[uid]) {
  5207. if( $(import_ids[uid].symbol).parents('#svgroot').length ) {
  5208. useExisting = true;
  5209. }
  5210. }
  5211. var batchCmd = new BatchCommand("Import SVG");
  5212. if(useExisting) {
  5213. var symbol = import_ids[uid].symbol;
  5214. var ts = import_ids[uid].xform;
  5215. } else {
  5216. // convert string into XML document
  5217. var newDoc = svgedit.utilities.text2xml(xmlString);
  5218. this.prepareSvg(newDoc);
  5219. // import new svg document into our document
  5220. var svg;
  5221. // If DOM3 adoptNode() available, use it. Otherwise fall back to DOM2 importNode()
  5222. if(svgdoc.adoptNode) {
  5223. svg = svgdoc.adoptNode(newDoc.documentElement);
  5224. }
  5225. else {
  5226. svg = svgdoc.importNode(newDoc.documentElement, true);
  5227. }
  5228. uniquifyElems(svg);
  5229. var innerw = convertToNum('width', svg.getAttribute("width")),
  5230. innerh = convertToNum('height', svg.getAttribute("height")),
  5231. innervb = svg.getAttribute("viewBox"),
  5232. // if no explicit viewbox, create one out of the width and height
  5233. vb = innervb ? innervb.split(" ") : [0,0,innerw,innerh];
  5234. for (var j = 0; j < 4; ++j)
  5235. vb[j] = +(vb[j]);
  5236. // TODO: properly handle preserveAspectRatio
  5237. var canvasw = +svgcontent.getAttribute("width"),
  5238. canvash = +svgcontent.getAttribute("height");
  5239. // imported content should be 1/3 of the canvas on its largest dimension
  5240. if (innerh > innerw) {
  5241. var ts = "scale(" + (canvash/3)/vb[3] + ")";
  5242. }
  5243. else {
  5244. var ts = "scale(" + (canvash/3)/vb[2] + ")";
  5245. }
  5246. // Hack to make recalculateDimensions understand how to scale
  5247. ts = "translate(0) " + ts + " translate(0)";
  5248. var symbol = svgdoc.createElementNS(svgns, "symbol");
  5249. var defs = findDefs();
  5250. if(svgedit.browser.isGecko()) {
  5251. // Move all gradients into root for Firefox, workaround for this bug:
  5252. // https://bugzilla.mozilla.org/show_bug.cgi?id=353575
  5253. // TODO: Make this properly undo-able.
  5254. $(svg).find('linearGradient, radialGradient, pattern').appendTo(defs);
  5255. }
  5256. while (svg.firstChild) {
  5257. var first = svg.firstChild;
  5258. symbol.appendChild(first);
  5259. }
  5260. var attrs = svg.attributes;
  5261. for(var i=0; i < attrs.length; i++) {
  5262. var attr = attrs[i];
  5263. symbol.setAttribute(attr.nodeName, attr.nodeValue);
  5264. }
  5265. symbol.id = getNextId();
  5266. // Store data
  5267. import_ids[uid] = {
  5268. symbol: symbol,
  5269. xform: ts
  5270. }
  5271. findDefs().appendChild(symbol);
  5272. batchCmd.addSubCommand(new InsertElementCommand(symbol));
  5273. }
  5274. var use_el = svgdoc.createElementNS(svgns, "use");
  5275. use_el.id = getNextId();
  5276. setHref(use_el, "#" + symbol.id);
  5277. (current_group || getCurrentDrawing().getCurrentLayer()).appendChild(use_el);
  5278. batchCmd.addSubCommand(new InsertElementCommand(use_el));
  5279. clearSelection();
  5280. use_el.setAttribute("transform", ts);
  5281. recalculateDimensions(use_el);
  5282. $(use_el).data('symbol', symbol).data('ref', symbol);
  5283. addToSelection([use_el]);
  5284. // TODO: Find way to add this in a recalculateDimensions-parsable way
  5285. // if (vb[0] != 0 || vb[1] != 0)
  5286. // ts = "translate(" + (-vb[0]) + "," + (-vb[1]) + ") " + ts;
  5287. addCommandToHistory(batchCmd);
  5288. call("changed", [svgcontent]);
  5289. } catch(e) {
  5290. console.log(e);
  5291. return false;
  5292. }
  5293. return true;
  5294. };
  5295. // TODO(codedread): Move all layer/context functions in draw.js
  5296. // Layer API Functions
  5297. // Group: Layers
  5298. // Function: identifyLayers
  5299. // Updates layer system
  5300. var identifyLayers = canvas.identifyLayers = function() {
  5301. leaveContext();
  5302. getCurrentDrawing().identifyLayers();
  5303. };
  5304. // Function: createLayer
  5305. // Creates a new top-level layer in the drawing with the given name, sets the current layer
  5306. // to it, and then clears the selection This function then calls the 'changed' handler.
  5307. // This is an undoable action.
  5308. //
  5309. // Parameters:
  5310. // name - The given name
  5311. this.createLayer = function(name) {
  5312. var batchCmd = new BatchCommand("Create Layer");
  5313. var new_layer = getCurrentDrawing().createLayer(name);
  5314. batchCmd.addSubCommand(new InsertElementCommand(new_layer));
  5315. addCommandToHistory(batchCmd);
  5316. clearSelection();
  5317. call("changed", [new_layer]);
  5318. };
  5319. // Function: cloneLayer
  5320. // Creates a new top-level layer in the drawing with the given name, copies all the current layer's contents
  5321. // to it, and then clears the selection This function then calls the 'changed' handler.
  5322. // This is an undoable action.
  5323. //
  5324. // Parameters:
  5325. // name - The given name
  5326. this.cloneLayer = function(name) {
  5327. var batchCmd = new BatchCommand("Duplicate Layer");
  5328. var new_layer = svgdoc.createElementNS(svgns, "g");
  5329. var layer_title = svgdoc.createElementNS(svgns, "title");
  5330. layer_title.textContent = name;
  5331. new_layer.appendChild(layer_title);
  5332. var current_layer = getCurrentDrawing().getCurrentLayer();
  5333. $(current_layer).after(new_layer);
  5334. var childs = current_layer.childNodes;
  5335. for(var i = 0; i < childs.length; i++) {
  5336. var ch = childs[i];
  5337. if(ch.localName == 'title') continue;
  5338. new_layer.appendChild(copyElem(ch));
  5339. }
  5340. clearSelection();
  5341. identifyLayers();
  5342. batchCmd.addSubCommand(new InsertElementCommand(new_layer));
  5343. addCommandToHistory(batchCmd);
  5344. canvas.setCurrentLayer(name);
  5345. call("changed", [new_layer]);
  5346. };
  5347. // Function: deleteCurrentLayer
  5348. // Deletes the current layer from the drawing and then clears the selection. This function
  5349. // then calls the 'changed' handler. This is an undoable action.
  5350. this.deleteCurrentLayer = function() {
  5351. var current_layer = getCurrentDrawing().getCurrentLayer();
  5352. var nextSibling = current_layer.nextSibling;
  5353. var parent = current_layer.parentNode;
  5354. current_layer = getCurrentDrawing().deleteCurrentLayer();
  5355. if (current_layer) {
  5356. var batchCmd = new BatchCommand("Delete Layer");
  5357. // store in our Undo History
  5358. batchCmd.addSubCommand(new RemoveElementCommand(current_layer, nextSibling, parent));
  5359. addCommandToHistory(batchCmd);
  5360. clearSelection();
  5361. call("changed", [parent]);
  5362. return true;
  5363. }
  5364. return false;
  5365. };
  5366. // Function: setCurrentLayer
  5367. // Sets the current layer. If the name is not a valid layer name, then this function returns
  5368. // false. Otherwise it returns true. This is not an undo-able action.
  5369. //
  5370. // Parameters:
  5371. // name - the name of the layer you want to switch to.
  5372. //
  5373. // Returns:
  5374. // true if the current layer was switched, otherwise false
  5375. this.setCurrentLayer = function(name) {
  5376. var result = getCurrentDrawing().setCurrentLayer(svgedit.utilities.toXml(name));
  5377. if (result) {
  5378. clearSelection();
  5379. }
  5380. return result;
  5381. };
  5382. // Function: renameCurrentLayer
  5383. // Renames the current layer. If the layer name is not valid (i.e. unique), then this function
  5384. // does nothing and returns false, otherwise it returns true. This is an undo-able action.
  5385. //
  5386. // Parameters:
  5387. // newname - the new name you want to give the current layer. This name must be unique
  5388. // among all layer names.
  5389. //
  5390. // Returns:
  5391. // true if the rename succeeded, false otherwise.
  5392. this.renameCurrentLayer = function(newname) {
  5393. var drawing = getCurrentDrawing();
  5394. if (drawing.current_layer) {
  5395. var oldLayer = drawing.current_layer;
  5396. // setCurrentLayer will return false if the name doesn't already exist
  5397. // this means we are free to rename our oldLayer
  5398. if (!canvas.setCurrentLayer(newname)) {
  5399. var batchCmd = new BatchCommand("Rename Layer");
  5400. // find the index of the layer
  5401. for (var i = 0; i < drawing.getNumLayers(); ++i) {
  5402. if (drawing.all_layers[i][1] == oldLayer) break;
  5403. }
  5404. var oldname = drawing.getLayerName(i);
  5405. drawing.all_layers[i][0] = svgedit.utilities.toXml(newname);
  5406. // now change the underlying title element contents
  5407. var len = oldLayer.childNodes.length;
  5408. for (var i = 0; i < len; ++i) {
  5409. var child = oldLayer.childNodes.item(i);
  5410. // found the <title> element, now append all the
  5411. if (child && child.tagName == "title") {
  5412. // wipe out old name
  5413. while (child.firstChild) { child.removeChild(child.firstChild); }
  5414. child.textContent = newname;
  5415. batchCmd.addSubCommand(new ChangeElementCommand(child, {"#text":oldname}));
  5416. addCommandToHistory(batchCmd);
  5417. call("changed", [oldLayer]);
  5418. return true;
  5419. }
  5420. }
  5421. }
  5422. drawing.current_layer = oldLayer;
  5423. }
  5424. return false;
  5425. };
  5426. // Function: setCurrentLayerPosition
  5427. // Changes the position of the current layer to the new value. If the new index is not valid,
  5428. // this function does nothing and returns false, otherwise it returns true. This is an
  5429. // undo-able action.
  5430. //
  5431. // Parameters:
  5432. // newpos - The zero-based index of the new position of the layer. This should be between
  5433. // 0 and (number of layers - 1)
  5434. //
  5435. // Returns:
  5436. // true if the current layer position was changed, false otherwise.
  5437. this.setCurrentLayerPosition = function(newpos) {
  5438. var drawing = getCurrentDrawing();
  5439. if (drawing.current_layer && newpos >= 0 && newpos < drawing.getNumLayers()) {
  5440. for (var oldpos = 0; oldpos < drawing.getNumLayers(); ++oldpos) {
  5441. if (drawing.all_layers[oldpos][1] == drawing.current_layer) break;
  5442. }
  5443. // some unknown error condition (current_layer not in all_layers)
  5444. if (oldpos == drawing.getNumLayers()) { return false; }
  5445. if (oldpos != newpos) {
  5446. // if our new position is below us, we need to insert before the node after newpos
  5447. var refLayer = null;
  5448. var oldNextSibling = drawing.current_layer.nextSibling;
  5449. if (newpos > oldpos ) {
  5450. if (newpos < drawing.getNumLayers()-1) {
  5451. refLayer = drawing.all_layers[newpos+1][1];
  5452. }
  5453. }
  5454. // if our new position is above us, we need to insert before the node at newpos
  5455. else {
  5456. refLayer = drawing.all_layers[newpos][1];
  5457. }
  5458. svgcontent.insertBefore(drawing.current_layer, refLayer);
  5459. addCommandToHistory(new MoveElementCommand(drawing.current_layer, oldNextSibling, svgcontent));
  5460. identifyLayers();
  5461. canvas.setCurrentLayer(drawing.getLayerName(newpos));
  5462. return true;
  5463. }
  5464. }
  5465. return false;
  5466. };
  5467. // Function: setLayerVisibility
  5468. // Sets the visibility of the layer. If the layer name is not valid, this function return
  5469. // false, otherwise it returns true. This is an undo-able action.
  5470. //
  5471. // Parameters:
  5472. // layername - the name of the layer to change the visibility
  5473. // bVisible - true/false, whether the layer should be visible
  5474. //
  5475. // Returns:
  5476. // true if the layer's visibility was set, false otherwise
  5477. this.setLayerVisibility = function(layername, bVisible) {
  5478. var drawing = getCurrentDrawing();
  5479. var prevVisibility = drawing.getLayerVisibility(layername);
  5480. var layer = drawing.setLayerVisibility(layername, bVisible);
  5481. if (layer) {
  5482. var oldDisplay = prevVisibility ? 'inline' : 'none';
  5483. addCommandToHistory(new ChangeElementCommand(layer, {'display':oldDisplay}, 'Layer Visibility'));
  5484. } else {
  5485. return false;
  5486. }
  5487. if (layer == drawing.getCurrentLayer()) {
  5488. clearSelection();
  5489. pathActions.clear();
  5490. }
  5491. // call("changed", [selected]);
  5492. return true;
  5493. };
  5494. // Function: moveSelectedToLayer
  5495. // Moves the selected elements to layername. If the name is not a valid layer name, then false
  5496. // is returned. Otherwise it returns true. This is an undo-able action.
  5497. //
  5498. // Parameters:
  5499. // layername - the name of the layer you want to which you want to move the selected elements
  5500. //
  5501. // Returns:
  5502. // true if the selected elements were moved to the layer, false otherwise.
  5503. this.moveSelectedToLayer = function(layername) {
  5504. // find the layer
  5505. var layer = null;
  5506. var drawing = getCurrentDrawing();
  5507. for (var i = 0; i < drawing.getNumLayers(); ++i) {
  5508. if (drawing.getLayerName(i) == layername) {
  5509. layer = drawing.all_layers[i][1];
  5510. break;
  5511. }
  5512. }
  5513. if (!layer) return false;
  5514. var batchCmd = new BatchCommand("Move Elements to Layer");
  5515. // loop for each selected element and move it
  5516. var selElems = selectedElements;
  5517. var i = selElems.length;
  5518. while (i--) {
  5519. var elem = selElems[i];
  5520. if (!elem) continue;
  5521. var oldNextSibling = elem.nextSibling;
  5522. // TODO: this is pretty brittle!
  5523. var oldLayer = elem.parentNode;
  5524. layer.appendChild(elem);
  5525. batchCmd.addSubCommand(new MoveElementCommand(elem, oldNextSibling, oldLayer));
  5526. }
  5527. addCommandToHistory(batchCmd);
  5528. return true;
  5529. };
  5530. this.mergeLayer = function(skipHistory) {
  5531. var batchCmd = new BatchCommand("Merge Layer");
  5532. var drawing = getCurrentDrawing();
  5533. var prev = $(drawing.current_layer).prev()[0];
  5534. if(!prev) return;
  5535. var childs = drawing.current_layer.childNodes;
  5536. var len = childs.length;
  5537. var layerNextSibling = drawing.current_layer.nextSibling;
  5538. batchCmd.addSubCommand(new RemoveElementCommand(drawing.current_layer, layerNextSibling, svgcontent));
  5539. while(drawing.current_layer.firstChild) {
  5540. var ch = drawing.current_layer.firstChild;
  5541. if(ch.localName == 'title') {
  5542. var chNextSibling = ch.nextSibling;
  5543. batchCmd.addSubCommand(new RemoveElementCommand(ch, chNextSibling, drawing.current_layer));
  5544. drawing.current_layer.removeChild(ch);
  5545. continue;
  5546. }
  5547. var oldNextSibling = ch.nextSibling;
  5548. prev.appendChild(ch);
  5549. batchCmd.addSubCommand(new MoveElementCommand(ch, oldNextSibling, drawing.current_layer));
  5550. }
  5551. // Remove current layer
  5552. svgcontent.removeChild(drawing.current_layer);
  5553. if(!skipHistory) {
  5554. clearSelection();
  5555. identifyLayers();
  5556. call("changed", [svgcontent]);
  5557. addCommandToHistory(batchCmd);
  5558. }
  5559. drawing.current_layer = prev;
  5560. return batchCmd;
  5561. }
  5562. this.mergeAllLayers = function() {
  5563. var batchCmd = new BatchCommand("Merge all Layers");
  5564. var drawing = getCurrentDrawing();
  5565. drawing.current_layer = drawing.all_layers[drawing.getNumLayers()-1][1];
  5566. while($(svgcontent).children('g').length > 1) {
  5567. batchCmd.addSubCommand(canvas.mergeLayer(true));
  5568. }
  5569. clearSelection();
  5570. identifyLayers();
  5571. call("changed", [svgcontent]);
  5572. addCommandToHistory(batchCmd);
  5573. }
  5574. // Function: leaveContext
  5575. // Return from a group context to the regular kind, make any previously
  5576. // disabled elements enabled again
  5577. var leaveContext = this.leaveContext = function() {
  5578. var len = disabled_elems.length;
  5579. if(len) {
  5580. for(var i = 0; i < len; i++) {
  5581. var elem = disabled_elems[i];
  5582. var orig = elData(elem, 'orig_opac');
  5583. if(orig !== 1) {
  5584. elem.setAttribute('opacity', orig);
  5585. } else {
  5586. elem.removeAttribute('opacity');
  5587. }
  5588. elem.setAttribute('style', 'pointer-events: inherit');
  5589. }
  5590. disabled_elems = [];
  5591. clearSelection(true);
  5592. call("contextset", null);
  5593. }
  5594. current_group = null;
  5595. }
  5596. // Function: setContext
  5597. // Set the current context (for in-group editing)
  5598. var setContext = this.setContext = function(elem) {
  5599. leaveContext();
  5600. if(typeof elem === 'string') {
  5601. elem = getElem(elem);
  5602. }
  5603. // Edit inside this group
  5604. current_group = elem;
  5605. // Disable other elements
  5606. $(elem).parentsUntil('#svgcontent').andSelf().siblings().each(function() {
  5607. var opac = this.getAttribute('opacity') || 1;
  5608. // Store the original's opacity
  5609. elData(this, 'orig_opac', opac);
  5610. this.setAttribute('opacity', opac * .33);
  5611. this.setAttribute('style', 'pointer-events: none');
  5612. disabled_elems.push(this);
  5613. });
  5614. clearSelection();
  5615. call("contextset", current_group);
  5616. }
  5617. // Group: Document functions
  5618. // Function: clear
  5619. // Clears the current document. This is not an undoable action.
  5620. this.clear = function() {
  5621. pathActions.clear();
  5622. clearSelection();
  5623. // clear the svgcontent node
  5624. canvas.clearSvgContentElement();
  5625. // create new document
  5626. canvas.current_drawing_ = new svgedit.draw.Drawing(svgcontent);
  5627. // create empty first layer
  5628. canvas.createLayer("Layer 1");
  5629. // clear the undo stack
  5630. canvas.undoMgr.resetUndoStack();
  5631. // reset the selector manager
  5632. selectorManager.initGroup();
  5633. // reset the rubber band box
  5634. rubberBox = selectorManager.getRubberBandBox();
  5635. call("cleared");
  5636. };
  5637. // Function: linkControlPoints
  5638. // Alias function
  5639. this.linkControlPoints = pathActions.linkControlPoints;
  5640. // Function: getContentElem
  5641. // Returns the content DOM element
  5642. this.getContentElem = function() { return svgcontent; };
  5643. // Function: getRootElem
  5644. // Returns the root DOM element
  5645. this.getRootElem = function() { return svgroot; };
  5646. // Function: getSelectedElems
  5647. // Returns the array with selected DOM elements
  5648. this.getSelectedElems = function() { return selectedElements; };
  5649. // Function: getResolution
  5650. // Returns the current dimensions and zoom level in an object
  5651. var getResolution = this.getResolution = function() {
  5652. // var vb = svgcontent.getAttribute("viewBox").split(' ');
  5653. // return {'w':vb[2], 'h':vb[3], 'zoom': current_zoom};
  5654. var width = svgcontent.getAttribute("width")/current_zoom;
  5655. var height = svgcontent.getAttribute("height")/current_zoom;
  5656. return {
  5657. 'w': width,
  5658. 'h': height,
  5659. 'zoom': current_zoom
  5660. };
  5661. };
  5662. // Function: getZoom
  5663. // Returns the current zoom level
  5664. this.getZoom = function(){return current_zoom;};
  5665. // Function: getVersion
  5666. // Returns a string which describes the revision number of SvgCanvas.
  5667. this.getVersion = function() {
  5668. return "svgcanvas.js ($Rev: 2059 $)";
  5669. };
  5670. // Function: setUiStrings
  5671. // Update interface strings with given values
  5672. //
  5673. // Parameters:
  5674. // strs - Object with strings (see uiStrings for examples)
  5675. this.setUiStrings = function(strs) {
  5676. $.extend(uiStrings, strs.notification);
  5677. }
  5678. // Function: setConfig
  5679. // Update configuration options with given values
  5680. //
  5681. // Parameters:
  5682. // opts - Object with options (see curConfig for examples)
  5683. this.setConfig = function(opts) {
  5684. $.extend(curConfig, opts);
  5685. }
  5686. // Function: getTitle
  5687. // Returns the current group/SVG's title contents
  5688. this.getTitle = function(elem) {
  5689. elem = elem || selectedElements[0];
  5690. if(!elem) return;
  5691. elem = $(elem).data('gsvg') || $(elem).data('symbol') || elem;
  5692. var childs = elem.childNodes;
  5693. for (var i=0; i<childs.length; i++) {
  5694. if(childs[i].nodeName == 'title') {
  5695. return childs[i].textContent;
  5696. }
  5697. }
  5698. return 'image';//hack for Chamilo. Avoid missing the title inside a svg document
  5699. }
  5700. // Function: setGroupTitle
  5701. // Sets the group/SVG's title content
  5702. // TODO: Combine this with setDocumentTitle
  5703. this.setGroupTitle = function(val) {
  5704. var elem = selectedElements[0];
  5705. elem = $(elem).data('gsvg') || elem;
  5706. var ts = $(elem).children('title');
  5707. var batchCmd = new BatchCommand("Set Label");
  5708. if(!val.length) {
  5709. // Remove title element
  5710. var tsNextSibling = ts.nextSibling;
  5711. batchCmd.addSubCommand(new RemoveElementCommand(ts[0], tsNextSibling, elem));
  5712. ts.remove();
  5713. } else if(ts.length) {
  5714. // Change title contents
  5715. var title = ts[0];
  5716. batchCmd.addSubCommand(new ChangeElementCommand(title, {'#text': title.textContent}));
  5717. title.textContent = val;
  5718. } else {
  5719. // Add title element
  5720. title = svgdoc.createElementNS(svgns, "title");
  5721. title.textContent = val;
  5722. $(elem).prepend(title);
  5723. batchCmd.addSubCommand(new InsertElementCommand(title));
  5724. }
  5725. addCommandToHistory(batchCmd);
  5726. }
  5727. // Function: getDocumentTitle
  5728. // Returns the current document title or an empty string if not found
  5729. this.getDocumentTitle = function() {
  5730. return canvas.getTitle(svgcontent);
  5731. }
  5732. // Function: setDocumentTitle
  5733. // Adds/updates a title element for the document with the given name.
  5734. // This is an undoable action
  5735. //
  5736. // Parameters:
  5737. // newtitle - String with the new title
  5738. this.setDocumentTitle = function(newtitle) {
  5739. var childs = svgcontent.childNodes, doc_title = false, old_title = '';
  5740. var batchCmd = new BatchCommand("Change Image Title");
  5741. for (var i=0; i<childs.length; i++) {
  5742. if(childs[i].nodeName == 'title') {
  5743. doc_title = childs[i];
  5744. old_title = doc_title.textContent;
  5745. break;
  5746. }
  5747. }
  5748. if(!doc_title) {
  5749. doc_title = svgdoc.createElementNS(svgns, "title");
  5750. svgcontent.insertBefore(doc_title, svgcontent.firstChild);
  5751. }
  5752. if(newtitle.length) {
  5753. doc_title.textContent = newtitle;
  5754. } else {
  5755. // No title given, so element is not necessary
  5756. doc_title.parentNode.removeChild(doc_title);
  5757. }
  5758. batchCmd.addSubCommand(new ChangeElementCommand(doc_title, {'#text': old_title}));
  5759. addCommandToHistory(batchCmd);
  5760. }
  5761. // Function: getEditorNS
  5762. // Returns the editor's namespace URL, optionally adds it to root element
  5763. //
  5764. // Parameters:
  5765. // add - Boolean to indicate whether or not to add the namespace value
  5766. this.getEditorNS = function(add) {
  5767. if(add) {
  5768. svgcontent.setAttribute('xmlns:se', se_ns);
  5769. }
  5770. return se_ns;
  5771. }
  5772. // Function: setResolution
  5773. // Changes the document's dimensions to the given size
  5774. //
  5775. // Parameters:
  5776. // x - Number with the width of the new dimensions in user units.
  5777. // Can also be the string "fit" to indicate "fit to content"
  5778. // y - Number with the height of the new dimensions in user units.
  5779. //
  5780. // Returns:
  5781. // Boolean to indicate if resolution change was succesful.
  5782. // It will fail on "fit to content" option with no content to fit to.
  5783. this.setResolution = function(x, y) {
  5784. var res = getResolution();
  5785. var w = res.w, h = res.h;
  5786. var batchCmd;
  5787. if(x == 'fit') {
  5788. // Get bounding box
  5789. var bbox = getStrokedBBox();
  5790. if(bbox) {
  5791. batchCmd = new BatchCommand("Fit Canvas to Content");
  5792. var visEls = getVisibleElements();
  5793. addToSelection(visEls);
  5794. var dx = [], dy = [];
  5795. $.each(visEls, function(i, item) {
  5796. dx.push(bbox.x*-1);
  5797. dy.push(bbox.y*-1);
  5798. });
  5799. var cmd = canvas.moveSelectedElements(dx, dy, true);
  5800. batchCmd.addSubCommand(cmd);
  5801. clearSelection();
  5802. x = Math.round(bbox.width);
  5803. y = Math.round(bbox.height);
  5804. } else {
  5805. return false;
  5806. }
  5807. }
  5808. if (x != w || y != h) {
  5809. var handle = svgroot.suspendRedraw(1000);
  5810. if(!batchCmd) {
  5811. batchCmd = new BatchCommand("Change Image Dimensions");
  5812. }
  5813. x = convertToNum('width', x);
  5814. y = convertToNum('height', y);
  5815. svgcontent.setAttribute('width', x);
  5816. svgcontent.setAttribute('height', y);
  5817. this.contentW = x;
  5818. this.contentH = y;
  5819. batchCmd.addSubCommand(new ChangeElementCommand(svgcontent, {"width":w, "height":h}));
  5820. svgcontent.setAttribute("viewBox", [0, 0, x/current_zoom, y/current_zoom].join(' '));
  5821. batchCmd.addSubCommand(new ChangeElementCommand(svgcontent, {"viewBox": ["0 0", w, h].join(' ')}));
  5822. addCommandToHistory(batchCmd);
  5823. svgroot.unsuspendRedraw(handle);
  5824. call("changed", [svgcontent]);
  5825. }
  5826. return true;
  5827. };
  5828. // Function: getOffset
  5829. // Returns an object with x, y values indicating the svgcontent element's
  5830. // position in the editor's canvas.
  5831. this.getOffset = function() {
  5832. return $(svgcontent).attr(['x', 'y']);
  5833. }
  5834. // Function: setBBoxZoom
  5835. // Sets the zoom level on the canvas-side based on the given value
  5836. //
  5837. // Parameters:
  5838. // val - Bounding box object to zoom to or string indicating zoom option
  5839. // editor_w - Integer with the editor's workarea box's width
  5840. // editor_h - Integer with the editor's workarea box's height
  5841. this.setBBoxZoom = function(val, editor_w, editor_h) {
  5842. var spacer = .85;
  5843. var bb;
  5844. var calcZoom = function(bb) {
  5845. if(!bb) return false;
  5846. var w_zoom = Math.round((editor_w / bb.width)*100 * spacer)/100;
  5847. var h_zoom = Math.round((editor_h / bb.height)*100 * spacer)/100;
  5848. var zoomlevel = Math.min(w_zoom,h_zoom);
  5849. canvas.setZoom(zoomlevel);
  5850. return {'zoom': zoomlevel, 'bbox': bb};
  5851. }
  5852. if(typeof val == 'object') {
  5853. bb = val;
  5854. if(bb.width == 0 || bb.height == 0) {
  5855. var newzoom = bb.zoom?bb.zoom:current_zoom * bb.factor;
  5856. canvas.setZoom(newzoom);
  5857. return {'zoom': current_zoom, 'bbox': bb};
  5858. }
  5859. return calcZoom(bb);
  5860. }
  5861. switch (val) {
  5862. case 'selection':
  5863. if(!selectedElements[0]) return;
  5864. var sel_elems = $.map(selectedElements, function(n){ if(n) return n; });
  5865. bb = getStrokedBBox(sel_elems);
  5866. break;
  5867. case 'canvas':
  5868. var res = getResolution();
  5869. spacer = .95;
  5870. bb = {width:res.w, height:res.h ,x:0, y:0};
  5871. break;
  5872. case 'content':
  5873. bb = getStrokedBBox();
  5874. break;
  5875. case 'layer':
  5876. bb = getStrokedBBox(getVisibleElements(getCurrentDrawing().getCurrentLayer()));
  5877. break;
  5878. default:
  5879. return;
  5880. }
  5881. return calcZoom(bb);
  5882. }
  5883. // Function: setZoom
  5884. // Sets the zoom to the given level
  5885. //
  5886. // Parameters:
  5887. // zoomlevel - Float indicating the zoom level to change to
  5888. this.setZoom = function(zoomlevel) {
  5889. var res = getResolution();
  5890. svgcontent.setAttribute("viewBox", "0 0 " + res.w/zoomlevel + " " + res.h/zoomlevel);
  5891. current_zoom = zoomlevel;
  5892. $.each(selectedElements, function(i, elem) {
  5893. if(!elem) return;
  5894. selectorManager.requestSelector(elem).resize();
  5895. });
  5896. pathActions.zoomChange();
  5897. runExtensions("zoomChanged", zoomlevel);
  5898. }
  5899. // Function: getMode
  5900. // Returns the current editor mode string
  5901. this.getMode = function() {
  5902. return current_mode;
  5903. };
  5904. // Function: setMode
  5905. // Sets the editor's mode to the given string
  5906. //
  5907. // Parameters:
  5908. // name - String with the new mode to change to
  5909. this.setMode = function(name) {
  5910. pathActions.clear(true);
  5911. textActions.clear();
  5912. cur_properties = (selectedElements[0] && selectedElements[0].nodeName == 'text') ? cur_text : cur_shape;
  5913. current_mode = name;
  5914. };
  5915. // Group: Element Styling
  5916. // Function: getColor
  5917. // Returns the current fill/stroke option
  5918. this.getColor = function(type) {
  5919. return cur_properties[type];
  5920. };
  5921. // Function: setColor
  5922. // Change the current stroke/fill color/gradient value
  5923. //
  5924. // Parameters:
  5925. // type - String indicating fill or stroke
  5926. // val - The value to set the stroke attribute to
  5927. // preventUndo - Boolean indicating whether or not this should be and undoable option
  5928. this.setColor = function(type, val, preventUndo) {
  5929. cur_shape[type] = val;
  5930. cur_properties[type + '_paint'] = {type:"solidColor"};
  5931. var elems = [];
  5932. var i = selectedElements.length;
  5933. while (i--) {
  5934. var elem = selectedElements[i];
  5935. if (elem) {
  5936. if (elem.tagName == "g")
  5937. svgedit.utilities.walkTree(elem, function(e){if(e.nodeName!="g") elems.push(e);});
  5938. else {
  5939. if(type == 'fill') {
  5940. if(elem.tagName != "polyline" && elem.tagName != "line") {
  5941. elems.push(elem);
  5942. }
  5943. } else {
  5944. elems.push(elem);
  5945. }
  5946. }
  5947. }
  5948. }
  5949. if (elems.length > 0) {
  5950. if (!preventUndo) {
  5951. changeSelectedAttribute(type, val, elems);
  5952. call("changed", elems);
  5953. } else
  5954. changeSelectedAttributeNoUndo(type, val, elems);
  5955. }
  5956. }
  5957. // Function: findDefs
  5958. // Return the document's <defs> element, create it first if necessary
  5959. var findDefs = function() {
  5960. var defs = svgcontent.getElementsByTagNameNS(svgns, "defs");
  5961. if (defs.length > 0) {
  5962. defs = defs[0];
  5963. }
  5964. else {
  5965. defs = svgdoc.createElementNS(svgns, "defs" );
  5966. if(svgcontent.firstChild) {
  5967. // first child is a comment, so call nextSibling
  5968. svgcontent.insertBefore( defs, svgcontent.firstChild.nextSibling);
  5969. } else {
  5970. svgcontent.appendChild(defs);
  5971. }
  5972. }
  5973. return defs;
  5974. };
  5975. // Function: setGradient
  5976. // Apply the current gradient to selected element's fill or stroke
  5977. //
  5978. // Parameters
  5979. // type - String indicating "fill" or "stroke" to apply to an element
  5980. var setGradient = this.setGradient = function(type) {
  5981. if(!cur_properties[type + '_paint'] || cur_properties[type + '_paint'].type == "solidColor") return;
  5982. var grad = canvas[type + 'Grad'];
  5983. // find out if there is a duplicate gradient already in the defs
  5984. var duplicate_grad = findDuplicateGradient(grad);
  5985. var defs = findDefs();
  5986. // no duplicate found, so import gradient into defs
  5987. if (!duplicate_grad) {
  5988. var orig_grad = grad;
  5989. grad = defs.appendChild( svgdoc.importNode(grad, true) );
  5990. // get next id and set it on the grad
  5991. grad.id = getNextId();
  5992. }
  5993. else { // use existing gradient
  5994. grad = duplicate_grad;
  5995. }
  5996. canvas.setColor(type, "url(#" + grad.id + ")");
  5997. }
  5998. // Function: findDuplicateGradient
  5999. // Check if exact gradient already exists
  6000. //
  6001. // Parameters:
  6002. // grad - The gradient DOM element to compare to others
  6003. //
  6004. // Returns:
  6005. // The existing gradient if found, null if not
  6006. var findDuplicateGradient = function(grad) {
  6007. var defs = findDefs();
  6008. var existing_grads = $(defs).find("linearGradient, radialGradient");
  6009. var i = existing_grads.length;
  6010. var rad_attrs = ['r','cx','cy','fx','fy'];
  6011. while (i--) {
  6012. var og = existing_grads[i];
  6013. if(grad.tagName == "linearGradient") {
  6014. if (grad.getAttribute('x1') != og.getAttribute('x1') ||
  6015. grad.getAttribute('y1') != og.getAttribute('y1') ||
  6016. grad.getAttribute('x2') != og.getAttribute('x2') ||
  6017. grad.getAttribute('y2') != og.getAttribute('y2'))
  6018. {
  6019. continue;
  6020. }
  6021. } else {
  6022. var grad_attrs = $(grad).attr(rad_attrs);
  6023. var og_attrs = $(og).attr(rad_attrs);
  6024. var diff = false;
  6025. $.each(rad_attrs, function(i, attr) {
  6026. if(grad_attrs[attr] != og_attrs[attr]) diff = true;
  6027. });
  6028. if(diff) continue;
  6029. }
  6030. // else could be a duplicate, iterate through stops
  6031. var stops = grad.getElementsByTagNameNS(svgns, "stop");
  6032. var ostops = og.getElementsByTagNameNS(svgns, "stop");
  6033. if (stops.length != ostops.length) {
  6034. continue;
  6035. }
  6036. var j = stops.length;
  6037. while(j--) {
  6038. var stop = stops[j];
  6039. var ostop = ostops[j];
  6040. if (stop.getAttribute('offset') != ostop.getAttribute('offset') ||
  6041. stop.getAttribute('stop-opacity') != ostop.getAttribute('stop-opacity') ||
  6042. stop.getAttribute('stop-color') != ostop.getAttribute('stop-color'))
  6043. {
  6044. break;
  6045. }
  6046. }
  6047. if (j == -1) {
  6048. return og;
  6049. }
  6050. } // for each gradient in defs
  6051. return null;
  6052. };
  6053. function reorientGrads(elem, m) {
  6054. var bb = svgedit.utilities.getBBox(elem);
  6055. for(var i = 0; i < 2; i++) {
  6056. var type = i === 0 ? 'fill' : 'stroke';
  6057. var attrVal = elem.getAttribute(type);
  6058. if(attrVal && attrVal.indexOf('url(') === 0) {
  6059. var grad = getRefElem(attrVal);
  6060. if(grad.tagName === 'linearGradient') {
  6061. var x1 = grad.getAttribute('x1') || 0;
  6062. var y1 = grad.getAttribute('y1') || 0;
  6063. var x2 = grad.getAttribute('x2') || 1;
  6064. var y2 = grad.getAttribute('y2') || 0;
  6065. // Convert to USOU points
  6066. x1 = (bb.width * x1) + bb.x;
  6067. y1 = (bb.height * y1) + bb.y;
  6068. x2 = (bb.width * x2) + bb.x;
  6069. y2 = (bb.height * y2) + bb.y;
  6070. // Transform those points
  6071. var pt1 = transformPoint(x1, y1, m);
  6072. var pt2 = transformPoint(x2, y2, m);
  6073. // Convert back to BB points
  6074. var g_coords = {};
  6075. g_coords.x1 = (pt1.x - bb.x) / bb.width;
  6076. g_coords.y1 = (pt1.y - bb.y) / bb.height;
  6077. g_coords.x2 = (pt2.x - bb.x) / bb.width;
  6078. g_coords.y2 = (pt2.y - bb.y) / bb.height;
  6079. var newgrad = grad.cloneNode(true);
  6080. $(newgrad).attr(g_coords);
  6081. newgrad.id = getNextId();
  6082. findDefs().appendChild(newgrad);
  6083. elem.setAttribute(type, 'url(#' + newgrad.id + ')');
  6084. }
  6085. }
  6086. }
  6087. }
  6088. // Function: setPaint
  6089. // Set a color/gradient to a fill/stroke
  6090. //
  6091. // Parameters:
  6092. // type - String with "fill" or "stroke"
  6093. // paint - The jGraduate paint object to apply
  6094. this.setPaint = function(type, paint) {
  6095. // make a copy
  6096. var p = new $.jGraduate.Paint(paint);
  6097. this.setPaintOpacity(type, p.alpha/100, true);
  6098. // now set the current paint object
  6099. cur_properties[type + '_paint'] = p;
  6100. switch ( p.type ) {
  6101. case "solidColor":
  6102. this.setColor(type, p.solidColor != "none" ? "#"+p.solidColor : "none");;
  6103. break;
  6104. case "linearGradient":
  6105. case "radialGradient":
  6106. canvas[type + 'Grad'] = p[p.type];
  6107. setGradient(type);
  6108. break;
  6109. default:
  6110. // console.log("none!");
  6111. }
  6112. };
  6113. // this.setStrokePaint = function(p) {
  6114. // // make a copy
  6115. // var p = new $.jGraduate.Paint(p);
  6116. // this.setStrokeOpacity(p.alpha/100);
  6117. //
  6118. // // now set the current paint object
  6119. // cur_properties.stroke_paint = p;
  6120. // switch ( p.type ) {
  6121. // case "solidColor":
  6122. // this.setColor('stroke', p.solidColor != "none" ? "#"+p.solidColor : "none");;
  6123. // break;
  6124. // case "linearGradient"
  6125. // case "radialGradient"
  6126. // canvas.strokeGrad = p[p.type];
  6127. // setGradient(type);
  6128. // default:
  6129. // // console.log("none!");
  6130. // }
  6131. // };
  6132. //
  6133. // this.setFillPaint = function(p, addGrad) {
  6134. // // make a copy
  6135. // var p = new $.jGraduate.Paint(p);
  6136. // this.setFillOpacity(p.alpha/100, true);
  6137. //
  6138. // // now set the current paint object
  6139. // cur_properties.fill_paint = p;
  6140. // if (p.type == "solidColor") {
  6141. // this.setColor('fill', p.solidColor != "none" ? "#"+p.solidColor : "none");
  6142. // }
  6143. // else if(p.type == "linearGradient") {
  6144. // canvas.fillGrad = p.linearGradient;
  6145. // if(addGrad) setGradient();
  6146. // }
  6147. // else if(p.type == "radialGradient") {
  6148. // canvas.fillGrad = p.radialGradient;
  6149. // if(addGrad) setGradient();
  6150. // }
  6151. // else {
  6152. // // console.log("none!");
  6153. // }
  6154. // };
  6155. // Function: getStrokeWidth
  6156. // Returns the current stroke-width value
  6157. this.getStrokeWidth = function() {
  6158. return cur_properties.stroke_width;
  6159. };
  6160. // Function: setStrokeWidth
  6161. // Sets the stroke width for the current selected elements
  6162. // When attempting to set a line's width to 0, this changes it to 1 instead
  6163. //
  6164. // Parameters:
  6165. // val - A Float indicating the new stroke width value
  6166. this.setStrokeWidth = function(val) {
  6167. if(val == 0 && ['line', 'path'].indexOf(current_mode) >= 0) {
  6168. canvas.setStrokeWidth(1);
  6169. return;
  6170. }
  6171. cur_properties.stroke_width = val;
  6172. var elems = [];
  6173. var i = selectedElements.length;
  6174. while (i--) {
  6175. var elem = selectedElements[i];
  6176. if (elem) {
  6177. if (elem.tagName == "g")
  6178. svgedit.utilities.walkTree(elem, function(e){if(e.nodeName!="g") elems.push(e);});
  6179. else
  6180. elems.push(elem);
  6181. }
  6182. }
  6183. if (elems.length > 0) {
  6184. changeSelectedAttribute("stroke-width", val, elems);
  6185. call("changed", selectedElements);
  6186. }
  6187. };
  6188. // Function: setStrokeAttr
  6189. // Set the given stroke-related attribute the given value for selected elements
  6190. //
  6191. // Parameters:
  6192. // attr - String with the attribute name
  6193. // val - String or number with the attribute value
  6194. this.setStrokeAttr = function(attr, val) {
  6195. cur_shape[attr.replace('-','_')] = val;
  6196. var elems = [];
  6197. var i = selectedElements.length;
  6198. while (i--) {
  6199. var elem = selectedElements[i];
  6200. if (elem) {
  6201. if (elem.tagName == "g")
  6202. svgedit.utilities.walkTree(elem, function(e){if(e.nodeName!="g") elems.push(e);});
  6203. else
  6204. elems.push(elem);
  6205. }
  6206. }
  6207. if (elems.length > 0) {
  6208. changeSelectedAttribute(attr, val, elems);
  6209. call("changed", selectedElements);
  6210. }
  6211. };
  6212. // Function: getStyle
  6213. // Returns current style options
  6214. this.getStyle = function() {
  6215. return cur_shape;
  6216. }
  6217. // Function: getOpacity
  6218. // Returns the current opacity
  6219. this.getOpacity = function() {
  6220. return cur_shape.opacity;
  6221. };
  6222. // Function: setOpacity
  6223. // Sets the given opacity to the current selected elements
  6224. this.setOpacity = function(val) {
  6225. cur_shape.opacity = val;
  6226. changeSelectedAttribute("opacity", val);
  6227. };
  6228. // Function: getOpacity
  6229. // Returns the current fill opacity
  6230. this.getFillOpacity = function() {
  6231. return cur_shape.fill_opacity;
  6232. };
  6233. // Function: getStrokeOpacity
  6234. // Returns the current stroke opacity
  6235. this.getStrokeOpacity = function() {
  6236. return cur_shape.stroke_opacity;
  6237. };
  6238. // Function: setPaintOpacity
  6239. // Sets the current fill/stroke opacity
  6240. //
  6241. // Parameters:
  6242. // type - String with "fill" or "stroke"
  6243. // val - Float with the new opacity value
  6244. // preventUndo - Boolean indicating whether or not this should be an undoable action
  6245. this.setPaintOpacity = function(type, val, preventUndo) {
  6246. cur_shape[type + '_opacity'] = val;
  6247. if (!preventUndo)
  6248. changeSelectedAttribute(type + "-opacity", val);
  6249. else
  6250. changeSelectedAttributeNoUndo(type + "-opacity", val);
  6251. };
  6252. // Function: getBlur
  6253. // Gets the stdDeviation blur value of the given element
  6254. //
  6255. // Parameters:
  6256. // elem - The element to check the blur value for
  6257. this.getBlur = function(elem) {
  6258. var val = 0;
  6259. // var elem = selectedElements[0];
  6260. if(elem) {
  6261. var filter_url = elem.getAttribute('filter');
  6262. if(filter_url) {
  6263. var blur = getElem(elem.id + '_blur');
  6264. if(blur) {
  6265. val = blur.firstChild.getAttribute('stdDeviation');
  6266. }
  6267. }
  6268. }
  6269. return val;
  6270. };
  6271. (function() {
  6272. var cur_command = null;
  6273. var filter = null;
  6274. var filterHidden = false;
  6275. // Function: setBlurNoUndo
  6276. // Sets the stdDeviation blur value on the selected element without being undoable
  6277. //
  6278. // Parameters:
  6279. // val - The new stdDeviation value
  6280. canvas.setBlurNoUndo = function(val) {
  6281. if(!filter) {
  6282. canvas.setBlur(val);
  6283. return;
  6284. }
  6285. if(val === 0) {
  6286. // Don't change the StdDev, as that will hide the element.
  6287. // Instead, just remove the value for "filter"
  6288. changeSelectedAttributeNoUndo("filter", "");
  6289. filterHidden = true;
  6290. } else {
  6291. var elem = selectedElements[0];
  6292. if(filterHidden) {
  6293. changeSelectedAttributeNoUndo("filter", 'url(#' + elem.id + '_blur)');
  6294. }
  6295. if(svgedit.browser.isWebkit()) {
  6296. console.log('e', elem);
  6297. elem.removeAttribute('filter');
  6298. elem.setAttribute('filter', 'url(#' + elem.id + '_blur)');
  6299. }
  6300. changeSelectedAttributeNoUndo("stdDeviation", val, [filter.firstChild]);
  6301. canvas.setBlurOffsets(filter, val);
  6302. }
  6303. }
  6304. function finishChange() {
  6305. var bCmd = canvas.undoMgr.finishUndoableChange();
  6306. cur_command.addSubCommand(bCmd);
  6307. addCommandToHistory(cur_command);
  6308. cur_command = null;
  6309. filter = null;
  6310. }
  6311. // Function: setBlurOffsets
  6312. // Sets the x, y, with, height values of the filter element in order to
  6313. // make the blur not be clipped. Removes them if not neeeded
  6314. //
  6315. // Parameters:
  6316. // filter - The filter DOM element to update
  6317. // stdDev - The standard deviation value on which to base the offset size
  6318. canvas.setBlurOffsets = function(filter, stdDev) {
  6319. if(stdDev > 3) {
  6320. // TODO: Create algorithm here where size is based on expected blur
  6321. assignAttributes(filter, {
  6322. x: '-50%',
  6323. y: '-50%',
  6324. width: '200%',
  6325. height: '200%'
  6326. }, 100);
  6327. } else {
  6328. // Removing these attributes hides text in Chrome (see Issue 579)
  6329. if(!svgedit.browser.isWebkit()) {
  6330. filter.removeAttribute('x');
  6331. filter.removeAttribute('y');
  6332. filter.removeAttribute('width');
  6333. filter.removeAttribute('height');
  6334. }
  6335. }
  6336. }
  6337. // Function: setBlur
  6338. // Adds/updates the blur filter to the selected element
  6339. //
  6340. // Parameters:
  6341. // val - Float with the new stdDeviation blur value
  6342. // complete - Boolean indicating whether or not the action should be completed (to add to the undo manager)
  6343. canvas.setBlur = function(val, complete) {
  6344. if(cur_command) {
  6345. finishChange();
  6346. return;
  6347. }
  6348. // Looks for associated blur, creates one if not found
  6349. var elem = selectedElements[0];
  6350. var elem_id = elem.id;
  6351. filter = getElem(elem_id + '_blur');
  6352. val -= 0;
  6353. var batchCmd = new BatchCommand();
  6354. // Blur found!
  6355. if(filter) {
  6356. if(val === 0) {
  6357. filter = null;
  6358. }
  6359. } else {
  6360. // Not found, so create
  6361. var newblur = addSvgElementFromJson({ "element": "feGaussianBlur",
  6362. "attr": {
  6363. "in": 'SourceGraphic',
  6364. "stdDeviation": val
  6365. }
  6366. });
  6367. filter = addSvgElementFromJson({ "element": "filter",
  6368. "attr": {
  6369. "id": elem_id + '_blur'
  6370. }
  6371. });
  6372. filter.appendChild(newblur);
  6373. findDefs().appendChild(filter);
  6374. batchCmd.addSubCommand(new InsertElementCommand(filter));
  6375. }
  6376. var changes = {filter: elem.getAttribute('filter')};
  6377. if(val === 0) {
  6378. elem.removeAttribute("filter");
  6379. batchCmd.addSubCommand(new ChangeElementCommand(elem, changes));
  6380. return;
  6381. } else {
  6382. changeSelectedAttribute("filter", 'url(#' + elem_id + '_blur)');
  6383. batchCmd.addSubCommand(new ChangeElementCommand(elem, changes));
  6384. canvas.setBlurOffsets(filter, val);
  6385. }
  6386. cur_command = batchCmd;
  6387. canvas.undoMgr.beginUndoableChange("stdDeviation", [filter?filter.firstChild:null]);
  6388. if(complete) {
  6389. canvas.setBlurNoUndo(val);
  6390. finishChange();
  6391. }
  6392. };
  6393. }());
  6394. // Function: getBold
  6395. // Check whether selected element is bold or not
  6396. //
  6397. // Returns:
  6398. // Boolean indicating whether or not element is bold
  6399. this.getBold = function() {
  6400. // should only have one element selected
  6401. var selected = selectedElements[0];
  6402. if (selected != null && selected.tagName == "text" &&
  6403. selectedElements[1] == null)
  6404. {
  6405. return (selected.getAttribute("font-weight") == "bold");
  6406. }
  6407. return false;
  6408. };
  6409. // Function: setBold
  6410. // Make the selected element bold or normal
  6411. //
  6412. // Parameters:
  6413. // b - Boolean indicating bold (true) or normal (false)
  6414. this.setBold = function(b) {
  6415. var selected = selectedElements[0];
  6416. if (selected != null && selected.tagName == "text" &&
  6417. selectedElements[1] == null)
  6418. {
  6419. changeSelectedAttribute("font-weight", b ? "bold" : "normal");
  6420. }
  6421. if(!selectedElements[0].textContent) {
  6422. textActions.setCursor();
  6423. }
  6424. };
  6425. // Function: getItalic
  6426. // Check whether selected element is italic or not
  6427. //
  6428. // Returns:
  6429. // Boolean indicating whether or not element is italic
  6430. this.getItalic = function() {
  6431. var selected = selectedElements[0];
  6432. if (selected != null && selected.tagName == "text" &&
  6433. selectedElements[1] == null)
  6434. {
  6435. return (selected.getAttribute("font-style") == "italic");
  6436. }
  6437. return false;
  6438. };
  6439. // Function: setItalic
  6440. // Make the selected element italic or normal
  6441. //
  6442. // Parameters:
  6443. // b - Boolean indicating italic (true) or normal (false)
  6444. this.setItalic = function(i) {
  6445. var selected = selectedElements[0];
  6446. if (selected != null && selected.tagName == "text" &&
  6447. selectedElements[1] == null)
  6448. {
  6449. changeSelectedAttribute("font-style", i ? "italic" : "normal");
  6450. }
  6451. if(!selectedElements[0].textContent) {
  6452. textActions.setCursor();
  6453. }
  6454. };
  6455. // Function: getFontFamily
  6456. // Returns the current font family
  6457. this.getFontFamily = function() {
  6458. return cur_text.font_family;
  6459. };
  6460. // Function: setFontFamily
  6461. // Set the new font family
  6462. //
  6463. // Parameters:
  6464. // val - String with the new font family
  6465. this.setFontFamily = function(val) {
  6466. cur_text.font_family = val;
  6467. changeSelectedAttribute("font-family", val);
  6468. if(selectedElements[0] && !selectedElements[0].textContent) {
  6469. textActions.setCursor();
  6470. }
  6471. };
  6472. // Function: setFontColor
  6473. // Set the new font color
  6474. //
  6475. // Parameters:
  6476. // val - String with the new font color
  6477. this.setFontColor = function(val) {
  6478. cur_text.fill = val;
  6479. changeSelectedAttribute("fill", val);
  6480. };
  6481. // Function: getFontColor
  6482. // Returns the current font color
  6483. this.getFontSize = function() {
  6484. return cur_text.fill;
  6485. };
  6486. // Function: getFontSize
  6487. // Returns the current font size
  6488. this.getFontSize = function() {
  6489. return cur_text.font_size;
  6490. };
  6491. // Function: setFontSize
  6492. // Applies the given font size to the selected element
  6493. //
  6494. // Parameters:
  6495. // val - Float with the new font size
  6496. this.setFontSize = function(val) {
  6497. cur_text.font_size = val;
  6498. changeSelectedAttribute("font-size", val);
  6499. if(!selectedElements[0].textContent) {
  6500. textActions.setCursor();
  6501. }
  6502. };
  6503. // Function: getText
  6504. // Returns the current text (textContent) of the selected element
  6505. this.getText = function() {
  6506. var selected = selectedElements[0];
  6507. if (selected == null) { return ""; }
  6508. return selected.textContent;
  6509. };
  6510. // Function: setTextContent
  6511. // Updates the text element with the given string
  6512. //
  6513. // Parameters:
  6514. // val - String with the new text
  6515. this.setTextContent = function(val) {
  6516. changeSelectedAttribute("#text", val);
  6517. textActions.init(val);
  6518. textActions.setCursor();
  6519. };
  6520. // Function: setImageURL
  6521. // Sets the new image URL for the selected image element. Updates its size if
  6522. // a new URL is given
  6523. //
  6524. // Parameters:
  6525. // val - String with the image URL/path
  6526. this.setImageURL = function(val) {
  6527. var elem = selectedElements[0];
  6528. if(!elem) return;
  6529. var attrs = $(elem).attr(['width', 'height']);
  6530. var setsize = (!attrs.width || !attrs.height);
  6531. var cur_href = getHref(elem);
  6532. // Do nothing if no URL change or size change
  6533. if(cur_href !== val) {
  6534. setsize = true;
  6535. } else if(!setsize) return;
  6536. var batchCmd = new BatchCommand("Change Image URL");
  6537. setHref(elem, val);
  6538. batchCmd.addSubCommand(new ChangeElementCommand(elem, {
  6539. "#href": cur_href
  6540. }));
  6541. if(setsize) {
  6542. $(new Image()).load(function() {
  6543. var changes = $(elem).attr(['width', 'height']);
  6544. $(elem).attr({
  6545. width: this.width,
  6546. height: this.height
  6547. });
  6548. selectorManager.requestSelector(elem).resize();
  6549. batchCmd.addSubCommand(new ChangeElementCommand(elem, changes));
  6550. addCommandToHistory(batchCmd);
  6551. call("changed", [elem]);
  6552. }).attr('src',val);
  6553. } else {
  6554. addCommandToHistory(batchCmd);
  6555. }
  6556. };
  6557. // Function: setLinkURL
  6558. // Sets the new link URL for the selected anchor element.
  6559. //
  6560. // Parameters:
  6561. // val - String with the link URL/path
  6562. this.setLinkURL = function(val) {
  6563. var elem = selectedElements[0];
  6564. if(!elem) return;
  6565. if(elem.tagName !== 'a') {
  6566. // See if parent is an anchor
  6567. var parents_a = $(elem).parents('a');
  6568. if(parents_a.length) {
  6569. elem = parents_a[0];
  6570. } else {
  6571. return;
  6572. }
  6573. }
  6574. var cur_href = getHref(elem);
  6575. if(cur_href === val) return;
  6576. var batchCmd = new BatchCommand("Change Link URL");
  6577. setHref(elem, val);
  6578. batchCmd.addSubCommand(new ChangeElementCommand(elem, {
  6579. "#href": cur_href
  6580. }));
  6581. addCommandToHistory(batchCmd);
  6582. };
  6583. // Function: setRectRadius
  6584. // Sets the rx & ry values to the selected rect element to change its corner radius
  6585. //
  6586. // Parameters:
  6587. // val - The new radius
  6588. this.setRectRadius = function(val) {
  6589. var selected = selectedElements[0];
  6590. if (selected != null && selected.tagName == "rect") {
  6591. var r = selected.getAttribute("rx");
  6592. if (r != val) {
  6593. selected.setAttribute("rx", val);
  6594. selected.setAttribute("ry", val);
  6595. addCommandToHistory(new ChangeElementCommand(selected, {"rx":r, "ry":r}, "Radius"));
  6596. call("changed", [selected]);
  6597. }
  6598. }
  6599. };
  6600. // Function: makeHyperlink
  6601. // Wraps the selected element(s) in an anchor element or converts group to one
  6602. this.makeHyperlink = function(url) {
  6603. canvas.groupSelectedElements('a', url);
  6604. // TODO: If element is a single "g", convert to "a"
  6605. // if(selectedElements.length > 1 && selectedElements[1]) {
  6606. }
  6607. // Function: removeHyperlink
  6608. this.removeHyperlink = function() {
  6609. canvas.ungroupSelectedElement();
  6610. }
  6611. // Group: Element manipulation
  6612. // Function: setSegType
  6613. // Sets the new segment type to the selected segment(s).
  6614. //
  6615. // Parameters:
  6616. // new_type - Integer with the new segment type
  6617. // See http://www.w3.org/TR/SVG/paths.html#InterfaceSVGPathSeg for list
  6618. this.setSegType = function(new_type) {
  6619. pathActions.setSegType(new_type);
  6620. }
  6621. // TODO(codedread): Remove the getBBox argument and split this function into two.
  6622. // Function: convertToPath
  6623. // Convert selected element to a path, or get the BBox of an element-as-path
  6624. //
  6625. // Parameters:
  6626. // elem - The DOM element to be converted
  6627. // getBBox - Boolean on whether or not to only return the path's BBox
  6628. //
  6629. // Returns:
  6630. // If the getBBox flag is true, the resulting path's bounding box object.
  6631. // Otherwise the resulting path element is returned.
  6632. this.convertToPath = function(elem, getBBox) {
  6633. if(elem == null) {
  6634. var elems = selectedElements;
  6635. $.each(selectedElements, function(i, elem) {
  6636. if(elem) canvas.convertToPath(elem);
  6637. });
  6638. return;
  6639. }
  6640. if(!getBBox) {
  6641. var batchCmd = new BatchCommand("Convert element to Path");
  6642. }
  6643. var attrs = getBBox?{}:{
  6644. "fill": cur_shape.fill,
  6645. "fill-opacity": cur_shape.fill_opacity,
  6646. "stroke": cur_shape.stroke,
  6647. "stroke-width": cur_shape.stroke_width,
  6648. "stroke-dasharray": cur_shape.stroke_dasharray,
  6649. "stroke-linejoin": cur_shape.stroke_linejoin,
  6650. "stroke-linecap": cur_shape.stroke_linecap,
  6651. "stroke-opacity": cur_shape.stroke_opacity,
  6652. "opacity": cur_shape.opacity,
  6653. "visibility":"hidden"
  6654. };
  6655. // any attribute on the element not covered by the above
  6656. // TODO: make this list global so that we can properly maintain it
  6657. // TODO: what about @transform, @clip-rule, @fill-rule, etc?
  6658. $.each(['marker-start', 'marker-end', 'marker-mid', 'filter', 'clip-path'], function() {
  6659. if (elem.getAttribute(this)) {
  6660. attrs[this] = elem.getAttribute(this);
  6661. }
  6662. });
  6663. var path = addSvgElementFromJson({
  6664. "element": "path",
  6665. "attr": attrs
  6666. });
  6667. var eltrans = elem.getAttribute("transform");
  6668. if(eltrans) {
  6669. path.setAttribute("transform",eltrans);
  6670. }
  6671. var id = elem.id;
  6672. var parent = elem.parentNode;
  6673. if(elem.nextSibling) {
  6674. parent.insertBefore(path, elem);
  6675. } else {
  6676. parent.appendChild(path);
  6677. }
  6678. var d = '';
  6679. var joinSegs = function(segs) {
  6680. $.each(segs, function(j, seg) {
  6681. var l = seg[0], pts = seg[1];
  6682. d += l;
  6683. for(var i=0; i < pts.length; i+=2) {
  6684. d += (pts[i] +','+pts[i+1]) + ' ';
  6685. }
  6686. });
  6687. }
  6688. // Possibly the cubed root of 6, but 1.81 works best
  6689. var num = 1.81;
  6690. switch (elem.tagName) {
  6691. case 'ellipse':
  6692. case 'circle':
  6693. var a = $(elem).attr(['rx', 'ry', 'cx', 'cy']);
  6694. var cx = a.cx, cy = a.cy, rx = a.rx, ry = a.ry;
  6695. if(elem.tagName == 'circle') {
  6696. rx = ry = $(elem).attr('r');
  6697. }
  6698. joinSegs([
  6699. ['M',[(cx-rx),(cy)]],
  6700. ['C',[(cx-rx),(cy-ry/num), (cx-rx/num),(cy-ry), (cx),(cy-ry)]],
  6701. ['C',[(cx+rx/num),(cy-ry), (cx+rx),(cy-ry/num), (cx+rx),(cy)]],
  6702. ['C',[(cx+rx),(cy+ry/num), (cx+rx/num),(cy+ry), (cx),(cy+ry)]],
  6703. ['C',[(cx-rx/num),(cy+ry), (cx-rx),(cy+ry/num), (cx-rx),(cy)]],
  6704. ['Z',[]]
  6705. ]);
  6706. break;
  6707. case 'path':
  6708. d = elem.getAttribute('d');
  6709. break;
  6710. case 'line':
  6711. var a = $(elem).attr(["x1", "y1", "x2", "y2"]);
  6712. d = "M"+a.x1+","+a.y1+"L"+a.x2+","+a.y2;
  6713. break;
  6714. case 'polyline':
  6715. case 'polygon':
  6716. d = "M" + elem.getAttribute('points');
  6717. break;
  6718. case 'rect':
  6719. var r = $(elem).attr(['rx', 'ry']);
  6720. var rx = r.rx, ry = r.ry;
  6721. var b = elem.getBBox();
  6722. var x = b.x, y = b.y, w = b.width, h = b.height;
  6723. var num = 4-num; // Why? Because!
  6724. if(!rx && !ry) {
  6725. // Regular rect
  6726. joinSegs([
  6727. ['M',[x, y]],
  6728. ['L',[x+w, y]],
  6729. ['L',[x+w, y+h]],
  6730. ['L',[x, y+h]],
  6731. ['L',[x, y]],
  6732. ['Z',[]]
  6733. ]);
  6734. } else {
  6735. joinSegs([
  6736. ['M',[x, y+ry]],
  6737. ['C',[x,y+ry/num, x+rx/num,y, x+rx,y]],
  6738. ['L',[x+w-rx, y]],
  6739. ['C',[x+w-rx/num,y, x+w,y+ry/num, x+w,y+ry]],
  6740. ['L',[x+w, y+h-ry]],
  6741. ['C',[x+w, y+h-ry/num, x+w-rx/num,y+h, x+w-rx,y+h]],
  6742. ['L',[x+rx, y+h]],
  6743. ['C',[x+rx/num, y+h, x,y+h-ry/num, x,y+h-ry]],
  6744. ['L',[x, y+ry]],
  6745. ['Z',[]]
  6746. ]);
  6747. }
  6748. break;
  6749. default:
  6750. path.parentNode.removeChild(path);
  6751. break;
  6752. }
  6753. if(d) {
  6754. path.setAttribute('d',d);
  6755. }
  6756. if(!getBBox) {
  6757. // Replace the current element with the converted one
  6758. // Reorient if it has a matrix
  6759. if(eltrans) {
  6760. var tlist = getTransformList(path);
  6761. if(hasMatrixTransform(tlist)) {
  6762. pathActions.resetOrientation(path);
  6763. }
  6764. }
  6765. var nextSibling = elem.nextSibling;
  6766. batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, parent));
  6767. batchCmd.addSubCommand(new InsertElementCommand(path));
  6768. clearSelection();
  6769. elem.parentNode.removeChild(elem)
  6770. path.setAttribute('id', id);
  6771. path.removeAttribute("visibility");
  6772. addToSelection([path], true);
  6773. addCommandToHistory(batchCmd);
  6774. } else {
  6775. // Get the correct BBox of the new path, then discard it
  6776. pathActions.resetOrientation(path);
  6777. var bb = false;
  6778. try {
  6779. bb = path.getBBox();
  6780. } catch(e) {
  6781. // Firefox fails
  6782. }
  6783. path.parentNode.removeChild(path);
  6784. return bb;
  6785. }
  6786. };
  6787. // Function: changeSelectedAttributeNoUndo
  6788. // This function makes the changes to the elements. It does not add the change
  6789. // to the history stack.
  6790. //
  6791. // Parameters:
  6792. // attr - String with the attribute name
  6793. // newValue - String or number with the new attribute value
  6794. // elems - The DOM elements to apply the change to
  6795. var changeSelectedAttributeNoUndo = function(attr, newValue, elems) {
  6796. var handle = svgroot.suspendRedraw(1000);
  6797. if(current_mode == 'pathedit') {
  6798. // Editing node
  6799. pathActions.moveNode(attr, newValue);
  6800. }
  6801. var elems = elems || selectedElements;
  6802. var i = elems.length;
  6803. var no_xy_elems = ['g', 'polyline', 'path'];
  6804. var good_g_attrs = ['transform', 'opacity', 'filter'];
  6805. while (i--) {
  6806. var elem = elems[i];
  6807. if (elem == null) continue;
  6808. // Go into "select" mode for text changes
  6809. if(current_mode === "textedit" && attr !== "#text" && elem.textContent.length) {
  6810. textActions.toSelectMode(elem);
  6811. }
  6812. // Set x,y vals on elements that don't have them
  6813. if((attr === 'x' || attr === 'y') && no_xy_elems.indexOf(elem.tagName) >= 0) {
  6814. var bbox = getStrokedBBox([elem]);
  6815. var diff_x = attr === 'x' ? newValue - bbox.x : 0;
  6816. var diff_y = attr === 'y' ? newValue - bbox.y : 0;
  6817. canvas.moveSelectedElements(diff_x*current_zoom, diff_y*current_zoom, true);
  6818. continue;
  6819. }
  6820. // only allow the transform/opacity/filter attribute to change on <g> elements, slightly hacky
  6821. // TODO: FIXME: This doesn't seem right. Where's the body of this if statement?
  6822. if (elem.tagName === "g" && good_g_attrs.indexOf(attr) >= 0);
  6823. var oldval = attr === "#text" ? elem.textContent : elem.getAttribute(attr);
  6824. if (oldval == null) oldval = "";
  6825. if (oldval !== String(newValue)) {
  6826. if (attr == "#text") {
  6827. var old_w = svgedit.utilities.getBBox(elem).width;
  6828. elem.textContent = newValue;
  6829. // FF bug occurs on on rotated elements
  6830. if(/rotate/.test(elem.getAttribute('transform'))) {
  6831. elem = ffClone(elem);
  6832. }
  6833. // Hoped to solve the issue of moving text with text-anchor="start",
  6834. // but this doesn't actually fix it. Hopefully on the right track, though. -Fyrd
  6835. // var box=getBBox(elem), left=box.x, top=box.y, width=box.width,
  6836. // height=box.height, dx = width - old_w, dy=0;
  6837. // var angle = getRotationAngle(elem, true);
  6838. // if (angle) {
  6839. // var r = Math.sqrt( dx*dx + dy*dy );
  6840. // var theta = Math.atan2(dy,dx) - angle;
  6841. // dx = r * Math.cos(theta);
  6842. // dy = r * Math.sin(theta);
  6843. //
  6844. // elem.setAttribute('x', elem.getAttribute('x')-dx);
  6845. // elem.setAttribute('y', elem.getAttribute('y')-dy);
  6846. // }
  6847. } else if (attr == "#href") {
  6848. setHref(elem, newValue);
  6849. }
  6850. else elem.setAttribute(attr, newValue);
  6851. // if (i==0)
  6852. // selectedBBoxes[0] = svgedit.utilities.getBBox(elem);
  6853. // Use the Firefox ffClone hack for text elements with gradients or
  6854. // where other text attributes are changed.
  6855. if(svgedit.browser.isGecko() && elem.nodeName === 'text' && /rotate/.test(elem.getAttribute('transform'))) {
  6856. if((newValue+'').indexOf('url') === 0 || ['font-size','font-family','x','y'].indexOf(attr) >= 0 && elem.textContent) {
  6857. elem = ffClone(elem);
  6858. }
  6859. }
  6860. // Timeout needed for Opera & Firefox
  6861. // codedread: it is now possible for this function to be called with elements
  6862. // that are not in the selectedElements array, we need to only request a
  6863. // selector if the element is in that array
  6864. if (selectedElements.indexOf(elem) >= 0) {
  6865. setTimeout(function() {
  6866. // Due to element replacement, this element may no longer
  6867. // be part of the DOM
  6868. if(!elem.parentNode) return;
  6869. selectorManager.requestSelector(elem).resize();
  6870. },0);
  6871. }
  6872. // if this element was rotated, and we changed the position of this element
  6873. // we need to update the rotational transform attribute
  6874. var angle = getRotationAngle(elem);
  6875. if (angle != 0 && attr != "transform") {
  6876. var tlist = getTransformList(elem);
  6877. var n = tlist.numberOfItems;
  6878. while (n--) {
  6879. var xform = tlist.getItem(n);
  6880. if (xform.type == 4) {
  6881. // remove old rotate
  6882. tlist.removeItem(n);
  6883. var box = svgedit.utilities.getBBox(elem);
  6884. var center = transformPoint(box.x+box.width/2, box.y+box.height/2, transformListToTransform(tlist).matrix);
  6885. var cx = center.x,
  6886. cy = center.y;
  6887. var newrot = svgroot.createSVGTransform();
  6888. newrot.setRotate(angle, cx, cy);
  6889. tlist.insertItemBefore(newrot, n);
  6890. break;
  6891. }
  6892. }
  6893. }
  6894. } // if oldValue != newValue
  6895. } // for each elem
  6896. svgroot.unsuspendRedraw(handle);
  6897. };
  6898. // Function: changeSelectedAttribute
  6899. // Change the given/selected element and add the original value to the history stack
  6900. // If you want to change all selectedElements, ignore the elems argument.
  6901. // If you want to change only a subset of selectedElements, then send the
  6902. // subset to this function in the elems argument.
  6903. //
  6904. // Parameters:
  6905. // attr - String with the attribute name
  6906. // newValue - String or number with the new attribute value
  6907. // elems - The DOM elements to apply the change to
  6908. var changeSelectedAttribute = this.changeSelectedAttribute = function(attr, val, elems) {
  6909. var elems = elems || selectedElements;
  6910. canvas.undoMgr.beginUndoableChange(attr, elems);
  6911. var i = elems.length;
  6912. changeSelectedAttributeNoUndo(attr, val, elems);
  6913. var batchCmd = canvas.undoMgr.finishUndoableChange();
  6914. if (!batchCmd.isEmpty()) {
  6915. addCommandToHistory(batchCmd);
  6916. }
  6917. };
  6918. // Function: deleteSelectedElements
  6919. // Removes all selected elements from the DOM and adds the change to the
  6920. // history stack
  6921. this.deleteSelectedElements = function() {
  6922. var batchCmd = new BatchCommand("Delete Elements");
  6923. var len = selectedElements.length;
  6924. var selectedCopy = []; //selectedElements is being deleted
  6925. for (var i = 0; i < len; ++i) {
  6926. var selected = selectedElements[i];
  6927. if (selected == null) break;
  6928. var parent = selected.parentNode;
  6929. var t = selected;
  6930. // this will unselect the element and remove the selectedOutline
  6931. selectorManager.releaseSelector(t);
  6932. // Remove the path if present.
  6933. svgedit.path.removePath_(t.id);
  6934. // Get the parent if it's a single-child anchor
  6935. if(parent.tagName === 'a' && parent.childNodes.length === 1) {
  6936. t = parent;
  6937. parent = parent.parentNode;
  6938. }
  6939. var nextSibling = t.nextSibling;
  6940. var elem = parent.removeChild(t);
  6941. selectedCopy.push(selected); //for the copy
  6942. selectedElements[i] = null;
  6943. batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, parent));
  6944. }
  6945. if (!batchCmd.isEmpty()) addCommandToHistory(batchCmd);
  6946. call("changed", selectedCopy);
  6947. clearSelection();
  6948. };
  6949. // Function: cutSelectedElements
  6950. // Removes all selected elements from the DOM and adds the change to the
  6951. // history stack. Remembers removed elements on the clipboard
  6952. // TODO: Combine similar code with deleteSelectedElements
  6953. this.cutSelectedElements = function() {
  6954. var batchCmd = new BatchCommand("Cut Elements");
  6955. var len = selectedElements.length;
  6956. var selectedCopy = []; //selectedElements is being deleted
  6957. for (var i = 0; i < len; ++i) {
  6958. var selected = selectedElements[i];
  6959. if (selected == null) break;
  6960. var parent = selected.parentNode;
  6961. var t = selected;
  6962. // this will unselect the element and remove the selectedOutline
  6963. selectorManager.releaseSelector(t);
  6964. // Remove the path if present.
  6965. svgedit.path.removePath_(t.id);
  6966. var nextSibling = t.nextSibling;
  6967. var elem = parent.removeChild(t);
  6968. selectedCopy.push(selected); //for the copy
  6969. selectedElements[i] = null;
  6970. batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, parent));
  6971. }
  6972. if (!batchCmd.isEmpty()) addCommandToHistory(batchCmd);
  6973. call("changed", selectedCopy);
  6974. clearSelection();
  6975. canvas.clipBoard = selectedCopy;
  6976. };
  6977. // Function: copySelectedElements
  6978. // Remembers the current selected elements on the clipboard
  6979. this.copySelectedElements = function() {
  6980. canvas.clipBoard = $.merge([], selectedElements);
  6981. };
  6982. this.pasteElements = function(type, x, y) {
  6983. var cb = canvas.clipBoard;
  6984. var len = cb.length;
  6985. if(!len) return;
  6986. var pasted = [];
  6987. var batchCmd = new BatchCommand('Paste elements');
  6988. // Move elements to lastClickPoint
  6989. while (len--) {
  6990. var elem = cb[len];
  6991. if(!elem) continue;
  6992. var copy = copyElem(elem);
  6993. // See if elem with elem ID is in the DOM already
  6994. if(!getElem(elem.id)) copy.id = elem.id;
  6995. pasted.push(copy);
  6996. (current_group || getCurrentDrawing().getCurrentLayer()).appendChild(copy);
  6997. batchCmd.addSubCommand(new InsertElementCommand(copy));
  6998. }
  6999. selectOnly(pasted);
  7000. if(type !== 'in_place') {
  7001. var ctr_x, ctr_y;
  7002. if(!type) {
  7003. ctr_x = lastClickPoint.x;
  7004. ctr_y = lastClickPoint.y;
  7005. } else if(type === 'point') {
  7006. ctr_x = x;
  7007. ctr_y = y;
  7008. }
  7009. var bbox = getStrokedBBox(pasted);
  7010. var cx = ctr_x - (bbox.x + bbox.width/2),
  7011. cy = ctr_y - (bbox.y + bbox.height/2),
  7012. dx = [],
  7013. dy = [];
  7014. $.each(pasted, function(i, item) {
  7015. dx.push(cx);
  7016. dy.push(cy);
  7017. });
  7018. var cmd = canvas.moveSelectedElements(dx, dy, false);
  7019. batchCmd.addSubCommand(cmd);
  7020. }
  7021. addCommandToHistory(batchCmd);
  7022. call("changed", pasted);
  7023. }
  7024. // Function: groupSelectedElements
  7025. // Wraps all the selected elements in a group (g) element
  7026. // Parameters:
  7027. // type - type of element to group into, defaults to <g>
  7028. this.groupSelectedElements = function(type) {
  7029. if(!type) type = 'g';
  7030. var cmd_str = '';
  7031. switch ( type ) {
  7032. case "a":
  7033. cmd_str = "Make hyperlink";
  7034. var url = '';
  7035. if(arguments.length > 1) {
  7036. url = arguments[1];
  7037. }
  7038. break;
  7039. default:
  7040. type = 'g';
  7041. cmd_str = "Group Elements";
  7042. break;
  7043. }
  7044. var batchCmd = new BatchCommand(cmd_str);
  7045. // create and insert the group element
  7046. var g = addSvgElementFromJson({
  7047. "element": type,
  7048. "attr": {
  7049. "id": getNextId()
  7050. }
  7051. });
  7052. if(type === 'a') {
  7053. setHref(g, url);
  7054. }
  7055. batchCmd.addSubCommand(new InsertElementCommand(g));
  7056. // now move all children into the group
  7057. var i = selectedElements.length;
  7058. while (i--) {
  7059. var elem = selectedElements[i];
  7060. if (elem == null) continue;
  7061. if (elem.parentNode.tagName === 'a' && elem.parentNode.childNodes.length === 1) {
  7062. elem = elem.parentNode;
  7063. }
  7064. var oldNextSibling = elem.nextSibling;
  7065. var oldParent = elem.parentNode;
  7066. g.appendChild(elem);
  7067. batchCmd.addSubCommand(new MoveElementCommand(elem, oldNextSibling, oldParent));
  7068. }
  7069. if (!batchCmd.isEmpty()) addCommandToHistory(batchCmd);
  7070. // update selection
  7071. selectOnly([g], true);
  7072. };
  7073. // Function: pushGroupProperties
  7074. // Pushes all appropriate parent group properties down to its children, then
  7075. // removes them from the group
  7076. var pushGroupProperties = this.pushGroupProperties = function(g, undoable) {
  7077. var children = g.childNodes;
  7078. var len = children.length;
  7079. var xform = g.getAttribute("transform");
  7080. var glist = getTransformList(g);
  7081. var m = transformListToTransform(glist).matrix;
  7082. var batchCmd = new BatchCommand("Push group properties");
  7083. // TODO: get all fill/stroke properties from the group that we are about to destroy
  7084. // "fill", "fill-opacity", "fill-rule", "stroke", "stroke-dasharray", "stroke-dashoffset",
  7085. // "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity",
  7086. // "stroke-width"
  7087. // and then for each child, if they do not have the attribute (or the value is 'inherit')
  7088. // then set the child's attribute
  7089. var i = 0;
  7090. var gangle = getRotationAngle(g);
  7091. var gattrs = $(g).attr(['filter', 'opacity']);
  7092. var gfilter, gblur;
  7093. for(var i = 0; i < len; i++) {
  7094. var elem = children[i];
  7095. if(elem.nodeType !== 1) continue;
  7096. if(gattrs.opacity !== null && gattrs.opacity !== 1) {
  7097. var c_opac = elem.getAttribute('opacity') || 1;
  7098. var new_opac = Math.round((elem.getAttribute('opacity') || 1) * gattrs.opacity * 100)/100;
  7099. changeSelectedAttribute('opacity', new_opac, [elem]);
  7100. }
  7101. if(gattrs.filter) {
  7102. var cblur = this.getBlur(elem);
  7103. var orig_cblur = cblur;
  7104. if(!gblur) gblur = this.getBlur(g);
  7105. if(cblur) {
  7106. // Is this formula correct?
  7107. cblur = (gblur-0) + (cblur-0);
  7108. } else if(cblur === 0) {
  7109. cblur = gblur;
  7110. }
  7111. // If child has no current filter, get group's filter or clone it.
  7112. if(!orig_cblur) {
  7113. // Set group's filter to use first child's ID
  7114. if(!gfilter) {
  7115. gfilter = getRefElem(gattrs.filter);
  7116. } else {
  7117. // Clone the group's filter
  7118. gfilter = copyElem(gfilter);
  7119. findDefs().appendChild(gfilter);
  7120. }
  7121. } else {
  7122. gfilter = getRefElem(elem.getAttribute('filter'));
  7123. }
  7124. // Change this in future for different filters
  7125. var suffix = (gfilter.firstChild.tagName === 'feGaussianBlur')?'blur':'filter';
  7126. gfilter.id = elem.id + '_' + suffix;
  7127. changeSelectedAttribute('filter', 'url(#' + gfilter.id + ')', [elem]);
  7128. // Update blur value
  7129. if(cblur) {
  7130. changeSelectedAttribute('stdDeviation', cblur, [gfilter.firstChild]);
  7131. canvas.setBlurOffsets(gfilter, cblur);
  7132. }
  7133. }
  7134. var chtlist = getTransformList(elem);
  7135. // Don't process gradient transforms
  7136. if(~elem.tagName.indexOf('Gradient')) chtlist = null;
  7137. // Hopefully not a problem to add this. Necessary for elements like <desc/>
  7138. if(!chtlist) continue;
  7139. // Apparently <defs> can get get a transformlist, but we don't want it to have one!
  7140. if(elem.tagName === 'defs') continue;
  7141. if (glist.numberOfItems) {
  7142. // TODO: if the group's transform is just a rotate, we can always transfer the
  7143. // rotate() down to the children (collapsing consecutive rotates and factoring
  7144. // out any translates)
  7145. if (gangle && glist.numberOfItems == 1) {
  7146. // [Rg] [Rc] [Mc]
  7147. // we want [Tr] [Rc2] [Mc] where:
  7148. // - [Rc2] is at the child's current center but has the
  7149. // sum of the group and child's rotation angles
  7150. // - [Tr] is the equivalent translation that this child
  7151. // undergoes if the group wasn't there
  7152. // [Tr] = [Rg] [Rc] [Rc2_inv]
  7153. // get group's rotation matrix (Rg)
  7154. var rgm = glist.getItem(0).matrix;
  7155. // get child's rotation matrix (Rc)
  7156. var rcm = svgroot.createSVGMatrix();
  7157. var cangle = getRotationAngle(elem);
  7158. if (cangle) {
  7159. rcm = chtlist.getItem(0).matrix;
  7160. }
  7161. // get child's old center of rotation
  7162. var cbox = svgedit.utilities.getBBox(elem);
  7163. var ceqm = transformListToTransform(chtlist).matrix;
  7164. var coldc = transformPoint(cbox.x+cbox.width/2, cbox.y+cbox.height/2,ceqm);
  7165. // sum group and child's angles
  7166. var sangle = gangle + cangle;
  7167. // get child's rotation at the old center (Rc2_inv)
  7168. var r2 = svgroot.createSVGTransform();
  7169. r2.setRotate(sangle, coldc.x, coldc.y);
  7170. // calculate equivalent translate
  7171. var trm = matrixMultiply(rgm, rcm, r2.matrix.inverse());
  7172. // set up tlist
  7173. if (cangle) {
  7174. chtlist.removeItem(0);
  7175. }
  7176. if (sangle) {
  7177. if(chtlist.numberOfItems) {
  7178. chtlist.insertItemBefore(r2, 0);
  7179. } else {
  7180. chtlist.appendItem(r2);
  7181. }
  7182. }
  7183. if (trm.e || trm.f) {
  7184. var tr = svgroot.createSVGTransform();
  7185. tr.setTranslate(trm.e, trm.f);
  7186. if(chtlist.numberOfItems) {
  7187. chtlist.insertItemBefore(tr, 0);
  7188. } else {
  7189. chtlist.appendItem(tr);
  7190. }
  7191. }
  7192. }
  7193. else { // more complicated than just a rotate
  7194. // transfer the group's transform down to each child and then
  7195. // call recalculateDimensions()
  7196. var oldxform = elem.getAttribute("transform");
  7197. var changes = {};
  7198. changes["transform"] = oldxform ? oldxform : "";
  7199. var newxform = svgroot.createSVGTransform();
  7200. // [ gm ] [ chm ] = [ chm ] [ gm' ]
  7201. // [ gm' ] = [ chm_inv ] [ gm ] [ chm ]
  7202. var chm = transformListToTransform(chtlist).matrix,
  7203. chm_inv = chm.inverse();
  7204. var gm = matrixMultiply( chm_inv, m, chm );
  7205. newxform.setMatrix(gm);
  7206. chtlist.appendItem(newxform);
  7207. }
  7208. var cmd = recalculateDimensions(elem);
  7209. if(cmd) batchCmd.addSubCommand(cmd);
  7210. }
  7211. }
  7212. // remove transform and make it undo-able
  7213. if (xform) {
  7214. var changes = {};
  7215. changes["transform"] = xform;
  7216. g.setAttribute("transform", "");
  7217. g.removeAttribute("transform");
  7218. batchCmd.addSubCommand(new ChangeElementCommand(g, changes));
  7219. }
  7220. if (undoable && !batchCmd.isEmpty()) {
  7221. return batchCmd;
  7222. }
  7223. }
  7224. // Function: ungroupSelectedElement
  7225. // Unwraps all the elements in a selected group (g) element. This requires
  7226. // significant recalculations to apply group's transforms, etc to its children
  7227. this.ungroupSelectedElement = function() {
  7228. var g = selectedElements[0];
  7229. if($(g).data('gsvg') || $(g).data('symbol')) {
  7230. // Is svg, so actually convert to group
  7231. convertToGroup(g);
  7232. return;
  7233. } else if(g.tagName === 'use') {
  7234. // Somehow doesn't have data set, so retrieve
  7235. var symbol = getElem(getHref(g).substr(1));
  7236. $(g).data('symbol', symbol).data('ref', symbol);
  7237. convertToGroup(g);
  7238. return;
  7239. }
  7240. var parents_a = $(g).parents('a');
  7241. if(parents_a.length) {
  7242. g = parents_a[0];
  7243. }
  7244. // Look for parent "a"
  7245. if (g.tagName === "g" || g.tagName === "a") {
  7246. var batchCmd = new BatchCommand("Ungroup Elements");
  7247. var cmd = pushGroupProperties(g, true);
  7248. if(cmd) batchCmd.addSubCommand(cmd);
  7249. var parent = g.parentNode;
  7250. var anchor = g.nextSibling;
  7251. var children = new Array(g.childNodes.length);
  7252. var i = 0;
  7253. while (g.firstChild) {
  7254. var elem = g.firstChild;
  7255. var oldNextSibling = elem.nextSibling;
  7256. var oldParent = elem.parentNode;
  7257. // Remove child title elements
  7258. if(elem.tagName === 'title') {
  7259. var nextSibling = elem.nextSibling;
  7260. batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, oldParent));
  7261. oldParent.removeChild(elem);
  7262. continue;
  7263. }
  7264. children[i++] = elem = parent.insertBefore(elem, anchor);
  7265. batchCmd.addSubCommand(new MoveElementCommand(elem, oldNextSibling, oldParent));
  7266. }
  7267. // remove the group from the selection
  7268. clearSelection();
  7269. // delete the group element (but make undo-able)
  7270. var gNextSibling = g.nextSibling;
  7271. g = parent.removeChild(g);
  7272. batchCmd.addSubCommand(new RemoveElementCommand(g, gNextSibling, parent));
  7273. if (!batchCmd.isEmpty()) addCommandToHistory(batchCmd);
  7274. // update selection
  7275. addToSelection(children);
  7276. }
  7277. };
  7278. // Function: moveToTopSelectedElement
  7279. // Repositions the selected element to the bottom in the DOM to appear on top of
  7280. // other elements
  7281. this.moveToTopSelectedElement = function() {
  7282. var selected = selectedElements[0];
  7283. if (selected != null) {
  7284. var t = selected;
  7285. var oldParent = t.parentNode;
  7286. var oldNextSibling = t.nextSibling;
  7287. t = t.parentNode.appendChild(t);
  7288. // If the element actually moved position, add the command and fire the changed
  7289. // event handler.
  7290. if (oldNextSibling != t.nextSibling) {
  7291. addCommandToHistory(new MoveElementCommand(t, oldNextSibling, oldParent, "top"));
  7292. call("changed", [t]);
  7293. }
  7294. }
  7295. };
  7296. // Function: moveToBottomSelectedElement
  7297. // Repositions the selected element to the top in the DOM to appear under
  7298. // other elements
  7299. this.moveToBottomSelectedElement = function() {
  7300. var selected = selectedElements[0];
  7301. if (selected != null) {
  7302. var t = selected;
  7303. var oldParent = t.parentNode;
  7304. var oldNextSibling = t.nextSibling;
  7305. var firstChild = t.parentNode.firstChild;
  7306. if (firstChild.tagName == 'title') {
  7307. firstChild = firstChild.nextSibling;
  7308. }
  7309. // This can probably be removed, as the defs should not ever apppear
  7310. // inside a layer group
  7311. if (firstChild.tagName == 'defs') {
  7312. firstChild = firstChild.nextSibling;
  7313. }
  7314. t = t.parentNode.insertBefore(t, firstChild);
  7315. // If the element actually moved position, add the command and fire the changed
  7316. // event handler.
  7317. if (oldNextSibling != t.nextSibling) {
  7318. addCommandToHistory(new MoveElementCommand(t, oldNextSibling, oldParent, "bottom"));
  7319. call("changed", [t]);
  7320. }
  7321. }
  7322. };
  7323. // Function: moveUpDownSelected
  7324. // Moves the select element up or down the stack, based on the visibly
  7325. // intersecting elements
  7326. //
  7327. // Parameters:
  7328. // dir - String that's either 'Up' or 'Down'
  7329. this.moveUpDownSelected = function(dir) {
  7330. var selected = selectedElements[0];
  7331. if (!selected) return;
  7332. curBBoxes = [];
  7333. var closest, found_cur;
  7334. // jQuery sorts this list
  7335. var list = $(getIntersectionList(getStrokedBBox([selected]))).toArray();
  7336. if(dir == 'Down') list.reverse();
  7337. $.each(list, function() {
  7338. if(!found_cur) {
  7339. if(this == selected) {
  7340. found_cur = true;
  7341. }
  7342. return;
  7343. }
  7344. closest = this;
  7345. return false;
  7346. });
  7347. if(!closest) return;
  7348. var t = selected;
  7349. var oldParent = t.parentNode;
  7350. var oldNextSibling = t.nextSibling;
  7351. $(closest)[dir == 'Down'?'before':'after'](t);
  7352. // If the element actually moved position, add the command and fire the changed
  7353. // event handler.
  7354. if (oldNextSibling != t.nextSibling) {
  7355. addCommandToHistory(new MoveElementCommand(t, oldNextSibling, oldParent, "Move " + dir));
  7356. call("changed", [t]);
  7357. }
  7358. };
  7359. // Function: moveSelectedElements
  7360. // Moves selected elements on the X/Y axis
  7361. //
  7362. // Parameters:
  7363. // dx - Float with the distance to move on the x-axis
  7364. // dy - Float with the distance to move on the y-axis
  7365. // undoable - Boolean indicating whether or not the action should be undoable
  7366. //
  7367. // Returns:
  7368. // Batch command for the move
  7369. this.moveSelectedElements = function(dx, dy, undoable) {
  7370. // if undoable is not sent, default to true
  7371. // if single values, scale them to the zoom
  7372. if (dx.constructor != Array) {
  7373. dx /= current_zoom;
  7374. dy /= current_zoom;
  7375. }
  7376. var undoable = undoable || true;
  7377. var batchCmd = new BatchCommand("position");
  7378. var i = selectedElements.length;
  7379. while (i--) {
  7380. var selected = selectedElements[i];
  7381. if (selected != null) {
  7382. // if (i==0)
  7383. // selectedBBoxes[0] = svgedit.utilities.getBBox(selected);
  7384. // var b = {};
  7385. // for(var j in selectedBBoxes[i]) b[j] = selectedBBoxes[i][j];
  7386. // selectedBBoxes[i] = b;
  7387. var xform = svgroot.createSVGTransform();
  7388. var tlist = getTransformList(selected);
  7389. // dx and dy could be arrays
  7390. if (dx.constructor == Array) {
  7391. // if (i==0) {
  7392. // selectedBBoxes[0].x += dx[0];
  7393. // selectedBBoxes[0].y += dy[0];
  7394. // }
  7395. xform.setTranslate(dx[i],dy[i]);
  7396. } else {
  7397. // if (i==0) {
  7398. // selectedBBoxes[0].x += dx;
  7399. // selectedBBoxes[0].y += dy;
  7400. // }
  7401. xform.setTranslate(dx,dy);
  7402. }
  7403. if(tlist.numberOfItems) {
  7404. tlist.insertItemBefore(xform, 0);
  7405. } else {
  7406. tlist.appendItem(xform);
  7407. }
  7408. var cmd = recalculateDimensions(selected);
  7409. if (cmd) {
  7410. batchCmd.addSubCommand(cmd);
  7411. }
  7412. selectorManager.requestSelector(selected).resize();
  7413. }
  7414. }
  7415. if (!batchCmd.isEmpty()) {
  7416. if (undoable)
  7417. addCommandToHistory(batchCmd);
  7418. call("changed", selectedElements);
  7419. return batchCmd;
  7420. }
  7421. };
  7422. // Function: cloneSelectedElements
  7423. // Create deep DOM copies (clones) of all selected elements and move them slightly
  7424. // from their originals
  7425. this.cloneSelectedElements = function(x,y) {
  7426. var batchCmd = new BatchCommand("Clone Elements");
  7427. // find all the elements selected (stop at first null)
  7428. var len = selectedElements.length;
  7429. for (var i = 0; i < len; ++i) {
  7430. var elem = selectedElements[i];
  7431. if (elem == null) break;
  7432. }
  7433. // use slice to quickly get the subset of elements we need
  7434. var copiedElements = selectedElements.slice(0,i);
  7435. this.clearSelection(true);
  7436. // note that we loop in the reverse way because of the way elements are added
  7437. // to the selectedElements array (top-first)
  7438. var i = copiedElements.length;
  7439. while (i--) {
  7440. // clone each element and replace it within copiedElements
  7441. var elem = copiedElements[i] = copyElem(copiedElements[i]);
  7442. (current_group || getCurrentDrawing().getCurrentLayer()).appendChild(elem);
  7443. batchCmd.addSubCommand(new InsertElementCommand(elem));
  7444. }
  7445. if (!batchCmd.isEmpty()) {
  7446. addToSelection(copiedElements.reverse()); // Need to reverse for correct selection-adding
  7447. this.moveSelectedElements(x,y,false);
  7448. addCommandToHistory(batchCmd);
  7449. }
  7450. };
  7451. // Function: alignSelectedElements
  7452. // Aligns selected elements
  7453. //
  7454. // Parameters:
  7455. // type - String with single character indicating the alignment type
  7456. // relative_to - String that must be one of the following:
  7457. // "selected", "largest", "smallest", "page"
  7458. this.alignSelectedElements = function(type, relative_to) {
  7459. var bboxes = [], angles = [];
  7460. var minx = Number.MAX_VALUE, maxx = Number.MIN_VALUE, miny = Number.MAX_VALUE, maxy = Number.MIN_VALUE;
  7461. var curwidth = Number.MIN_VALUE, curheight = Number.MIN_VALUE;
  7462. var len = selectedElements.length;
  7463. if (!len) return;
  7464. for (var i = 0; i < len; ++i) {
  7465. if (selectedElements[i] == null) break;
  7466. var elem = selectedElements[i];
  7467. bboxes[i] = getStrokedBBox([elem]);
  7468. // now bbox is axis-aligned and handles rotation
  7469. switch (relative_to) {
  7470. case 'smallest':
  7471. if ( (type == 'l' || type == 'c' || type == 'r') && (curwidth == Number.MIN_VALUE || curwidth > bboxes[i].width) ||
  7472. (type == 't' || type == 'm' || type == 'b') && (curheight == Number.MIN_VALUE || curheight > bboxes[i].height) ) {
  7473. minx = bboxes[i].x;
  7474. miny = bboxes[i].y;
  7475. maxx = bboxes[i].x + bboxes[i].width;
  7476. maxy = bboxes[i].y + bboxes[i].height;
  7477. curwidth = bboxes[i].width;
  7478. curheight = bboxes[i].height;
  7479. }
  7480. break;
  7481. case 'largest':
  7482. if ( (type == 'l' || type == 'c' || type == 'r') && (curwidth == Number.MIN_VALUE || curwidth < bboxes[i].width) ||
  7483. (type == 't' || type == 'm' || type == 'b') && (curheight == Number.MIN_VALUE || curheight < bboxes[i].height) ) {
  7484. minx = bboxes[i].x;
  7485. miny = bboxes[i].y;
  7486. maxx = bboxes[i].x + bboxes[i].width;
  7487. maxy = bboxes[i].y + bboxes[i].height;
  7488. curwidth = bboxes[i].width;
  7489. curheight = bboxes[i].height;
  7490. }
  7491. break;
  7492. default: // 'selected'
  7493. if (bboxes[i].x < minx) minx = bboxes[i].x;
  7494. if (bboxes[i].y < miny) miny = bboxes[i].y;
  7495. if (bboxes[i].x + bboxes[i].width > maxx) maxx = bboxes[i].x + bboxes[i].width;
  7496. if (bboxes[i].y + bboxes[i].height > maxy) maxy = bboxes[i].y + bboxes[i].height;
  7497. break;
  7498. }
  7499. } // loop for each element to find the bbox and adjust min/max
  7500. if (relative_to == 'page') {
  7501. minx = 0;
  7502. miny = 0;
  7503. maxx = canvas.contentW;
  7504. maxy = canvas.contentH;
  7505. }
  7506. var dx = new Array(len);
  7507. var dy = new Array(len);
  7508. for (var i = 0; i < len; ++i) {
  7509. if (selectedElements[i] == null) break;
  7510. var elem = selectedElements[i];
  7511. var bbox = bboxes[i];
  7512. dx[i] = 0;
  7513. dy[i] = 0;
  7514. switch (type) {
  7515. case 'l': // left (horizontal)
  7516. dx[i] = minx - bbox.x;
  7517. break;
  7518. case 'c': // center (horizontal)
  7519. dx[i] = (minx+maxx)/2 - (bbox.x + bbox.width/2);
  7520. break;
  7521. case 'r': // right (horizontal)
  7522. dx[i] = maxx - (bbox.x + bbox.width);
  7523. break;
  7524. case 't': // top (vertical)
  7525. dy[i] = miny - bbox.y;
  7526. break;
  7527. case 'm': // middle (vertical)
  7528. dy[i] = (miny+maxy)/2 - (bbox.y + bbox.height/2);
  7529. break;
  7530. case 'b': // bottom (vertical)
  7531. dy[i] = maxy - (bbox.y + bbox.height);
  7532. break;
  7533. }
  7534. }
  7535. this.moveSelectedElements(dx,dy);
  7536. };
  7537. // Group: Additional editor tools
  7538. this.contentW = getResolution().w;
  7539. this.contentH = getResolution().h;
  7540. // Function: updateCanvas
  7541. // Updates the editor canvas width/height/position after a zoom has occurred
  7542. //
  7543. // Parameters:
  7544. // w - Float with the new width
  7545. // h - Float with the new height
  7546. //
  7547. // Returns:
  7548. // Object with the following values:
  7549. // * x - The canvas' new x coordinate
  7550. // * y - The canvas' new y coordinate
  7551. // * old_x - The canvas' old x coordinate
  7552. // * old_y - The canvas' old y coordinate
  7553. // * d_x - The x position difference
  7554. // * d_y - The y position difference
  7555. this.updateCanvas = function(w, h) {
  7556. svgroot.setAttribute("width", w);
  7557. svgroot.setAttribute("height", h);
  7558. var bg = $('#canvasBackground')[0];
  7559. var old_x = svgcontent.getAttribute('x');
  7560. var old_y = svgcontent.getAttribute('y');
  7561. var x = (w/2 - this.contentW*current_zoom/2);
  7562. var y = (h/2 - this.contentH*current_zoom/2);
  7563. assignAttributes(svgcontent, {
  7564. width: this.contentW*current_zoom,
  7565. height: this.contentH*current_zoom,
  7566. 'x': x,
  7567. 'y': y,
  7568. "viewBox" : "0 0 " + this.contentW + " " + this.contentH
  7569. });
  7570. assignAttributes(bg, {
  7571. width: svgcontent.getAttribute('width'),
  7572. height: svgcontent.getAttribute('height'),
  7573. x: x,
  7574. y: y
  7575. });
  7576. var bg_img = getElem('background_image');
  7577. if (bg_img) {
  7578. assignAttributes(bg_img, {
  7579. 'width': '100%',
  7580. 'height': '100%'
  7581. });
  7582. }
  7583. selectorManager.selectorParentGroup.setAttribute("transform","translate(" + x + "," + y + ")");
  7584. return {x:x, y:y, old_x:old_x, old_y:old_y, d_x:x - old_x, d_y:y - old_y};
  7585. }
  7586. // Function: setBackground
  7587. // Set the background of the editor (NOT the actual document)
  7588. //
  7589. // Parameters:
  7590. // color - String with fill color to apply
  7591. // url - URL or path to image to use
  7592. this.setBackground = function(color, url) {
  7593. var bg = getElem('canvasBackground');
  7594. var border = $(bg).find('rect')[0];
  7595. var bg_img = getElem('background_image');
  7596. border.setAttribute('fill',color);
  7597. if(url) {
  7598. if(!bg_img) {
  7599. bg_img = svgdoc.createElementNS(svgns, "image");
  7600. assignAttributes(bg_img, {
  7601. 'id': 'background_image',
  7602. 'width': '100%',
  7603. 'height': '100%',
  7604. 'preserveAspectRatio': 'xMinYMin',
  7605. 'style':'pointer-events:none'
  7606. });
  7607. }
  7608. setHref(bg_img, url);
  7609. bg.appendChild(bg_img);
  7610. } else if(bg_img) {
  7611. bg_img.parentNode.removeChild(bg_img);
  7612. }
  7613. }
  7614. // Function: cycleElement
  7615. // Select the next/previous element within the current layer
  7616. //
  7617. // Parameters:
  7618. // next - Boolean where true = next and false = previous element
  7619. this.cycleElement = function(next) {
  7620. var cur_elem = selectedElements[0];
  7621. var elem = false;
  7622. var all_elems = getVisibleElements(current_group || getCurrentDrawing().getCurrentLayer());
  7623. if(!all_elems.length) return;
  7624. if (cur_elem == null) {
  7625. var num = next?all_elems.length-1:0;
  7626. elem = all_elems[num];
  7627. } else {
  7628. var i = all_elems.length;
  7629. while(i--) {
  7630. if(all_elems[i] == cur_elem) {
  7631. var num = next?i-1:i+1;
  7632. if(num >= all_elems.length) {
  7633. num = 0;
  7634. } else if(num < 0) {
  7635. num = all_elems.length-1;
  7636. }
  7637. elem = all_elems[num];
  7638. break;
  7639. }
  7640. }
  7641. }
  7642. selectOnly([elem], true);
  7643. call("selected", selectedElements);
  7644. }
  7645. this.clear();
  7646. // DEPRECATED: getPrivateMethods
  7647. // Since all methods are/should be public somehow, this function should be removed
  7648. // Being able to access private methods publicly seems wrong somehow,
  7649. // but currently appears to be the best way to allow testing and provide
  7650. // access to them to plugins.
  7651. this.getPrivateMethods = function() {
  7652. var obj = {
  7653. addCommandToHistory: addCommandToHistory,
  7654. setGradient: setGradient,
  7655. addSvgElementFromJson: addSvgElementFromJson,
  7656. assignAttributes: assignAttributes,
  7657. BatchCommand: BatchCommand,
  7658. call: call,
  7659. ChangeElementCommand: ChangeElementCommand,
  7660. copyElem: copyElem,
  7661. ffClone: ffClone,
  7662. findDefs: findDefs,
  7663. findDuplicateGradient: findDuplicateGradient,
  7664. getElem: getElem,
  7665. getId: getId,
  7666. getIntersectionList: getIntersectionList,
  7667. getMouseTarget: getMouseTarget,
  7668. getNextId: getNextId,
  7669. getPathBBox: getPathBBox,
  7670. getUrlFromAttr: getUrlFromAttr,
  7671. hasMatrixTransform: hasMatrixTransform,
  7672. identifyLayers: identifyLayers,
  7673. InsertElementCommand: InsertElementCommand,
  7674. isIdentity: svgedit.math.isIdentity,
  7675. logMatrix: logMatrix,
  7676. matrixMultiply: matrixMultiply,
  7677. MoveElementCommand: MoveElementCommand,
  7678. preventClickDefault: preventClickDefault,
  7679. recalculateAllSelectedDimensions: recalculateAllSelectedDimensions,
  7680. recalculateDimensions: recalculateDimensions,
  7681. remapElement: remapElement,
  7682. RemoveElementCommand: RemoveElementCommand,
  7683. removeUnusedDefElems: removeUnusedDefElems,
  7684. round: round,
  7685. runExtensions: runExtensions,
  7686. sanitizeSvg: sanitizeSvg,
  7687. SVGEditTransformList: svgedit.transformlist.SVGTransformList,
  7688. toString: toString,
  7689. transformBox: svgedit.math.transformBox,
  7690. transformListToTransform: transformListToTransform,
  7691. transformPoint: transformPoint,
  7692. walkTree: svgedit.utilities.walkTree
  7693. }
  7694. return obj;
  7695. };
  7696. }