12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760 |
- /*globals $, svgedit, svgCanvas*/
- /*jslint vars: true, eqeq: true, todo: true, bitwise: true, continue: true, forin: true */
- /*
- * svgcanvas.js
- *
- * Licensed under the MIT License
- *
- * Copyright(c) 2010 Alexis Deveria
- * Copyright(c) 2010 Pavol Rusnak
- * Copyright(c) 2010 Jeff Schiller
- *
- */
- // Dependencies:
- // 1) jQuery
- // 2) browser.js
- // 3) svgtransformlist.js
- // 4) math.js
- // 5) units.js
- // 6) svgutils.js
- // 7) sanitize.js
- // 8) history.js
- // 9) select.js
- // 10) draw.js
- // 11) path.js
- // 12) coords.js
- // 13) recalculate.js
- (function () {
- if (!window.console) {
- window.console = {};
- window.console.log = function(str) {};
- window.console.dir = function(str) {};
- }
- if (window.opera) {
- window.console.log = function(str) { opera.postError(str); };
- window.console.dir = function(str) {};
- }
- }());
- // Class: SvgCanvas
- // The main SvgCanvas class that manages all SVG-related functions
- //
- // Parameters:
- // container - The container HTML element that should hold the SVG root element
- // config - An object that contains configuration data
- $.SvgCanvas = function(container, config) {
- // Alias Namespace constants
- var NS = svgedit.NS;
- // Default configuration options
- var curConfig = {
- show_outside_canvas: true,
- selectNew: true,
- dimensions: [640, 480]
- };
- // Update config with new one if given
- if (config) {
- $.extend(curConfig, config);
- }
- // Array with width/height of canvas
- var dimensions = curConfig.dimensions;
- var canvas = this;
- // "document" element associated with the container (same as window.document using default svg-editor.js)
- // NOTE: This is not actually a SVG document, but a HTML document.
- var svgdoc = container.ownerDocument;
- // This is a container for the document being edited, not the document itself.
- var svgroot = svgdoc.importNode(svgedit.utilities.text2xml(
- '<svg id="svgroot" xmlns="' + NS.SVG + '" xlinkns="' + NS.XLINK + '" ' +
- 'width="' + dimensions[0] + '" height="' + dimensions[1] + '" x="' + dimensions[0] + '" y="' + dimensions[1] + '" overflow="visible">' +
- '<defs>' +
- '<filter id="canvashadow" filterUnits="objectBoundingBox">' +
- '<feGaussianBlur in="SourceAlpha" stdDeviation="4" result="blur"/>'+
- '<feOffset in="blur" dx="5" dy="5" result="offsetBlur"/>'+
- '<feMerge>'+
- '<feMergeNode in="offsetBlur"/>'+
- '<feMergeNode in="SourceGraphic"/>'+
- '</feMerge>'+
- '</filter>'+
- '</defs>'+
- '</svg>').documentElement, true);
- container.appendChild(svgroot);
- // The actual element that represents the final output SVG element
- var svgcontent = svgdoc.createElementNS(NS.SVG, "svg");
- // This function resets the svgcontent element while keeping it in the DOM.
- var clearSvgContentElement = canvas.clearSvgContentElement = function() {
- while (svgcontent.firstChild) { svgcontent.removeChild(svgcontent.firstChild); }
- // TODO: Clear out all other attributes first?
- $(svgcontent).attr({
- id: 'svgcontent',
- width: dimensions[0],
- height: dimensions[1],
- x: dimensions[0],
- y: dimensions[1],
- overflow: curConfig.show_outside_canvas ? 'visible' : 'hidden',
- xmlns: NS.SVG,
- "xmlns:se": NS.SE,
- "xmlns:xlink": NS.XLINK
- }).appendTo(svgroot);
- // TODO: make this string optional and set by the client
- var comment = svgdoc.createComment(" Created with SVG-edit - http://svg-edit.googlecode.com/ ");
- svgcontent.appendChild(comment);
- };
- clearSvgContentElement();
- // Prefix string for element IDs
- var idprefix = "svg_";
- // Function: setIdPrefix
- // Changes the ID prefix to the given value
- //
- // Parameters:
- // p - String with the new prefix
- canvas.setIdPrefix = function(p) {
- idprefix = p;
- };
- // Current svgedit.draw.Drawing object
- // @type {svgedit.draw.Drawing}
- canvas.current_drawing_ = new svgedit.draw.Drawing(svgcontent, idprefix);
- // Function: getCurrentDrawing
- // Returns the current Drawing.
- // @return {svgedit.draw.Drawing}
- var getCurrentDrawing = canvas.getCurrentDrawing = function() {
- return canvas.current_drawing_;
- };
- // Float displaying the current zoom level (1 = 100%, .5 = 50%, etc)
- var current_zoom = 1;
- // pointer to current group (for in-group editing)
- var current_group = null;
- // Object containing data for the currently selected styles
- var all_properties = {
- shape: {
- fill: (curConfig.initFill.color == 'none' ? '' : '#') + curConfig.initFill.color,
- fill_paint: null,
- fill_opacity: curConfig.initFill.opacity,
- stroke: "#" + curConfig.initStroke.color,
- stroke_paint: null,
- stroke_opacity: curConfig.initStroke.opacity,
- stroke_width: curConfig.initStroke.width,
- stroke_dasharray: 'none',
- stroke_linejoin: 'miter',
- stroke_linecap: 'butt',
- opacity: curConfig.initOpacity
- }
- };
- all_properties.text = $.extend(true, {}, all_properties.shape);
- $.extend(all_properties.text, {
- fill: "#000000",
- stroke_width: 0,
- font_size: 24,
- font_family: 'serif'
- });
- // Current shape style properties
- var cur_shape = all_properties.shape;
- // Array with all the currently selected elements
- // default size of 1 until it needs to grow bigger
- var selectedElements = new Array(1);
- // Function: addSvgElementFromJson
- // Create a new SVG element based on the given object keys/values and add it to the current layer
- // The element will be ran through cleanupElement before being returned
- //
- // Parameters:
- // data - Object with the following keys/values:
- // * element - tag name of the SVG element to create
- // * attr - Object with attributes key-values to assign to the new element
- // * curStyles - Boolean indicating that current style attributes should be applied first
- //
- // Returns: The new element
- var addSvgElementFromJson = this.addSvgElementFromJson = function(data) {
- var shape = svgedit.utilities.getElem(data.attr.id);
- // if shape is a path but we need to create a rect/ellipse, then remove the path
- var current_layer = getCurrentDrawing().getCurrentLayer();
- if (shape && data.element != shape.tagName) {
- current_layer.removeChild(shape);
- shape = null;
- }
- if (!shape) {
- shape = svgdoc.createElementNS(NS.SVG, data.element);
- if (current_layer) {
- (current_group || current_layer).appendChild(shape);
- }
- }
- if (data.curStyles) {
- svgedit.utilities.assignAttributes(shape, {
- "fill": cur_shape.fill,
- "stroke": cur_shape.stroke,
- "stroke-width": cur_shape.stroke_width,
- "stroke-dasharray": cur_shape.stroke_dasharray,
- "stroke-linejoin": cur_shape.stroke_linejoin,
- "stroke-linecap": cur_shape.stroke_linecap,
- "stroke-opacity": cur_shape.stroke_opacity,
- "fill-opacity": cur_shape.fill_opacity,
- "opacity": cur_shape.opacity / 2,
- "style": "pointer-events:inherit"
- }, 100);
- }
- svgedit.utilities.assignAttributes(shape, data.attr, 100);
- svgedit.utilities.cleanupElement(shape);
- return shape;
- };
- // import svgtransformlist.js
- var getTransformList = canvas.getTransformList = svgedit.transformlist.getTransformList;
- // import from math.js.
- var transformPoint = svgedit.math.transformPoint;
- var matrixMultiply = canvas.matrixMultiply = svgedit.math.matrixMultiply;
- var hasMatrixTransform = canvas.hasMatrixTransform = svgedit.math.hasMatrixTransform;
- var transformListToTransform = canvas.transformListToTransform = svgedit.math.transformListToTransform;
- var snapToAngle = svgedit.math.snapToAngle;
- var getMatrix = svgedit.math.getMatrix;
- // initialize from units.js
- // send in an object implementing the ElementContainer interface (see units.js)
- svgedit.units.init({
- getBaseUnit: function() { return curConfig.baseUnit; },
- getElement: svgedit.utilities.getElem,
- getHeight: function() { return svgcontent.getAttribute("height")/current_zoom; },
- getWidth: function() { return svgcontent.getAttribute("width")/current_zoom; },
- getRoundDigits: function() { return save_options.round_digits; }
- });
- // import from units.js
- var convertToNum = canvas.convertToNum = svgedit.units.convertToNum;
- // import from svgutils.js
- svgedit.utilities.init({
- getDOMDocument: function() { return svgdoc; },
- getDOMContainer: function() { return container; },
- getSVGRoot: function() { return svgroot; },
- // TODO: replace this mostly with a way to get the current drawing.
- getSelectedElements: function() { return selectedElements; },
- getSVGContent: function() { return svgcontent; },
- getBaseUnit: function() { return curConfig.baseUnit; },
- getStepSize: function() { return curConfig.stepSize; }
- });
- var findDefs = canvas.findDefs = svgedit.utilities.findDefs;
- var getUrlFromAttr = canvas.getUrlFromAttr = svgedit.utilities.getUrlFromAttr;
- var getHref = canvas.getHref = svgedit.utilities.getHref;
- var setHref = canvas.setHref = svgedit.utilities.setHref;
- var getPathBBox = svgedit.utilities.getPathBBox;
- var getBBox = canvas.getBBox = svgedit.utilities.getBBox;
- var getRotationAngle = canvas.getRotationAngle = svgedit.utilities.getRotationAngle;
- var getElem = canvas.getElem = svgedit.utilities.getElem;
- var getRefElem = canvas.getRefElem = svgedit.utilities.getRefElem;
- var assignAttributes = canvas.assignAttributes = svgedit.utilities.assignAttributes;
- var cleanupElement = this.cleanupElement = svgedit.utilities.cleanupElement;
- // import from coords.js
- svgedit.coords.init({
- getDrawing: function() { return getCurrentDrawing(); },
- getGridSnapping: function() { return curConfig.gridSnapping; }
- });
- var remapElement = this.remapElement = svgedit.coords.remapElement;
- // import from recalculate.js
- svgedit.recalculate.init({
- getSVGRoot: function() { return svgroot; },
- getStartTransform: function() { return startTransform; },
- setStartTransform: function(transform) { startTransform = transform; }
- });
- var recalculateDimensions = this.recalculateDimensions = svgedit.recalculate.recalculateDimensions;
- // import from sanitize.js
- var nsMap = svgedit.getReverseNS();
- var sanitizeSvg = canvas.sanitizeSvg = svgedit.sanitize.sanitizeSvg;
- // import from history.js
- var MoveElementCommand = svgedit.history.MoveElementCommand;
- var InsertElementCommand = svgedit.history.InsertElementCommand;
- var RemoveElementCommand = svgedit.history.RemoveElementCommand;
- var ChangeElementCommand = svgedit.history.ChangeElementCommand;
- var BatchCommand = svgedit.history.BatchCommand;
- // Implement the svgedit.history.HistoryEventHandler interface.
- canvas.undoMgr = new svgedit.history.UndoManager({
- handleHistoryEvent: function(eventType, cmd) {
- var EventTypes = svgedit.history.HistoryEventTypes;
- // TODO: handle setBlurOffsets.
- if (eventType == EventTypes.BEFORE_UNAPPLY || eventType == EventTypes.BEFORE_APPLY) {
- canvas.clearSelection();
- } else if (eventType == EventTypes.AFTER_APPLY || eventType == EventTypes.AFTER_UNAPPLY) {
- var elems = cmd.elements();
- canvas.pathActions.clear();
- call("changed", elems);
- var cmdType = cmd.type();
- var isApply = (eventType == EventTypes.AFTER_APPLY);
- if (cmdType == MoveElementCommand.type()) {
- var parent = isApply ? cmd.newParent : cmd.oldParent;
- if (parent == svgcontent) {
- canvas.identifyLayers();
- }
- } else if (cmdType == InsertElementCommand.type() ||
- cmdType == RemoveElementCommand.type()) {
- if (cmd.parent == svgcontent) {
- canvas.identifyLayers();
- }
- if (cmdType == InsertElementCommand.type()) {
- if (isApply) {restoreRefElems(cmd.elem);}
- } else {
- if (!isApply) {restoreRefElems(cmd.elem);}
- }
- if (cmd.elem.tagName === 'use') {
- setUseData(cmd.elem);
- }
- } else if (cmdType == ChangeElementCommand.type()) {
- // if we are changing layer names, re-identify all layers
- if (cmd.elem.tagName == "title" && cmd.elem.parentNode.parentNode == svgcontent) {
- canvas.identifyLayers();
- }
- var values = isApply ? cmd.newValues : cmd.oldValues;
- // If stdDeviation was changed, update the blur.
- if (values.stdDeviation) {
- canvas.setBlurOffsets(cmd.elem.parentNode, values.stdDeviation);
- }
- // This is resolved in later versions of webkit, perhaps we should
- // have a featured detection for correct 'use' behavior?
- // ——————————
- // Remove & Re-add hack for Webkit (issue 775)
- //if (cmd.elem.tagName === 'use' && svgedit.browser.isWebkit()) {
- // var elem = cmd.elem;
- // if (!elem.getAttribute('x') && !elem.getAttribute('y')) {
- // var parent = elem.parentNode;
- // var sib = elem.nextSibling;
- // parent.removeChild(elem);
- // parent.insertBefore(elem, sib);
- // }
- //}
- }
- }
- }
- });
- var addCommandToHistory = function(cmd) {
- canvas.undoMgr.addCommandToHistory(cmd);
- };
- // import from select.js
- svgedit.select.init(curConfig, {
- createSVGElement: function(jsonMap) { return canvas.addSvgElementFromJson(jsonMap); },
- svgRoot: function() { return svgroot; },
- svgContent: function() { return svgcontent; },
- currentZoom: function() { return current_zoom; },
- // TODO(codedread): Remove when getStrokedBBox() has been put into svgutils.js.
- getStrokedBBox: function(elems) { return canvas.getStrokedBBox([elems]); }
- });
- // this object manages selectors for us
- var selectorManager = this.selectorManager = svgedit.select.getSelectorManager();
- // Import from path.js
- svgedit.path.init({
- getCurrentZoom: function() { return current_zoom; },
- getSVGRoot: function() { return svgroot; }
- });
- // Interface strings, usually for title elements
- var uiStrings = {
- "exportNoBlur": "Blurred elements will appear as un-blurred",
- "exportNoforeignObject": "foreignObject elements will not appear",
- "exportNoDashArray": "Strokes will appear filled",
- "exportNoText": "Text may not appear as expected"
- };
- var visElems = 'a,circle,ellipse,foreignObject,g,image,line,path,polygon,polyline,rect,svg,text,tspan,use';
- var ref_attrs = ["clip-path", "fill", "filter", "marker-end", "marker-mid", "marker-start", "mask", "stroke"];
- var elData = $.data;
- // Animation element to change the opacity of any newly created element
- var opac_ani = document.createElementNS(NS.SVG, 'animate');
- $(opac_ani).attr({
- attributeName: 'opacity',
- begin: 'indefinite',
- dur: 1,
- fill: 'freeze'
- }).appendTo(svgroot);
- var restoreRefElems = function(elem) {
- // Look for missing reference elements, restore any found
- var o, i,
- attrs = $(elem).attr(ref_attrs);
- for (o in attrs) {
- var val = attrs[o];
- if (val && val.indexOf('url(') === 0) {
- var id = svgedit.utilities.getUrlFromAttr(val).substr(1);
- var ref = getElem(id);
- if (!ref) {
- svgedit.utilities.findDefs().appendChild(removedElements[id]);
- delete removedElements[id];
- }
- }
- }
-
- var childs = elem.getElementsByTagName('*');
-
- if (childs.length) {
- for (i = 0, l = childs.length; i < l; i++) {
- restoreRefElems(childs[i]);
- }
- }
- };
- (function() {
- // TODO For Issue 208: this is a start on a thumbnail
- // var svgthumb = svgdoc.createElementNS(NS.SVG, "use");
- // svgthumb.setAttribute('width', '100');
- // svgthumb.setAttribute('height', '100');
- // svgedit.utilities.setHref(svgthumb, '#svgcontent');
- // svgroot.appendChild(svgthumb);
- }());
- // Object to contain image data for raster images that were found encodable
- var encodableImages = {},
-
- // String with image URL of last loadable image
- last_good_img_url = curConfig.imgPath + 'logo.png',
-
- // Array with current disabled elements (for in-group editing)
- disabled_elems = [],
-
- // Object with save options
- save_options = {round_digits: 5},
-
- // Boolean indicating whether or not a draw action has been started
- started = false,
-
- // String with an element's initial transform attribute value
- startTransform = null,
-
- // String indicating the current editor mode
- current_mode = "select",
-
- // String with the current direction in which an element is being resized
- current_resize_mode = "none",
-
- // Object with IDs for imported files, to see if one was already added
- import_ids = {},
- // Current text style properties
- cur_text = all_properties.text,
-
- // Current general properties
- cur_properties = cur_shape,
-
- // Array with selected elements' Bounding box object
- // selectedBBoxes = new Array(1),
-
- // The DOM element that was just selected
- justSelected = null,
-
- // DOM element for selection rectangle drawn by the user
- rubberBox = null,
-
- // Array of current BBoxes (still needed?)
- curBBoxes = [],
-
- // Object to contain all included extensions
- extensions = {},
-
- // Canvas point for the most recent right click
- lastClickPoint = null,
-
- // Map of deleted reference elements
- removedElements = {};
- // Clipboard for cut, copy&pasted elements
- canvas.clipBoard = [];
- // Should this return an array by default, so extension results aren't overwritten?
- var runExtensions = this.runExtensions = function(action, vars, returnArray) {
- var result = returnArray ? [] : false;
- $.each(extensions, function(name, opts) {
- if (opts && action in opts) {
- if (returnArray) {
- result.push(opts[action](vars));
- } else {
- result = opts[action](vars);
- }
- }
- });
- return result;
- };
- // Function: addExtension
- // Add an extension to the editor
- //
- // Parameters:
- // name - String with the ID of the extension
- // ext_func - Function supplied by the extension with its data
- this.addExtension = function(name, ext_func) {
- var ext;
- if (!(name in extensions)) {
- // Provide private vars/funcs here. Is there a better way to do this?
- if ($.isFunction(ext_func)) {
- ext = ext_func($.extend(canvas.getPrivateMethods(), {
- svgroot: svgroot,
- svgcontent: svgcontent,
- nonce: getCurrentDrawing().getNonce(),
- selectorManager: selectorManager
- }));
- } else {
- ext = ext_func;
- }
- extensions[name] = ext;
- call("extension_added", ext);
- } else {
- console.log('Cannot add extension "' + name + '", an extension by that name already exists"');
- }
- };
-
- // This method rounds the incoming value to the nearest value based on the current_zoom
- var round = this.round = function(val) {
- return parseInt(val*current_zoom, 10)/current_zoom;
- };
- // This method sends back an array or a NodeList full of elements that
- // intersect the multi-select rubber-band-box on the current_layer only.
- //
- // Since the only browser that supports the SVG DOM getIntersectionList is Opera,
- // we need to provide an implementation here. We brute-force it for now.
- //
- // Reference:
- // Firefox does not implement getIntersectionList(), see https://bugzilla.mozilla.org/show_bug.cgi?id=501421
- // Webkit does not implement getIntersectionList(), see https://bugs.webkit.org/show_bug.cgi?id=11274
- var getIntersectionList = this.getIntersectionList = function(rect) {
- if (rubberBox == null) { return null; }
- var parent = current_group || getCurrentDrawing().getCurrentLayer();
-
- if (!curBBoxes.length) {
- // Cache all bboxes
- curBBoxes = getVisibleElementsAndBBoxes(parent);
- }
-
- var resultList = null;
- try {
- resultList = parent.getIntersectionList(rect, null);
- } catch(e) { }
- if (resultList == null || typeof(resultList.item) != "function") {
- resultList = [];
- var rubberBBox;
- if (!rect) {
- rubberBBox = rubberBox.getBBox();
- var o,
- bb = {};
-
- for (o in rubberBBox) {
- bb[o] = rubberBBox[o] / current_zoom;
- }
- rubberBBox = bb;
-
- } else {
- rubberBBox = rect;
- }
- var i = curBBoxes.length;
- while (i--) {
- if (!rubberBBox.width) {continue;}
- if (svgedit.math.rectsIntersect(rubberBBox, curBBoxes[i].bbox)) {
- resultList.push(curBBoxes[i].elem);
- }
- }
- }
- // addToSelection expects an array, but it's ok to pass a NodeList
- // because using square-bracket notation is allowed:
- // http://www.w3.org/TR/DOM-Level-2-Core/ecma-script-binding.html
- return resultList;
- };
- // TODO(codedread): Migrate this into svgutils.js
- // Function: getStrokedBBox
- // Get the bounding box for one or more stroked and/or transformed elements
- //
- // Parameters:
- // elems - Array with DOM elements to check
- //
- // Returns:
- // A single bounding box object
- getStrokedBBox = this.getStrokedBBox = function(elems) {
- if (!elems) {elems = getVisibleElements();}
- if (!elems.length) {return false;}
- // Make sure the expected BBox is returned if the element is a group
- var getCheckedBBox = function(elem) {
- try {
- // TODO: Fix issue with rotated groups. Currently they work
- // fine in FF, but not in other browsers (same problem mentioned
- // in Issue 339 comment #2).
- var bb = svgedit.utilities.getBBox(elem);
- var angle = svgedit.utilities.getRotationAngle(elem);
- if ((angle && angle % 90) ||
- svgedit.math.hasMatrixTransform(svgedit.transformlist.getTransformList(elem))) {
- // Accurate way to get BBox of rotated element in Firefox:
- // Put element in group and get its BBox
- var good_bb = false;
- // Get the BBox from the raw path for these elements
- var elemNames = ['ellipse', 'path', 'line', 'polyline', 'polygon'];
- if (elemNames.indexOf(elem.tagName) >= 0) {
- bb = good_bb = canvas.convertToPath(elem, true);
- } else if (elem.tagName == 'rect') {
- // Look for radius
- var rx = elem.getAttribute('rx');
- var ry = elem.getAttribute('ry');
- if (rx || ry) {
- bb = good_bb = canvas.convertToPath(elem, true);
- }
- }
- if (!good_bb) {
- // Must use clone else FF freaks out
- var clone = elem.cloneNode(true);
- var g = document.createElementNS(NS.SVG, "g");
- var parent = elem.parentNode;
- parent.appendChild(g);
- g.appendChild(clone);
- bb = svgedit.utilities.bboxToObj(g.getBBox());
- parent.removeChild(g);
- }
- // Old method: Works by giving the rotated BBox,
- // this is (unfortunately) what Opera and Safari do
- // natively when getting the BBox of the parent group
- // var angle = angle * Math.PI / 180.0;
- // var rminx = Number.MAX_VALUE, rminy = Number.MAX_VALUE,
- // rmaxx = Number.MIN_VALUE, rmaxy = Number.MIN_VALUE;
- // var cx = round(bb.x + bb.width/2),
- // cy = round(bb.y + bb.height/2);
- // var pts = [ [bb.x - cx, bb.y - cy],
- // [bb.x + bb.width - cx, bb.y - cy],
- // [bb.x + bb.width - cx, bb.y + bb.height - cy],
- // [bb.x - cx, bb.y + bb.height - cy] ];
- // var j = 4;
- // while (j--) {
- // var x = pts[j][0],
- // y = pts[j][1],
- // r = Math.sqrt( x*x + y*y );
- // var theta = Math.atan2(y,x) + angle;
- // x = round(r * Math.cos(theta) + cx);
- // y = round(r * Math.sin(theta) + cy);
- //
- // // now set the bbox for the shape after it's been rotated
- // if (x < rminx) rminx = x;
- // if (y < rminy) rminy = y;
- // if (x > rmaxx) rmaxx = x;
- // if (y > rmaxy) rmaxy = y;
- // }
- //
- // bb.x = rminx;
- // bb.y = rminy;
- // bb.width = rmaxx - rminx;
- // bb.height = rmaxy - rminy;
- }
- return bb;
- } catch(e) {
- console.log(elem, e);
- return null;
- }
- };
- var full_bb;
- $.each(elems, function() {
- if (full_bb) {return;}
- if (!this.parentNode) {return;}
- full_bb = getCheckedBBox(this);
- });
- // This shouldn't ever happen...
- if (full_bb == null) {return null;}
- // full_bb doesn't include the stoke, so this does no good!
- // if (elems.length == 1) return full_bb;
- var max_x = full_bb.x + full_bb.width;
- var max_y = full_bb.y + full_bb.height;
- var min_x = full_bb.x;
- var min_y = full_bb.y;
- // FIXME: same re-creation problem with this function as getCheckedBBox() above
- var getOffset = function(elem) {
- var sw = elem.getAttribute("stroke-width");
- var offset = 0;
- if (elem.getAttribute("stroke") != "none" && !isNaN(sw)) {
- offset += sw/2;
- }
- return offset;
- };
- var bboxes = [];
- $.each(elems, function(i, elem) {
- var cur_bb = getCheckedBBox(elem);
- if (cur_bb) {
- var offset = getOffset(elem);
- min_x = Math.min(min_x, cur_bb.x - offset);
- min_y = Math.min(min_y, cur_bb.y - offset);
- bboxes.push(cur_bb);
- }
- });
-
- full_bb.x = min_x;
- full_bb.y = min_y;
-
- $.each(elems, function(i, elem) {
- var cur_bb = bboxes[i];
- // ensure that elem is really an element node
- if (cur_bb && elem.nodeType == 1) {
- var offset = getOffset(elem);
- max_x = Math.max(max_x, cur_bb.x + cur_bb.width + offset);
- max_y = Math.max(max_y, cur_bb.y + cur_bb.height + offset);
- }
- });
-
- full_bb.width = max_x - min_x;
- full_bb.height = max_y - min_y;
- return full_bb;
- };
- // Function: getVisibleElements
- // Get all elements that have a BBox (excludes <defs>, <title>, etc).
- // Note that 0-opacity, off-screen etc elements are still considered "visible"
- // for this function
- //
- // Parameters:
- // parent - The parent DOM element to search within
- //
- // Returns:
- // An array with all "visible" elements.
- var getVisibleElements = this.getVisibleElements = function(parent) {
- if (!parent) {
- parent = $(svgcontent).children(); // Prevent layers from being included
- }
-
- var contentElems = [];
- $(parent).children().each(function(i, elem) {
- try {
- if (elem.getBBox()) {
- contentElems.push(elem);
- }
- } catch(e) {}
- });
- return contentElems.reverse();
- };
- // Function: getVisibleElementsAndBBoxes
- // Get all elements that have a BBox (excludes <defs>, <title>, etc).
- // Note that 0-opacity, off-screen etc elements are still considered "visible"
- // for this function
- //
- // Parameters:
- // parent - The parent DOM element to search within
- //
- // Returns:
- // An array with objects that include:
- // * elem - The element
- // * bbox - The element's BBox as retrieved from getStrokedBBox
- var getVisibleElementsAndBBoxes = this.getVisibleElementsAndBBoxes = function(parent) {
- if (!parent) {
- parent = $(svgcontent).children(); // Prevent layers from being included
- }
- var contentElems = [];
- $(parent).children().each(function(i, elem) {
- try {
- if (elem.getBBox()) {
- contentElems.push({'elem':elem, 'bbox':getStrokedBBox([elem])});
- }
- } catch(e) {}
- });
- return contentElems.reverse();
- };
- // Function: groupSvgElem
- // Wrap an SVG element into a group element, mark the group as 'gsvg'
- //
- // Parameters:
- // elem - SVG element to wrap
- var groupSvgElem = this.groupSvgElem = function(elem) {
- var g = document.createElementNS(NS.SVG, "g");
- elem.parentNode.replaceChild(g, elem);
- $(g).append(elem).data('gsvg', elem)[0].id = getNextId();
- };
- // Function: copyElem
- // Create a clone of an element, updating its ID and its children's IDs when needed
- //
- // Parameters:
- // el - DOM element to clone
- //
- // Returns: The cloned element
- var copyElem = function(el) {
- // manually create a copy of the element
- var new_el = document.createElementNS(el.namespaceURI, el.nodeName);
- $.each(el.attributes, function(i, attr) {
- if (attr.localName != '-moz-math-font-style') {
- new_el.setAttributeNS(attr.namespaceURI, attr.nodeName, attr.nodeValue);
- }
- });
- // set the copied element's new id
- new_el.removeAttribute("id");
- new_el.id = getNextId();
-
- // Opera's "d" value needs to be reset for Opera/Win/non-EN
- // Also needed for webkit (else does not keep curved segments on clone)
- if (svgedit.browser.isWebkit() && el.nodeName == 'path') {
- var fixed_d = pathActions.convertPath(el);
- new_el.setAttribute('d', fixed_d);
- }
- // now create copies of all children
- $.each(el.childNodes, function(i, child) {
- switch(child.nodeType) {
- case 1: // element node
- new_el.appendChild(copyElem(child));
- break;
- case 3: // text node
- new_el.textContent = child.nodeValue;
- break;
- default:
- break;
- }
- });
-
- if ($(el).data('gsvg')) {
- $(new_el).data('gsvg', new_el.firstChild);
- } else if ($(el).data('symbol')) {
- var ref = $(el).data('symbol');
- $(new_el).data('ref', ref).data('symbol', ref);
- } else if (new_el.tagName == 'image') {
- preventClickDefault(new_el);
- }
- return new_el;
- };
- // Set scope for these functions
- var getId, getNextId, call;
- var textActions, pathActions;
- (function(c) {
- // Object to contain editor event names and callback functions
- var events = {};
- getId = c.getId = function() { return getCurrentDrawing().getId(); };
- getNextId = c.getNextId = function() { return getCurrentDrawing().getNextId(); };
- // Function: call
- // Run the callback function associated with the given event
- //
- // Parameters:
- // event - String with the event name
- // arg - Argument to pass through to the callback function
- call = c.call = function(event, arg) {
- if (events[event]) {
- return events[event](this, arg);
- }
- };
- // Function: bind
- // Attaches a callback function to an event
- //
- // Parameters:
- // event - String indicating the name of the event
- // f - The callback function to bind to the event
- //
- // Return:
- // The previous event
- c.bind = function(event, f) {
- var old = events[event];
- events[event] = f;
- return old;
- };
- }(canvas));
- // Function: canvas.prepareSvg
- // Runs the SVG Document through the sanitizer and then updates its paths.
- //
- // Parameters:
- // newDoc - The SVG DOM document
- this.prepareSvg = function(newDoc) {
- this.sanitizeSvg(newDoc.documentElement);
- // convert paths into absolute commands
- var i, path, len,
- paths = newDoc.getElementsByTagNameNS(NS.SVG, "path");
- for (i = 0, len = paths.length; i < len; ++i) {
- path = paths[i];
- path.setAttribute('d', pathActions.convertPath(path));
- pathActions.fixEnd(path);
- }
- };
- // Function: ffClone
- // Hack for Firefox bugs where text element features aren't updated or get
- // messed up. See issue 136 and issue 137.
- // This function clones the element and re-selects it
- // TODO: Test for this bug on load and add it to "support" object instead of
- // browser sniffing
- //
- // Parameters:
- // elem - The (text) DOM element to clone
- var ffClone = function(elem) {
- if (!svgedit.browser.isGecko()) {return elem;}
- var clone = elem.cloneNode(true);
- elem.parentNode.insertBefore(clone, elem);
- elem.parentNode.removeChild(elem);
- selectorManager.releaseSelector(elem);
- selectedElements[0] = clone;
- selectorManager.requestSelector(clone).showGrips(true);
- return clone;
- };
- // this.each is deprecated, if any extension used this it can be recreated by doing this:
- // $(canvas.getRootElem()).children().each(...)
- // this.each = function(cb) {
- // $(svgroot).children().each(cb);
- // };
- // Function: setRotationAngle
- // Removes any old rotations if present, prepends a new rotation at the
- // transformed center
- //
- // Parameters:
- // val - The new rotation angle in degrees
- // preventUndo - Boolean indicating whether the action should be undoable or not
- this.setRotationAngle = function(val, preventUndo) {
- // ensure val is the proper type
- val = parseFloat(val);
- var elem = selectedElements[0];
- var oldTransform = elem.getAttribute("transform");
- var bbox = svgedit.utilities.getBBox(elem);
- var cx = bbox.x+bbox.width/2, cy = bbox.y+bbox.height/2;
- var tlist = svgedit.transformlist.getTransformList(elem);
-
- // only remove the real rotational transform if present (i.e. at index=0)
- if (tlist.numberOfItems > 0) {
- var xform = tlist.getItem(0);
- if (xform.type == 4) {
- tlist.removeItem(0);
- }
- }
- // find R_nc and insert it
- if (val != 0) {
- var center = svgedit.math.transformPoint(cx, cy, svgedit.math.transformListToTransform(tlist).matrix);
- var R_nc = svgroot.createSVGTransform();
- R_nc.setRotate(val, center.x, center.y);
- if (tlist.numberOfItems) {
- tlist.insertItemBefore(R_nc, 0);
- } else {
- tlist.appendItem(R_nc);
- }
- } else if (tlist.numberOfItems == 0) {
- elem.removeAttribute("transform");
- }
-
- if (!preventUndo) {
- // we need to undo it, then redo it so it can be undo-able! :)
- // TODO: figure out how to make changes to transform list undo-able cross-browser?
- var newTransform = elem.getAttribute("transform");
- elem.setAttribute("transform", oldTransform);
- changeSelectedAttribute("transform", newTransform, selectedElements);
- call("changed", selectedElements);
- }
- var pointGripContainer = svgedit.utilities.getElem("pathpointgrip_container");
- // if (elem.nodeName == "path" && pointGripContainer) {
- // pathActions.setPointContainerTransform(elem.getAttribute("transform"));
- // }
- var selector = selectorManager.requestSelector(selectedElements[0]);
- selector.resize();
- selector.updateGripCursors(val);
- };
- // Function: recalculateAllSelectedDimensions
- // Runs recalculateDimensions on the selected elements,
- // adding the changes to a single batch command
- var recalculateAllSelectedDimensions = this.recalculateAllSelectedDimensions = function() {
- var text = (current_resize_mode == "none" ? "position" : "size");
- var batchCmd = new svgedit.history.BatchCommand(text);
- var i = selectedElements.length;
- while (i--) {
- var elem = selectedElements[i];
- // if (svgedit.utilities.getRotationAngle(elem) && !svgedit.math.hasMatrixTransform(getTransformList(elem))) {continue;}
- var cmd = svgedit.recalculate.recalculateDimensions(elem);
- if (cmd) {
- batchCmd.addSubCommand(cmd);
- }
- }
- if (!batchCmd.isEmpty()) {
- addCommandToHistory(batchCmd);
- call("changed", selectedElements);
- }
- };
- // this is how we map paths to our preferred relative segment types
- var pathMap = [0, 'z', 'M', 'm', 'L', 'l', 'C', 'c', 'Q', 'q', 'A', 'a',
- 'H', 'h', 'V', 'v', 'S', 's', 'T', 't'];
-
- // Debug tool to easily see the current matrix in the browser's console
- var logMatrix = function(m) {
- console.log([m.a, m.b, m.c, m.d, m.e, m.f]);
- };
- // Root Current Transformation Matrix in user units
- var root_sctm = null;
- // Group: Selection
- // Function: clearSelection
- // Clears the selection. The 'selected' handler is then called.
- // Parameters:
- // noCall - Optional boolean that when true does not call the "selected" handler
- var clearSelection = this.clearSelection = function(noCall) {
- if (selectedElements[0] != null) {
- var i, elem,
- len = selectedElements.length;
- for (i = 0; i < len; ++i) {
- elem = selectedElements[i];
- if (elem == null) {break;}
- selectorManager.releaseSelector(elem);
- selectedElements[i] = null;
- }
- // selectedBBoxes[0] = null;
- }
- if (!noCall) {call("selected", selectedElements);}
- };
- // TODO: do we need to worry about selectedBBoxes here?
- // Function: addToSelection
- // Adds a list of elements to the selection. The 'selected' handler is then called.
- //
- // Parameters:
- // elemsToAdd - an array of DOM elements to add to the selection
- // showGrips - a boolean flag indicating whether the resize grips should be shown
- var addToSelection = this.addToSelection = function(elemsToAdd, showGrips) {
- if (elemsToAdd.length == 0) { return; }
- // find the first null in our selectedElements array
- var j = 0;
-
- while (j < selectedElements.length) {
- if (selectedElements[j] == null) {
- break;
- }
- ++j;
- }
- // now add each element consecutively
- var i = elemsToAdd.length;
- while (i--) {
- var elem = elemsToAdd[i];
- if (!elem || !svgedit.utilities.getBBox(elem)) {continue;}
- if (elem.tagName === 'a' && elem.childNodes.length === 1) {
- // Make "a" element's child be the selected element
- elem = elem.firstChild;
- }
- // if it's not already there, add it
- if (selectedElements.indexOf(elem) == -1) {
- selectedElements[j] = elem;
- // only the first selectedBBoxes element is ever used in the codebase these days
- // if (j == 0) selectedBBoxes[0] = svgedit.utilities.getBBox(elem);
- j++;
- var sel = selectorManager.requestSelector(elem);
-
- if (selectedElements.length > 1) {
- sel.showGrips(false);
- }
- }
- }
- call("selected", selectedElements);
-
- if (showGrips || selectedElements.length == 1) {
- selectorManager.requestSelector(selectedElements[0]).showGrips(true);
- }
- else {
- selectorManager.requestSelector(selectedElements[0]).showGrips(false);
- }
- // make sure the elements are in the correct order
- // See: http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-compareDocumentPosition
- selectedElements.sort(function(a, b) {
- if (a && b && a.compareDocumentPosition) {
- return 3 - (b.compareDocumentPosition(a) & 6);
- }
- if (a == null) {
- return 1;
- }
- });
-
- // Make sure first elements are not null
- while (selectedElements[0] == null) {
- selectedElements.shift(0);
- }
- };
- // Function: selectOnly()
- // Selects only the given elements, shortcut for clearSelection(); addToSelection()
- //
- // Parameters:
- // elems - an array of DOM elements to be selected
- var selectOnly = this.selectOnly = function(elems, showGrips) {
- clearSelection(true);
- addToSelection(elems, showGrips);
- };
- // TODO: could use slice here to make this faster?
- // TODO: should the 'selected' handler
- // Function: removeFromSelection
- // Removes elements from the selection.
- //
- // Parameters:
- // elemsToRemove - an array of elements to remove from selection
- var removeFromSelection = this.removeFromSelection = function(elemsToRemove) {
- if (selectedElements[0] == null) { return; }
- if (elemsToRemove.length == 0) { return; }
- // find every element and remove it from our array copy
- var i,
- j = 0,
- newSelectedItems = new Array(selectedElements.length),
- len = selectedElements.length;
- for (i = 0; i < len; ++i) {
- var elem = selectedElements[i];
- if (elem) {
- // keep the item
- if (elemsToRemove.indexOf(elem) == -1) {
- newSelectedItems[j] = elem;
- j++;
- } else { // remove the item and its selector
- selectorManager.releaseSelector(elem);
- }
- }
- }
- // the copy becomes the master now
- selectedElements = newSelectedItems;
- };
- // Function: selectAllInCurrentLayer
- // Clears the selection, then adds all elements in the current layer to the selection.
- this.selectAllInCurrentLayer = function() {
- var current_layer = getCurrentDrawing().getCurrentLayer();
- if (current_layer) {
- current_mode = "select";
- selectOnly($(current_group || current_layer).children());
- }
- };
- // Function: getMouseTarget
- // Gets the desired element from a mouse event
- //
- // Parameters:
- // evt - Event object from the mouse event
- //
- // Returns:
- // DOM element we want
- var getMouseTarget = this.getMouseTarget = function(evt) {
- if (evt == null) {
- return null;
- }
- var mouse_target = evt.target;
-
- // if it was a <use>, Opera and WebKit return the SVGElementInstance
- if (mouse_target.correspondingUseElement) {mouse_target = mouse_target.correspondingUseElement;}
-
- // for foreign content, go up until we find the foreignObject
- // WebKit browsers set the mouse target to the svgcanvas div
- if ([NS.MATH, NS.HTML].indexOf(mouse_target.namespaceURI) >= 0 &&
- mouse_target.id != "svgcanvas")
- {
- while (mouse_target.nodeName != "foreignObject") {
- mouse_target = mouse_target.parentNode;
- if (!mouse_target) {return svgroot;}
- }
- }
-
- // Get the desired mouse_target with jQuery selector-fu
- // If it's root-like, select the root
- var current_layer = getCurrentDrawing().getCurrentLayer();
- if ([svgroot, container, svgcontent, current_layer].indexOf(mouse_target) >= 0) {
- return svgroot;
- }
-
- var $target = $(mouse_target);
- // If it's a selection grip, return the grip parent
- if ($target.closest('#selectorParentGroup').length) {
- // While we could instead have just returned mouse_target,
- // this makes it easier to indentify as being a selector grip
- return selectorManager.selectorParentGroup;
- }
- while (mouse_target.parentNode !== (current_group || current_layer)) {
- mouse_target = mouse_target.parentNode;
- }
-
- //
- // // go up until we hit a child of a layer
- // while (mouse_target.parentNode.parentNode.tagName == 'g') {
- // mouse_target = mouse_target.parentNode;
- // }
- // Webkit bubbles the mouse event all the way up to the div, so we
- // set the mouse_target to the svgroot like the other browsers
- // if (mouse_target.nodeName.toLowerCase() == "div") {
- // mouse_target = svgroot;
- // }
-
- return mouse_target;
- };
- // Mouse events
- (function() {
- var d_attr = null,
- start_x = null,
- start_y = null,
- r_start_x = null,
- r_start_y = null,
- init_bbox = {},
- freehand = {
- minx: null,
- miny: null,
- maxx: null,
- maxy: null
- },
- sumDistance = 0,
- controllPoint2 = {x:0, y:0},
- controllPoint1 = {x:0, y:0},
- start = {x:0, y:0},
- end = {x:0, y:0},
- parameter,
- nextParameter,
- bSpline = {x:0, y:0},
- nextPos = {x:0, y:0},
- THRESHOLD_DIST = 0.8,
- STEP_COUNT = 10;
- var getBsplinePoint = function(t) {
- var spline = {x:0, y:0},
- p0 = controllPoint2,
- p1 = controllPoint1,
- p2 = start,
- p3 = end,
- S = 1.0 / 6.0,
- t2 = t * t,
- t3 = t2 * t;
- var m = [
- [-1, 3, -3, 1],
- [3, -6, 3, 0],
- [-3, 0, 3, 0],
- [1, 4, 1, 0]
- ];
- spline.x = S * (
- (p0.x * m[0][0] + p1.x * m[0][1] + p2.x * m[0][2] + p3.x * m[0][3] ) * t3 +
- (p0.x * m[1][0] + p1.x * m[1][1] + p2.x * m[1][2] + p3.x * m[1][3] ) * t2 +
- (p0.x * m[2][0] + p1.x * m[2][1] + p2.x * m[2][2] + p3.x * m[2][3] ) * t +
- (p0.x * m[3][0] + p1.x * m[3][1] + p2.x * m[3][2] + p3.x * m[3][3] )
- );
- spline.y = S * (
- (p0.y * m[0][0] + p1.y * m[0][1] + p2.y * m[0][2] + p3.y * m[0][3] ) * t3 +
- (p0.y * m[1][0] + p1.y * m[1][1] + p2.y * m[1][2] + p3.y * m[1][3] ) * t2 +
- (p0.y * m[2][0] + p1.y * m[2][1] + p2.y * m[2][2] + p3.y * m[2][3] ) * t +
- (p0.y * m[3][0] + p1.y * m[3][1] + p2.y * m[3][2] + p3.y * m[3][3] )
- );
- return {
- x:spline.x,
- y:spline.y
- };
- };
- // - when we are in a create mode, the element is added to the canvas
- // but the action is not recorded until mousing up
- // - when we are in select mode, select the element, remember the position
- // and do nothing else
- var mouseDown = function(evt) {
- if (canvas.spaceKey || evt.button === 1) {return;}
- var right_click = evt.button === 2;
-
- if (evt.altKey) { // duplicate when dragging
- svgCanvas.cloneSelectedElements(0, 0);
- }
-
- root_sctm = $('#svgcontent g')[0].getScreenCTM().inverse();
-
- var pt = svgedit.math.transformPoint( evt.pageX, evt.pageY, root_sctm ),
- mouse_x = pt.x * current_zoom,
- mouse_y = pt.y * current_zoom;
-
- evt.preventDefault();
- if (right_click) {
- current_mode = "select";
- lastClickPoint = pt;
- }
-
- // This would seem to be unnecessary...
- // if (['select', 'resize'].indexOf(current_mode) == -1) {
- // setGradient();
- // }
-
- var x = mouse_x / current_zoom,
- y = mouse_y / current_zoom,
- mouse_target = getMouseTarget(evt);
-
- if (mouse_target.tagName === 'a' && mouse_target.childNodes.length === 1) {
- mouse_target = mouse_target.firstChild;
- }
-
- // real_x/y ignores grid-snap value
- var real_x = x;
- r_start_x = start_x = x;
- var real_y = y;
- r_start_y = start_y = y;
- if (curConfig.gridSnapping){
- x = svgedit.utilities.snapToGrid(x);
- y = svgedit.utilities.snapToGrid(y);
- start_x = svgedit.utilities.snapToGrid(start_x);
- start_y = svgedit.utilities.snapToGrid(start_y);
- }
- // if it is a selector grip, then it must be a single element selected,
- // set the mouse_target to that and update the mode to rotate/resize
-
- if (mouse_target == selectorManager.selectorParentGroup && selectedElements[0] != null) {
- var grip = evt.target;
- var griptype = elData(grip, "type");
- // rotating
- if (griptype == "rotate") {
- current_mode = "rotate";
- }
- // resizing
- else if (griptype == "resize") {
- current_mode = "resize";
- current_resize_mode = elData(grip, "dir");
- }
- mouse_target = selectedElements[0];
- }
-
- startTransform = mouse_target.getAttribute("transform");
- var i, stroke_w,
- tlist = svgedit.transformlist.getTransformList(mouse_target);
- switch (current_mode) {
- case "select":
- started = true;
- current_resize_mode = "none";
- if (right_click) {started = false;}
-
- if (mouse_target != svgroot) {
- // if this element is not yet selected, clear selection and select it
- if (selectedElements.indexOf(mouse_target) == -1) {
- // only clear selection if shift is not pressed (otherwise, add
- // element to selection)
- if (!evt.shiftKey) {
- // No need to do the call here as it will be done on addToSelection
- clearSelection(true);
- }
- addToSelection([mouse_target]);
- justSelected = mouse_target;
- pathActions.clear();
- }
- // else if it's a path, go into pathedit mode in mouseup
-
- if (!right_click) {
- // insert a dummy transform so if the element(s) are moved it will have
- // a transform to use for its translate
- for (i = 0; i < selectedElements.length; ++i) {
- if (selectedElements[i] == null) {continue;}
- var slist = svgedit.transformlist.getTransformList(selectedElements[i]);
- if (slist.numberOfItems) {
- slist.insertItemBefore(svgroot.createSVGTransform(), 0);
- } else {
- slist.appendItem(svgroot.createSVGTransform());
- }
- }
- }
- } else if (!right_click){
- clearSelection();
- current_mode = "multiselect";
- if (rubberBox == null) {
- rubberBox = selectorManager.getRubberBandBox();
- }
- r_start_x *= current_zoom;
- r_start_y *= current_zoom;
- // console.log('p',[evt.pageX, evt.pageY]);
- // console.log('c',[evt.clientX, evt.clientY]);
- // console.log('o',[evt.offsetX, evt.offsetY]);
- // console.log('s',[start_x, start_y]);
-
- svgedit.utilities.assignAttributes(rubberBox, {
- 'x': r_start_x,
- 'y': r_start_y,
- 'width': 0,
- 'height': 0,
- 'display': 'inline'
- }, 100);
- }
- break;
- case "zoom":
- started = true;
- if (rubberBox == null) {
- rubberBox = selectorManager.getRubberBandBox();
- }
- svgedit.utilities.assignAttributes(rubberBox, {
- 'x': real_x * current_zoom,
- 'y': real_x * current_zoom,
- 'width': 0,
- 'height': 0,
- 'display': 'inline'
- }, 100);
- break;
- case "resize":
- started = true;
- start_x = x;
- start_y = y;
-
- // Getting the BBox from the selection box, since we know we
- // want to orient around it
- init_bbox = svgedit.utilities.getBBox($('#selectedBox0')[0]);
- var bb = {};
- $.each(init_bbox, function(key, val) {
- bb[key] = val/current_zoom;
- });
- init_bbox = bb;
-
- // append three dummy transforms to the tlist so that
- // we can translate,scale,translate in mousemove
- var pos = svgedit.utilities.getRotationAngle(mouse_target) ? 1 : 0;
-
- if (svgedit.math.hasMatrixTransform(tlist)) {
- tlist.insertItemBefore(svgroot.createSVGTransform(), pos);
- tlist.insertItemBefore(svgroot.createSVGTransform(), pos);
- tlist.insertItemBefore(svgroot.createSVGTransform(), pos);
- } else {
- tlist.appendItem(svgroot.createSVGTransform());
- tlist.appendItem(svgroot.createSVGTransform());
- tlist.appendItem(svgroot.createSVGTransform());
-
- if (svgedit.browser.supportsNonScalingStroke()) {
- // Handle crash for newer Chrome and Safari 6 (Mobile and Desktop):
- // https://code.google.com/p/svg-edit/issues/detail?id=904
- // Chromium issue: https://code.google.com/p/chromium/issues/detail?id=114625
- // TODO: Remove this workaround once vendor fixes the issue
- var isWebkit = svgedit.browser.isWebkit();
- if (isWebkit) {
- var delayedStroke = function(ele) {
- var _stroke = ele.getAttributeNS(null, 'stroke');
- ele.removeAttributeNS(null, 'stroke');
- //Re-apply stroke after delay. Anything higher than 1 seems to cause flicker
- setTimeout(function() { ele.setAttributeNS(null, 'stroke', _stroke); }, 0);
- };
- }
- mouse_target.style.vectorEffect = 'non-scaling-stroke';
- if (isWebkit) {delayedStroke(mouse_target);}
- var all = mouse_target.getElementsByTagName('*'),
- len = all.length;
- for (i = 0; i < len; i++) {
- all[i].style.vectorEffect = 'non-scaling-stroke';
- if (isWebkit) {delayedStroke(all[i]);}
- }
- }
- }
- break;
- case "fhellipse":
- case "fhrect":
- case "fhpath":
- start.x = real_x;
- start.y = real_y;
- started = true;
- d_attr = real_x + "," + real_y + " ";
- stroke_w = cur_shape.stroke_width == 0 ? 1 : cur_shape.stroke_width;
- addSvgElementFromJson({
- "element": "polyline",
- "curStyles": true,
- "attr": {
- "points": d_attr,
- "id": getNextId(),
- "fill": "none",
- "opacity": cur_shape.opacity / 2,
- "stroke-linecap": "round",
- "style": "pointer-events:none"
- }
- });
- freehand.minx = real_x;
- freehand.maxx = real_x;
- freehand.miny = real_y;
- freehand.maxy = real_y;
- break;
- case "image":
- started = true;
- var newImage = addSvgElementFromJson({
- "element": "image",
- "attr": {
- "x": x,
- "y": y,
- "width": 0,
- "height": 0,
- "id": getNextId(),
- "opacity": cur_shape.opacity / 2,
- "style": "pointer-events:inherit"
- }
- });
- setHref(newImage, last_good_img_url);
- preventClickDefault(newImage);
- break;
- case "square":
- // FIXME: once we create the rect, we lose information that this was a square
- // (for resizing purposes this could be important)
- case "rect":
- started = true;
- start_x = x;
- start_y = y;
- addSvgElementFromJson({
- "element": "rect",
- "curStyles": true,
- "attr": {
- "x": x,
- "y": y,
- "width": 0,
- "height": 0,
- "id": getNextId(),
- "opacity": cur_shape.opacity / 2
- }
- });
- break;
- case "line":
- started = true;
- stroke_w = cur_shape.stroke_width == 0 ? 1 : cur_shape.stroke_width;
- addSvgElementFromJson({
- "element": "line",
- "curStyles": true,
- "attr": {
- "x1": x,
- "y1": y,
- "x2": x,
- "y2": y,
- "id": getNextId(),
- "stroke": cur_shape.stroke,
- "stroke-width": stroke_w,
- "stroke-dasharray": cur_shape.stroke_dasharray,
- "stroke-linejoin": cur_shape.stroke_linejoin,
- "stroke-linecap": cur_shape.stroke_linecap,
- "stroke-opacity": cur_shape.stroke_opacity,
- "fill": "none",
- "opacity": cur_shape.opacity / 2,
- "style": "pointer-events:none"
- }
- });
- break;
- case "circle":
- started = true;
- addSvgElementFromJson({
- "element": "circle",
- "curStyles": true,
- "attr": {
- "cx": x,
- "cy": y,
- "r": 0,
- "id": getNextId(),
- "opacity": cur_shape.opacity / 2
- }
- });
- break;
- case "ellipse":
- started = true;
- addSvgElementFromJson({
- "element": "ellipse",
- "curStyles": true,
- "attr": {
- "cx": x,
- "cy": y,
- "rx": 0,
- "ry": 0,
- "id": getNextId(),
- "opacity": cur_shape.opacity / 2
- }
- });
- break;
- case "text":
- started = true;
- var newText = addSvgElementFromJson({
- "element": "text",
- "curStyles": true,
- "attr": {
- "x": x,
- "y": y,
- "id": getNextId(),
- "fill": cur_text.fill,
- "stroke-width": cur_text.stroke_width,
- "font-size": cur_text.font_size,
- "font-family": cur_text.font_family,
- "text-anchor": "middle",
- "xml:space": "preserve",
- "opacity": cur_shape.opacity
- }
- });
- // newText.textContent = "text";
- break;
- case "path":
- // Fall through
- case "pathedit":
- start_x *= current_zoom;
- start_y *= current_zoom;
- pathActions.mouseDown(evt, mouse_target, start_x, start_y);
- started = true;
- break;
- case "textedit":
- start_x *= current_zoom;
- start_y *= current_zoom;
- textActions.mouseDown(evt, mouse_target, start_x, start_y);
- started = true;
- break;
- case "rotate":
- started = true;
- // we are starting an undoable change (a drag-rotation)
- canvas.undoMgr.beginUndoableChange("transform", selectedElements);
- break;
- default:
- // This could occur in an extension
- break;
- }
-
- var ext_result = runExtensions("mouseDown", {
- event: evt,
- start_x: start_x,
- start_y: start_y,
- selectedElements: selectedElements
- }, true);
-
- $.each(ext_result, function(i, r) {
- if (r && r.started) {
- started = true;
- }
- });
- };
-
- // in this function we do not record any state changes yet (but we do update
- // any elements that are still being created, moved or resized on the canvas)
- var mouseMove = function(evt) {
- if (!started) {return;}
- if (evt.button === 1 || canvas.spaceKey) {return;}
- var i, xya, c, cx, cy, dx, dy, len, angle, box,
- selected = selectedElements[0],
- pt = svgedit.math.transformPoint( evt.pageX, evt.pageY, root_sctm ),
- mouse_x = pt.x * current_zoom,
- mouse_y = pt.y * current_zoom,
- shape = svgedit.utilities.getElem(getId());
- var real_x = mouse_x / current_zoom;
- x = real_x;
- var real_y = mouse_y / current_zoom;
- y = real_y;
-
- if (curConfig.gridSnapping){
- x = svgedit.utilities.snapToGrid(x);
- y = svgedit.utilities.snapToGrid(y);
- }
- evt.preventDefault();
- var tlist;
- switch (current_mode) {
- case "select":
- // we temporarily use a translate on the element(s) being dragged
- // this transform is removed upon mousing up and the element is
- // relocated to the new location
- if (selectedElements[0] !== null) {
- dx = x - start_x;
- dy = y - start_y;
-
- if (curConfig.gridSnapping){
- dx = svgedit.utilities.snapToGrid(dx);
- dy = svgedit.utilities.snapToGrid(dy);
- }
- if (evt.shiftKey) {
- xya = svgedit.math.snapToAngle(start_x, start_y, x, y);
- x = xya.x;
- y = xya.y;
- }
- if (dx != 0 || dy != 0) {
- len = selectedElements.length;
- for (i = 0; i < len; ++i) {
- selected = selectedElements[i];
- if (selected == null) {break;}
- // if (i==0) {
- // var box = svgedit.utilities.getBBox(selected);
- // selectedBBoxes[i].x = box.x + dx;
- // selectedBBoxes[i].y = box.y + dy;
- // }
- // update the dummy transform in our transform list
- // to be a translate
- var xform = svgroot.createSVGTransform();
- tlist = svgedit.transformlist.getTransformList(selected);
- // Note that if Webkit and there's no ID for this
- // element, the dummy transform may have gotten lost.
- // This results in unexpected behaviour
-
- xform.setTranslate(dx, dy);
- if (tlist.numberOfItems) {
- tlist.replaceItem(xform, 0);
- } else {
- tlist.appendItem(xform);
- }
-
- // update our internal bbox that we're tracking while dragging
- selectorManager.requestSelector(selected).resize();
- }
-
- call("transition", selectedElements);
- }
- }
- break;
- case "multiselect":
- real_x *= current_zoom;
- real_y *= current_zoom;
- svgedit.utilities.assignAttributes(rubberBox, {
- 'x': Math.min(r_start_x, real_x),
- 'y': Math.min(r_start_y, real_y),
- 'width': Math.abs(real_x - r_start_x),
- 'height': Math.abs(real_y - r_start_y)
- }, 100);
- // for each selected:
- // - if newList contains selected, do nothing
- // - if newList doesn't contain selected, remove it from selected
- // - for any newList that was not in selectedElements, add it to selected
- var elemsToRemove = [], elemsToAdd = [],
- newList = getIntersectionList();
- len = selectedElements.length;
-
- for (i = 0; i < len; ++i) {
- var ind = newList.indexOf(selectedElements[i]);
- if (ind == -1) {
- elemsToRemove.push(selectedElements[i]);
- } else {
- newList[ind] = null;
- }
- }
-
- len = newList.length;
- for (i = 0; i < len; ++i) {
- if (newList[i]) {elemsToAdd.push(newList[i]);}
- }
-
- if (elemsToRemove.length > 0) {
- canvas.removeFromSelection(elemsToRemove);
- }
-
- if (elemsToAdd.length > 0) {
- addToSelection(elemsToAdd);
- }
-
- break;
- case "resize":
- // we track the resize bounding box and translate/scale the selected element
- // while the mouse is down, when mouse goes up, we use this to recalculate
- // the shape's coordinates
- tlist = svgedit.transformlist.getTransformList(selected);
- var hasMatrix = svgedit.math.hasMatrixTransform(tlist);
- box = hasMatrix ? init_bbox : svgedit.utilities.getBBox(selected);
- var left = box.x, top = box.y, width = box.width,
- height = box.height;
- dx = (x-start_x);
- dy = (y-start_y);
-
- if (curConfig.gridSnapping) {
- dx = svgedit.utilities.snapToGrid(dx);
- dy = svgedit.utilities.snapToGrid(dy);
- height = svgedit.utilities.snapToGrid(height);
- width = svgedit.utilities.snapToGrid(width);
- }
- // if rotated, adjust the dx,dy values
- angle = svgedit.utilities.getRotationAngle(selected);
- if (angle) {
- var r = Math.sqrt( dx*dx + dy*dy ),
- theta = Math.atan2(dy, dx) - angle * Math.PI / 180.0;
- dx = r * Math.cos(theta);
- dy = r * Math.sin(theta);
- }
- // if not stretching in y direction, set dy to 0
- // if not stretching in x direction, set dx to 0
- if (current_resize_mode.indexOf("n")==-1 && current_resize_mode.indexOf("s")==-1) {
- dy = 0;
- }
- if (current_resize_mode.indexOf("e")==-1 && current_resize_mode.indexOf("w")==-1) {
- dx = 0;
- }
-
- var ts = null,
- tx = 0, ty = 0,
- sy = height ? (height+dy)/height : 1,
- sx = width ? (width+dx)/width : 1;
- // if we are dragging on the north side, then adjust the scale factor and ty
- if (current_resize_mode.indexOf("n") >= 0) {
- sy = height ? (height-dy)/height : 1;
- ty = height;
- }
-
- // if we dragging on the east side, then adjust the scale factor and tx
- if (current_resize_mode.indexOf("w") >= 0) {
- sx = width ? (width-dx)/width : 1;
- tx = width;
- }
-
- // update the transform list with translate,scale,translate
- var translateOrigin = svgroot.createSVGTransform(),
- scale = svgroot.createSVGTransform(),
- translateBack = svgroot.createSVGTransform();
- if (curConfig.gridSnapping) {
- left = svgedit.utilities.snapToGrid(left);
- tx = svgedit.utilities.snapToGrid(tx);
- top = svgedit.utilities.snapToGrid(top);
- ty = svgedit.utilities.snapToGrid(ty);
- }
- translateOrigin.setTranslate(-(left+tx), -(top+ty));
- if (evt.shiftKey) {
- if (sx == 1) {sx = sy;}
- else {sy = sx;}
- }
- scale.setScale(sx, sy);
-
- translateBack.setTranslate(left+tx, top+ty);
- if (hasMatrix) {
- var diff = angle ? 1 : 0;
- tlist.replaceItem(translateOrigin, 2+diff);
- tlist.replaceItem(scale, 1+diff);
- tlist.replaceItem(translateBack, Number(diff));
- } else {
- var N = tlist.numberOfItems;
- tlist.replaceItem(translateBack, N-3);
- tlist.replaceItem(scale, N-2);
- tlist.replaceItem(translateOrigin, N-1);
- }
- selectorManager.requestSelector(selected).resize();
-
- call("transition", selectedElements);
-
- break;
- case "zoom":
- real_x *= current_zoom;
- real_y *= current_zoom;
- svgedit.utilities.assignAttributes(rubberBox, {
- 'x': Math.min(r_start_x*current_zoom, real_x),
- 'y': Math.min(r_start_y*current_zoom, real_y),
- 'width': Math.abs(real_x - r_start_x*current_zoom),
- 'height': Math.abs(real_y - r_start_y*current_zoom)
- }, 100);
- break;
- case "text":
- svgedit.utilities.assignAttributes(shape,{
- 'x': x,
- 'y': y
- }, 1000);
- break;
- case "line":
- // Opera has a problem with suspendRedraw() apparently
- var handle = null;
- if (!window.opera) {svgroot.suspendRedraw(1000);}
- if (curConfig.gridSnapping) {
- x = svgedit.utilities.snapToGrid(x);
- y = svgedit.utilities.snapToGrid(y);
- }
- var x2 = x;
- var y2 = y;
- if (evt.shiftKey) {
- xya = svgedit.math.snapToAngle(start_x, start_y, x2, y2);
- x2 = xya.x;
- y2 = xya.y;
- }
-
- shape.setAttributeNS(null, "x2", x2);
- shape.setAttributeNS(null, "y2", y2);
- if (!window.opera) {svgroot.unsuspendRedraw(handle);}
- break;
- case "foreignObject":
- // fall through
- case "square":
- // fall through
- case "rect":
- // fall through
- case "image":
- var square = (current_mode == 'square') || evt.shiftKey,
- w = Math.abs(x - start_x),
- h = Math.abs(y - start_y),
- new_x, new_y;
- if (square) {
- w = h = Math.max(w, h);
- new_x = start_x < x ? start_x : start_x - w;
- new_y = start_y < y ? start_y : start_y - h;
- } else {
- new_x = Math.min(start_x, x);
- new_y = Math.min(start_y, y);
- }
-
- if (curConfig.gridSnapping) {
- w = svgedit.utilities.snapToGrid(w);
- h = svgedit.utilities.snapToGrid(h);
- new_x = svgedit.utilities.snapToGrid(new_x);
- new_y = svgedit.utilities.snapToGrid(new_y);
- }
- svgedit.utilities.assignAttributes(shape,{
- 'width': w,
- 'height': h,
- 'x': new_x,
- 'y': new_y
- },1000);
-
- break;
- case "circle":
- c = $(shape).attr(["cx", "cy"]);
- cx = c.cx;
- cy = c.cy;
- var rad = Math.sqrt( (x-cx)*(x-cx) + (y-cy)*(y-cy) );
- if (curConfig.gridSnapping) {
- rad = svgedit.utilities.snapToGrid(rad);
- }
- shape.setAttributeNS(null, "r", rad);
- break;
- case "ellipse":
- c = $(shape).attr(["cx", "cy"]);
- cx = c.cx;
- cy = c.cy;
- // Opera has a problem with suspendRedraw() apparently
- handle = null;
- if (!window.opera) {svgroot.suspendRedraw(1000);}
- if (curConfig.gridSnapping) {
- x = svgedit.utilities.snapToGrid(x);
- cx = svgedit.utilities.snapToGrid(cx);
- y = svgedit.utilities.snapToGrid(y);
- cy = svgedit.utilities.snapToGrid(cy);
- }
- shape.setAttributeNS(null, "rx", Math.abs(x - cx) );
- var ry = Math.abs(evt.shiftKey?(x - cx):(y - cy));
- shape.setAttributeNS(null, "ry", ry );
- if (!window.opera) {svgroot.unsuspendRedraw(handle);}
- break;
- case "fhellipse":
- case "fhrect":
- freehand.minx = Math.min(real_x, freehand.minx);
- freehand.maxx = Math.max(real_x, freehand.maxx);
- freehand.miny = Math.min(real_y, freehand.miny);
- freehand.maxy = Math.max(real_y, freehand.maxy);
- // break; missing on purpose
- case "fhpath":
- // d_attr += + real_x + "," + real_y + " ";
- // shape.setAttributeNS(null, "points", d_attr);
- end.x = real_x; end.y = real_y;
- if (controllPoint2.x && controllPoint2.y) {
- for (i = 0; i < STEP_COUNT - 1; i++) {
- parameter = i / STEP_COUNT;
- nextParameter = (i + 1) / STEP_COUNT;
- bSpline = getBsplinePoint(nextParameter);
- nextPos = bSpline;
- bSpline = getBsplinePoint(parameter);
- sumDistance += Math.sqrt((nextPos.x - bSpline.x) * (nextPos.x - bSpline.x) + (nextPos.y - bSpline.y) * (nextPos.y - bSpline.y));
- if (sumDistance > THRESHOLD_DIST) {
- d_attr += + bSpline.x + "," + bSpline.y + " ";
- shape.setAttributeNS(null, "points", d_attr);
- sumDistance -= THRESHOLD_DIST;
- }
- }
- }
- controllPoint2 = {x:controllPoint1.x, y:controllPoint1.y};
- controllPoint1 = {x:start.x, y:start.y};
- start = {x:end.x, y:end.y};
- break;
- // update path stretch line coordinates
- case "path":
- // fall through
- case "pathedit":
- x *= current_zoom;
- y *= current_zoom;
-
- if (curConfig.gridSnapping) {
- x = svgedit.utilities.snapToGrid(x);
- y = svgedit.utilities.snapToGrid(y);
- start_x = svgedit.utilities.snapToGrid(start_x);
- start_y = svgedit.utilities.snapToGrid(start_y);
- }
- if (evt.shiftKey) {
- var path = svgedit.path.path;
- var x1, y1;
- if (path) {
- x1 = path.dragging?path.dragging[0]:start_x;
- y1 = path.dragging?path.dragging[1]:start_y;
- } else {
- x1 = start_x;
- y1 = start_y;
- }
- xya = svgedit.math.snapToAngle(x1, y1, x, y);
- x = xya.x;
- y = xya.y;
- }
-
- if (rubberBox && rubberBox.getAttribute('display') !== 'none') {
- real_x *= current_zoom;
- real_y *= current_zoom;
- svgedit.utilities.assignAttributes(rubberBox, {
- 'x': Math.min(r_start_x*current_zoom, real_x),
- 'y': Math.min(r_start_y*current_zoom, real_y),
- 'width': Math.abs(real_x - r_start_x*current_zoom),
- 'height': Math.abs(real_y - r_start_y*current_zoom)
- },100);
- }
- pathActions.mouseMove(x, y);
-
- break;
- case "textedit":
- x *= current_zoom;
- y *= current_zoom;
- // if (rubberBox && rubberBox.getAttribute('display') != 'none') {
- // svgedit.utilities.assignAttributes(rubberBox, {
- // 'x': Math.min(start_x,x),
- // 'y': Math.min(start_y,y),
- // 'width': Math.abs(x-start_x),
- // 'height': Math.abs(y-start_y)
- // },100);
- // }
-
- textActions.mouseMove(mouse_x, mouse_y);
-
- break;
- case "rotate":
- box = svgedit.utilities.getBBox(selected);
- cx = box.x + box.width/2;
- cy = box.y + box.height/2;
- var m = svgedit.math.getMatrix(selected),
- center = svgedit.math.transformPoint(cx, cy, m);
- cx = center.x;
- cy = center.y;
- angle = ((Math.atan2(cy-y, cx-x) * (180/Math.PI))-90) % 360;
- if (curConfig.gridSnapping) {
- angle = svgedit.utilities.snapToGrid(angle);
- }
- if (evt.shiftKey) { // restrict rotations to nice angles (WRS)
- var snap = 45;
- angle= Math.round(angle/snap)*snap;
- }
- canvas.setRotationAngle(angle<-180?(360+angle):angle, true);
- call("transition", selectedElements);
- break;
- default:
- break;
- }
-
- runExtensions("mouseMove", {
- event: evt,
- mouse_x: mouse_x,
- mouse_y: mouse_y,
- selected: selected
- });
- }; // mouseMove()
-
- // - in create mode, the element's opacity is set properly, we create an InsertElementCommand
- // and store it on the Undo stack
- // - in move/resize mode, the element's attributes which were affected by the move/resize are
- // identified, a ChangeElementCommand is created and stored on the stack for those attrs
- // this is done in when we recalculate the selected dimensions()
- var mouseUp = function(evt) {
- if (evt.button === 2) {return;}
- var tempJustSelected = justSelected;
- justSelected = null;
- if (!started) {return;}
- var pt = svgedit.math.transformPoint(evt.pageX, evt.pageY, root_sctm),
- mouse_x = pt.x * current_zoom,
- mouse_y = pt.y * current_zoom,
- x = mouse_x / current_zoom,
- y = mouse_y / current_zoom,
- element = svgedit.utilities.getElem(getId()),
- keep = false;
- var real_x = x;
- var real_y = y;
- // TODO: Make true when in multi-unit mode
- var useUnit = false; // (curConfig.baseUnit !== 'px');
- started = false;
- var attrs, t;
- switch (current_mode) {
- // intentionally fall-through to select here
- case "resize":
- case "multiselect":
- if (rubberBox != null) {
- rubberBox.setAttribute("display", "none");
- curBBoxes = [];
- }
- current_mode = "select";
- case "select":
- if (selectedElements[0] != null) {
- // if we only have one selected element
- if (selectedElements[1] == null) {
- // set our current stroke/fill properties to the element's
- var selected = selectedElements[0];
- switch ( selected.tagName ) {
- case "g":
- case "use":
- case "image":
- case "foreignObject":
- break;
- default:
- cur_properties.fill = selected.getAttribute("fill");
- cur_properties.fill_opacity = selected.getAttribute("fill-opacity");
- cur_properties.stroke = selected.getAttribute("stroke");
- cur_properties.stroke_opacity = selected.getAttribute("stroke-opacity");
- cur_properties.stroke_width = selected.getAttribute("stroke-width");
- cur_properties.stroke_dasharray = selected.getAttribute("stroke-dasharray");
- cur_properties.stroke_linejoin = selected.getAttribute("stroke-linejoin");
- cur_properties.stroke_linecap = selected.getAttribute("stroke-linecap");
- }
- if (selected.tagName == "text") {
- cur_text.font_size = selected.getAttribute("font-size");
- cur_text.font_family = selected.getAttribute("font-family");
- }
- selectorManager.requestSelector(selected).showGrips(true);
-
- // This shouldn't be necessary as it was done on mouseDown...
- // call("selected", [selected]);
- }
- // always recalculate dimensions to strip off stray identity transforms
- recalculateAllSelectedDimensions();
- // if it was being dragged/resized
- if (real_x != r_start_x || real_y != r_start_y) {
- var i, len = selectedElements.length;
- for (i = 0; i < len; ++i) {
- if (selectedElements[i] == null) {break;}
- if (!selectedElements[i].firstChild) {
- // Not needed for groups (incorrectly resizes elems), possibly not needed at all?
- selectorManager.requestSelector(selectedElements[i]).resize();
- }
- }
- }
- // no change in position/size, so maybe we should move to pathedit
- else {
- t = evt.target;
- if (selectedElements[0].nodeName === "path" && selectedElements[1] == null) {
- pathActions.select(selectedElements[0]);
- } // if it was a path
- // else, if it was selected and this is a shift-click, remove it from selection
- else if (evt.shiftKey) {
- if (tempJustSelected != t) {
- canvas.removeFromSelection([t]);
- }
- }
- } // no change in mouse position
-
- // Remove non-scaling stroke
- if (svgedit.browser.supportsNonScalingStroke()) {
- var elem = selectedElements[0];
- if (elem) {
- elem.removeAttribute('style');
- svgedit.utilities.walkTree(elem, function(elem) {
- elem.removeAttribute('style');
- });
- }
- }
- }
- return;
- case "zoom":
- if (rubberBox != null) {
- rubberBox.setAttribute("display", "none");
- }
- var factor = evt.shiftKey ? 0.5 : 2;
- call("zoomed", {
- 'x': Math.min(r_start_x, real_x),
- 'y': Math.min(r_start_y, real_y),
- 'width': Math.abs(real_x - r_start_x),
- 'height': Math.abs(real_y - r_start_y),
- 'factor': factor
- });
- return;
- case "fhpath":
- // Check that the path contains at least 2 points; a degenerate one-point path
- // causes problems.
- // Webkit ignores how we set the points attribute with commas and uses space
- // to separate all coordinates, see https://bugs.webkit.org/show_bug.cgi?id=29870
- sumDistance = 0;
- controllPoint2 = {x:0, y:0};
- controllPoint1 = {x:0, y:0};
- start = {x:0, y:0};
- end = {x:0, y:0};
- var coords = element.getAttribute('points');
- var commaIndex = coords.indexOf(',');
- if (commaIndex >= 0) {
- keep = coords.indexOf(',', commaIndex+1) >= 0;
- } else {
- keep = coords.indexOf(' ', coords.indexOf(' ')+1) >= 0;
- }
- if (keep) {
- element = pathActions.smoothPolylineIntoPath(element);
- }
- break;
- case "line":
- attrs = $(element).attr(["x1", "x2", "y1", "y2"]);
- keep = (attrs.x1 != attrs.x2 || attrs.y1 != attrs.y2);
- break;
- case "foreignObject":
- case "square":
- case "rect":
- case "image":
- attrs = $(element).attr(["width", "height"]);
- // Image should be kept regardless of size (use inherit dimensions later)
- keep = (attrs.width != 0 || attrs.height != 0) || current_mode === "image";
- break;
- case "circle":
- keep = (element.getAttribute('r') != 0);
- break;
- case "ellipse":
- attrs = $(element).attr(["rx", "ry"]);
- keep = (attrs.rx != null || attrs.ry != null);
- break;
- case "fhellipse":
- if ((freehand.maxx - freehand.minx) > 0 &&
- (freehand.maxy - freehand.miny) > 0) {
- element = addSvgElementFromJson({
- "element": "ellipse",
- "curStyles": true,
- "attr": {
- "cx": (freehand.minx + freehand.maxx) / 2,
- "cy": (freehand.miny + freehand.maxy) / 2,
- "rx": (freehand.maxx - freehand.minx) / 2,
- "ry": (freehand.maxy - freehand.miny) / 2,
- "id": getId()
- }
- });
- call("changed",[element]);
- keep = true;
- }
- break;
- case "fhrect":
- if ((freehand.maxx - freehand.minx) > 0 &&
- (freehand.maxy - freehand.miny) > 0) {
- element = addSvgElementFromJson({
- "element": "rect",
- "curStyles": true,
- "attr": {
- "x": freehand.minx,
- "y": freehand.miny,
- "width": (freehand.maxx - freehand.minx),
- "height": (freehand.maxy - freehand.miny),
- "id": getId()
- }
- });
- call("changed",[element]);
- keep = true;
- }
- break;
- case "text":
- keep = true;
- selectOnly([element]);
- textActions.start(element);
- break;
- case "path":
- // set element to null here so that it is not removed nor finalized
- element = null;
- // continue to be set to true so that mouseMove happens
- started = true;
-
- var res = pathActions.mouseUp(evt, element, mouse_x, mouse_y);
- element = res.element;
- keep = res.keep;
- break;
- case "pathedit":
- keep = true;
- element = null;
- pathActions.mouseUp(evt);
- break;
- case "textedit":
- keep = false;
- element = null;
- textActions.mouseUp(evt, mouse_x, mouse_y);
- break;
- case "rotate":
- keep = true;
- element = null;
- current_mode = "select";
- var batchCmd = canvas.undoMgr.finishUndoableChange();
- if (!batchCmd.isEmpty()) {
- addCommandToHistory(batchCmd);
- }
- // perform recalculation to weed out any stray identity transforms that might get stuck
- recalculateAllSelectedDimensions();
- call("changed", selectedElements);
- break;
- default:
- // This could occur in an extension
- break;
- }
-
- var ext_result = runExtensions("mouseUp", {
- event: evt,
- mouse_x: mouse_x,
- mouse_y: mouse_y
- }, true);
-
- $.each(ext_result, function(i, r) {
- if (r) {
- keep = r.keep || keep;
- element = r.element;
- started = r.started || started;
- }
- });
-
- if (!keep && element != null) {
- getCurrentDrawing().releaseId(getId());
- element.parentNode.removeChild(element);
- element = null;
-
- t = evt.target;
-
- // if this element is in a group, go up until we reach the top-level group
- // just below the layer groups
- // TODO: once we implement links, we also would have to check for <a> elements
- while (t.parentNode.parentNode.tagName == "g") {
- t = t.parentNode;
- }
- // if we are not in the middle of creating a path, and we've clicked on some shape,
- // then go to Select mode.
- // WebKit returns <div> when the canvas is clicked, Firefox/Opera return <svg>
- if ( (current_mode != "path" || !drawn_path) &&
- t.parentNode.id != "selectorParentGroup" &&
- t.id != "svgcanvas" && t.id != "svgroot")
- {
- // switch into "select" mode if we've clicked on an element
- canvas.setMode("select");
- selectOnly([t], true);
- }
-
- } else if (element != null) {
- canvas.addedNew = true;
-
- if (useUnit) {svgedit.units.convertAttrs(element);}
-
- var ani_dur = 0.2, c_ani;
- if (opac_ani.beginElement && element.getAttribute('opacity') != cur_shape.opacity) {
- c_ani = $(opac_ani).clone().attr({
- to: cur_shape.opacity,
- dur: ani_dur
- }).appendTo(element);
- try {
- // Fails in FF4 on foreignObject
- c_ani[0].beginElement();
- } catch(e){}
- } else {
- ani_dur = 0;
- }
-
- // Ideally this would be done on the endEvent of the animation,
- // but that doesn't seem to be supported in Webkit
- setTimeout(function() {
- if (c_ani) {c_ani.remove();}
- element.setAttribute("opacity", cur_shape.opacity);
- element.setAttribute("style", "pointer-events:inherit");
- cleanupElement(element);
- if (current_mode === "path") {
- pathActions.toEditMode(element);
- } else if (curConfig.selectNew) {
- selectOnly([element], true);
- }
- // we create the insert command that is stored on the stack
- // undo means to call cmd.unapply(), redo means to call cmd.apply()
- addCommandToHistory(new svgedit.history.InsertElementCommand(element));
-
- call("changed",[element]);
- }, ani_dur * 1000);
- }
-
- startTransform = null;
- };
-
- var dblClick = function(evt) {
- var evt_target = evt.target;
- var parent = evt_target.parentNode;
-
- // Do nothing if already in current group
- if (parent === current_group) {return;}
-
- var mouse_target = getMouseTarget(evt);
- var tagName = mouse_target.tagName;
-
- if (tagName === 'text' && current_mode !== 'textedit') {
- var pt = svgedit.math.transformPoint( evt.pageX, evt.pageY, root_sctm );
- textActions.select(mouse_target, pt.x, pt.y);
- }
-
- if ((tagName === "g" || tagName === "a") && svgedit.utilities.getRotationAngle(mouse_target)) {
- // TODO: Allow method of in-group editing without having to do
- // this (similar to editing rotated paths)
-
- // Ungroup and regroup
- pushGroupProperties(mouse_target);
- mouse_target = selectedElements[0];
- clearSelection(true);
- }
- // Reset context
- if (current_group) {
- leaveContext();
- }
-
- if ((parent.tagName !== 'g' && parent.tagName !== 'a') ||
- parent === getCurrentDrawing().getCurrentLayer() ||
- mouse_target === selectorManager.selectorParentGroup)
- {
- // Escape from in-group edit
- return;
- }
- setContext(mouse_target);
- };
- // prevent links from being followed in the canvas
- var handleLinkInCanvas = function(e) {
- e.preventDefault();
- return false;
- };
-
- // Added mouseup to the container here.
- // TODO(codedread): Figure out why after the Closure compiler, the window mouseup is ignored.
- $(container).mousedown(mouseDown).mousemove(mouseMove).click(handleLinkInCanvas).dblclick(dblClick).mouseup(mouseUp);
- // $(window).mouseup(mouseUp);
-
- //TODO(rafaelcastrocouto): User preference for shift key and zoom factor
- $(container).bind("mousewheel DOMMouseScroll", function(e){
- //if (!e.shiftKey) {return;}
- e.preventDefault();
- var evt = e.originalEvent;
- root_sctm = $('#svgcontent g')[0].getScreenCTM().inverse();
- var pt = svgedit.math.transformPoint( evt.pageX, evt.pageY, root_sctm );
- var bbox = {
- 'x': pt.x,
- 'y': pt.y,
- 'width': 0,
- 'height': 0
- };
- var delta = (evt.wheelDelta) ? evt.wheelDelta : (evt.detail) ? -evt.detail : 0;
- if (!delta) {return;}
- bbox.factor = Math.max(3/4, Math.min(4/3, (delta)));
-
- call("zoomed", bbox);
- });
-
- }());
- // Function: preventClickDefault
- // Prevents default browser click behaviour on the given element
- //
- // Parameters:
- // img - The DOM element to prevent the cilck on
- var preventClickDefault = function(img) {
- $(img).click(function(e){e.preventDefault();});
- };
- // Group: Text edit functions
- // Functions relating to editing text elements
- textActions = canvas.textActions = (function() {
- var curtext;
- var textinput;
- var cursor;
- var selblock;
- var blinker;
- var chardata = [];
- var textbb, transbb;
- var matrix;
- var last_x, last_y;
- var allow_dbl;
-
- function setCursor(index) {
- var empty = (textinput.value === "");
- $(textinput).focus();
-
- if (!arguments.length) {
- if (empty) {
- index = 0;
- } else {
- if (textinput.selectionEnd !== textinput.selectionStart) {return;}
- index = textinput.selectionEnd;
- }
- }
-
- var charbb;
- charbb = chardata[index];
- if (!empty) {
- textinput.setSelectionRange(index, index);
- }
- cursor = svgedit.utilities.getElem("text_cursor");
- if (!cursor) {
- cursor = document.createElementNS(NS.SVG, "line");
- svgedit.utilities.assignAttributes(cursor, {
- 'id': "text_cursor",
- 'stroke': "#333",
- 'stroke-width': 1
- });
- cursor = svgedit.utilities.getElem("selectorParentGroup").appendChild(cursor);
- }
-
- if (!blinker) {
- blinker = setInterval(function() {
- var show = (cursor.getAttribute('display') === 'none');
- cursor.setAttribute('display', show?'inline':'none');
- }, 600);
- }
-
- var start_pt = ptToScreen(charbb.x, textbb.y);
- var end_pt = ptToScreen(charbb.x, (textbb.y + textbb.height));
-
- svgedit.utilities.assignAttributes(cursor, {
- x1: start_pt.x,
- y1: start_pt.y,
- x2: end_pt.x,
- y2: end_pt.y,
- visibility: 'visible',
- display: 'inline'
- });
-
- if (selblock) {selblock.setAttribute('d', '');}
- }
-
- function setSelection(start, end, skipInput) {
- if (start === end) {
- setCursor(end);
- return;
- }
-
- if (!skipInput) {
- textinput.setSelectionRange(start, end);
- }
-
- selblock = svgedit.utilities.getElem("text_selectblock");
- if (!selblock) {
- selblock = document.createElementNS(NS.SVG, "path");
- svgedit.utilities.assignAttributes(selblock, {
- 'id': "text_selectblock",
- 'fill': "green",
- 'opacity': 0.5,
- 'style': "pointer-events:none"
- });
- svgedit.utilities.getElem("selectorParentGroup").appendChild(selblock);
- }
- var startbb = chardata[start];
- var endbb = chardata[end];
-
- cursor.setAttribute('visibility', 'hidden');
-
- var tl = ptToScreen(startbb.x, textbb.y),
- tr = ptToScreen(startbb.x + (endbb.x - startbb.x), textbb.y),
- bl = ptToScreen(startbb.x, textbb.y + textbb.height),
- br = ptToScreen(startbb.x + (endbb.x - startbb.x), textbb.y + textbb.height);
-
- var dstr = "M" + tl.x + "," + tl.y
- + " L" + tr.x + "," + tr.y
- + " " + br.x + "," + br.y
- + " " + bl.x + "," + bl.y + "z";
-
- svgedit.utilities.assignAttributes(selblock, {
- d: dstr,
- 'display': 'inline'
- });
- }
-
- function getIndexFromPoint(mouse_x, mouse_y) {
- // Position cursor here
- var pt = svgroot.createSVGPoint();
- pt.x = mouse_x;
- pt.y = mouse_y;
- // No content, so return 0
- if (chardata.length == 1) {return 0;}
- // Determine if cursor should be on left or right of character
- var charpos = curtext.getCharNumAtPosition(pt);
- if (charpos < 0) {
- // Out of text range, look at mouse coords
- charpos = chardata.length - 2;
- if (mouse_x <= chardata[0].x) {
- charpos = 0;
- }
- } else if (charpos >= chardata.length - 2) {
- charpos = chardata.length - 2;
- }
- var charbb = chardata[charpos];
- var mid = charbb.x + (charbb.width/2);
- if (mouse_x > mid) {
- charpos++;
- }
- return charpos;
- }
-
- function setCursorFromPoint(mouse_x, mouse_y) {
- setCursor(getIndexFromPoint(mouse_x, mouse_y));
- }
-
- function setEndSelectionFromPoint(x, y, apply) {
- var i1 = textinput.selectionStart;
- var i2 = getIndexFromPoint(x, y);
-
- var start = Math.min(i1, i2);
- var end = Math.max(i1, i2);
- setSelection(start, end, !apply);
- }
-
- function screenToPt(x_in, y_in) {
- var out = {
- x: x_in,
- y: y_in
- };
-
- out.x /= current_zoom;
- out.y /= current_zoom;
- if (matrix) {
- var pt = svgedit.math.transformPoint(out.x, out.y, matrix.inverse());
- out.x = pt.x;
- out.y = pt.y;
- }
-
- return out;
- }
-
- function ptToScreen(x_in, y_in) {
- var out = {
- x: x_in,
- y: y_in
- };
-
- if (matrix) {
- var pt = svgedit.math.transformPoint(out.x, out.y, matrix);
- out.x = pt.x;
- out.y = pt.y;
- }
-
- out.x *= current_zoom;
- out.y *= current_zoom;
-
- return out;
- }
-
- function hideCursor() {
- if (cursor) {
- cursor.setAttribute('visibility', 'hidden');
- }
- }
-
- function selectAll(evt) {
- setSelection(0, curtext.textContent.length);
- $(this).unbind(evt);
- }
- function selectWord(evt) {
- if (!allow_dbl || !curtext) {return;}
-
- var ept = svgedit.math.transformPoint( evt.pageX, evt.pageY, root_sctm ),
- mouse_x = ept.x * current_zoom,
- mouse_y = ept.y * current_zoom;
- var pt = screenToPt(mouse_x, mouse_y);
-
- var index = getIndexFromPoint(pt.x, pt.y);
- var str = curtext.textContent;
- var first = str.substr(0, index).replace(/[a-z0-9]+$/i, '').length;
- var m = str.substr(index).match(/^[a-z0-9]+/i);
- var last = (m?m[0].length:0) + index;
- setSelection(first, last);
-
- // Set tripleclick
- $(evt.target).click(selectAll);
- setTimeout(function() {
- $(evt.target).unbind('click', selectAll);
- }, 300);
-
- }
- return {
- select: function(target, x, y) {
- curtext = target;
- textActions.toEditMode(x, y);
- },
- start: function(elem) {
- curtext = elem;
- textActions.toEditMode();
- },
- mouseDown: function(evt, mouse_target, start_x, start_y) {
- var pt = screenToPt(start_x, start_y);
-
- textinput.focus();
- setCursorFromPoint(pt.x, pt.y);
- last_x = start_x;
- last_y = start_y;
-
- // TODO: Find way to block native selection
- },
- mouseMove: function(mouse_x, mouse_y) {
- var pt = screenToPt(mouse_x, mouse_y);
- setEndSelectionFromPoint(pt.x, pt.y);
- },
- mouseUp: function(evt, mouse_x, mouse_y) {
- var pt = screenToPt(mouse_x, mouse_y);
-
- setEndSelectionFromPoint(pt.x, pt.y, true);
-
- // TODO: Find a way to make this work: Use transformed BBox instead of evt.target
- // if (last_x === mouse_x && last_y === mouse_y
- // && !svgedit.math.rectsIntersect(transbb, {x: pt.x, y: pt.y, width:0, height:0})) {
- // textActions.toSelectMode(true);
- // }
- if (
- evt.target !== curtext
- && mouse_x < last_x + 2
- && mouse_x > last_x - 2
- && mouse_y < last_y + 2
- && mouse_y > last_y - 2) {
- textActions.toSelectMode(true);
- }
- },
- setCursor: setCursor,
- toEditMode: function(x, y) {
- allow_dbl = false;
- current_mode = "textedit";
- selectorManager.requestSelector(curtext).showGrips(false);
- // Make selector group accept clicks
- var sel = selectorManager.requestSelector(curtext).selectorRect;
-
- textActions.init();
- $(curtext).css('cursor', 'text');
-
- // if (svgedit.browser.supportsEditableText()) {
- // curtext.setAttribute('editable', 'simple');
- // return;
- // }
-
- if (!arguments.length) {
- setCursor();
- } else {
- var pt = screenToPt(x, y);
- setCursorFromPoint(pt.x, pt.y);
- }
-
- setTimeout(function() {
- allow_dbl = true;
- }, 300);
- },
- toSelectMode: function(selectElem) {
- current_mode = "select";
- clearInterval(blinker);
- blinker = null;
- if (selblock) {$(selblock).attr('display', 'none');}
- if (cursor) {$(cursor).attr('visibility', 'hidden');}
- $(curtext).css('cursor', 'move');
-
- if (selectElem) {
- clearSelection();
- $(curtext).css('cursor', 'move');
-
- call("selected", [curtext]);
- addToSelection([curtext], true);
- }
- if (curtext && !curtext.textContent.length) {
- // No content, so delete
- canvas.deleteSelectedElements();
- }
-
- $(textinput).blur();
-
- curtext = false;
-
- // if (svgedit.browser.supportsEditableText()) {
- // curtext.removeAttribute('editable');
- // }
- },
- setInputElem: function(elem) {
- textinput = elem;
- // $(textinput).blur(hideCursor);
- },
- clear: function() {
- if (current_mode == "textedit") {
- textActions.toSelectMode();
- }
- },
- init: function(inputElem) {
- if (!curtext) {return;}
- var i, end;
- // if (svgedit.browser.supportsEditableText()) {
- // curtext.select();
- // return;
- // }
-
- if (!curtext.parentNode) {
- // Result of the ffClone, need to get correct element
- curtext = selectedElements[0];
- selectorManager.requestSelector(curtext).showGrips(false);
- }
-
- var str = curtext.textContent;
- var len = str.length;
-
- var xform = curtext.getAttribute('transform');
- textbb = svgedit.utilities.getBBox(curtext);
-
- matrix = xform ? svgedit.math.getMatrix(curtext) : null;
- chardata = new Array(len);
- textinput.focus();
-
- $(curtext).unbind('dblclick', selectWord).dblclick(selectWord);
-
- if (!len) {
- end = {x: textbb.x + (textbb.width/2), width: 0};
- }
-
- for (i=0; i<len; i++) {
- var start = curtext.getStartPositionOfChar(i);
- end = curtext.getEndPositionOfChar(i);
-
- if (!svgedit.browser.supportsGoodTextCharPos()) {
- var offset = canvas.contentW * current_zoom;
- start.x -= offset;
- end.x -= offset;
-
- start.x /= current_zoom;
- end.x /= current_zoom;
- }
-
- // Get a "bbox" equivalent for each character. Uses the
- // bbox data of the actual text for y, height purposes
-
- // TODO: Decide if y, width and height are actually necessary
- chardata[i] = {
- x: start.x,
- y: textbb.y, // start.y?
- width: end.x - start.x,
- height: textbb.height
- };
- }
-
- // Add a last bbox for cursor at end of text
- chardata.push({
- x: end.x,
- width: 0
- });
- setSelection(textinput.selectionStart, textinput.selectionEnd, true);
- }
- };
- }());
- // TODO: Migrate all of this code into path.js
- // Group: Path edit functions
- // Functions relating to editing path elements
- pathActions = canvas.pathActions = function() {
-
- var subpath = false;
- var current_path;
- var newPoint, firstCtrl;
-
- function resetD(p) {
- p.setAttribute("d", pathActions.convertPath(p));
- }
- // TODO: Move into path.js
- svgedit.path.Path.prototype.endChanges = function(text) {
- if (svgedit.browser.isWebkit()) {resetD(this.elem);}
- var cmd = new svgedit.history.ChangeElementCommand(this.elem, {d: this.last_d}, text);
- addCommandToHistory(cmd);
- call("changed", [this.elem]);
- };
- svgedit.path.Path.prototype.addPtsToSelection = function(indexes) {
- var i, seg;
- if (!$.isArray(indexes)) {indexes = [indexes];}
- for (i = 0; i< indexes.length; i++) {
- var index = indexes[i];
- seg = this.segs[index];
- if (seg.ptgrip) {
- if (this.selected_pts.indexOf(index) == -1 && index >= 0) {
- this.selected_pts.push(index);
- }
- }
- }
- this.selected_pts.sort();
- i = this.selected_pts.length;
- var grips = new Array(i);
- // Loop through points to be selected and highlight each
- while (i--) {
- var pt = this.selected_pts[i];
- seg = this.segs[pt];
- seg.select(true);
- grips[i] = seg.ptgrip;
- }
- // TODO: Correct this:
- pathActions.canDeleteNodes = true;
-
- pathActions.closed_subpath = this.subpathIsClosed(this.selected_pts[0]);
-
- call("selected", grips);
- };
- current_path = null;
- var drawn_path = null,
- hasMoved = false;
-
- // This function converts a polyline (created by the fh_path tool) into
- // a path element and coverts every three line segments into a single bezier
- // curve in an attempt to smooth out the free-hand
- var smoothPolylineIntoPath = function(element) {
- var i, points = element.points;
- var N = points.numberOfItems;
- if (N >= 4) {
- // loop through every 3 points and convert to a cubic bezier curve segment
- //
- // NOTE: this is cheating, it means that every 3 points has the potential to
- // be a corner instead of treating each point in an equal manner. In general,
- // this technique does not look that good.
- //
- // I am open to better ideas!
- //
- // Reading:
- // - http://www.efg2.com/Lab/Graphics/Jean-YvesQueinecBezierCurves.htm
- // - http://www.codeproject.com/KB/graphics/BezierSpline.aspx?msg=2956963
- // - http://www.ian-ko.com/ET_GeoWizards/UserGuide/smooth.htm
- // - http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/bezier-der.html
- var curpos = points.getItem(0), prevCtlPt = null;
- var d = [];
- d.push(["M", curpos.x, ",", curpos.y, " C"].join(""));
- for (i = 1; i <= (N-4); i += 3) {
- var ct1 = points.getItem(i);
- var ct2 = points.getItem(i+1);
- var end = points.getItem(i+2);
-
- // if the previous segment had a control point, we want to smooth out
- // the control points on both sides
- if (prevCtlPt) {
- var newpts = svgedit.path.smoothControlPoints( prevCtlPt, ct1, curpos );
- if (newpts && newpts.length == 2) {
- var prevArr = d[d.length-1].split(',');
- prevArr[2] = newpts[0].x;
- prevArr[3] = newpts[0].y;
- d[d.length-1] = prevArr.join(',');
- ct1 = newpts[1];
- }
- }
-
- d.push([ct1.x, ct1.y, ct2.x, ct2.y, end.x, end.y].join(','));
-
- curpos = end;
- prevCtlPt = ct2;
- }
- // handle remaining line segments
- d.push("L");
- while (i < N) {
- var pt = points.getItem(i);
- d.push([pt.x, pt.y].join(","));
- i++;
- }
- d = d.join(" ");
- // create new path element
- element = addSvgElementFromJson({
- "element": "path",
- "curStyles": true,
- "attr": {
- "id": getId(),
- "d": d,
- "fill": "none"
- }
- });
- // No need to call "changed", as this is already done under mouseUp
- }
- return element;
- };
- return {
- mouseDown: function(evt, mouse_target, start_x, start_y) {
- var id;
- if (current_mode === "path") {
- mouse_x = start_x;
- mouse_y = start_y;
-
- var x = mouse_x/current_zoom,
- y = mouse_y/current_zoom,
- stretchy = svgedit.utilities.getElem("path_stretch_line");
- newPoint = [x, y];
-
- if (curConfig.gridSnapping){
- x = svgedit.utilities.snapToGrid(x);
- y = svgedit.utilities.snapToGrid(y);
- mouse_x = svgedit.utilities.snapToGrid(mouse_x);
- mouse_y = svgedit.utilities.snapToGrid(mouse_y);
- }
- if (!stretchy) {
- stretchy = document.createElementNS(NS.SVG, "path");
- svgedit.utilities.assignAttributes(stretchy, {
- 'id': "path_stretch_line",
- 'stroke': "#22C",
- 'stroke-width': "0.5",
- 'fill': 'none'
- });
- stretchy = svgedit.utilities.getElem("selectorParentGroup").appendChild(stretchy);
- }
- stretchy.setAttribute("display", "inline");
-
- var keep = null;
- var index;
- // if pts array is empty, create path element with M at current point
- if (!drawn_path) {
- d_attr = "M" + x + "," + y + " ";
- drawn_path = addSvgElementFromJson({
- "element": "path",
- "curStyles": true,
- "attr": {
- "d": d_attr,
- "id": getNextId(),
- "opacity": cur_shape.opacity / 2
- }
- });
- // set stretchy line to first point
- stretchy.setAttribute('d', ['M', mouse_x, mouse_y, mouse_x, mouse_y].join(' '));
- index = subpath ? svgedit.path.path.segs.length : 0;
- svgedit.path.addPointGrip(index, mouse_x, mouse_y);
- } else {
- // determine if we clicked on an existing point
- var seglist = drawn_path.pathSegList;
- var i = seglist.numberOfItems;
- var FUZZ = 6/current_zoom;
- var clickOnPoint = false;
- while (i) {
- i --;
- var item = seglist.getItem(i);
- var px = item.x, py = item.y;
- // found a matching point
- if ( x >= (px-FUZZ) && x <= (px+FUZZ) && y >= (py-FUZZ) && y <= (py+FUZZ) ) {
- clickOnPoint = true;
- break;
- }
- }
-
- // get path element that we are in the process of creating
- id = getId();
-
- // Remove previous path object if previously created
- svgedit.path.removePath_(id);
-
- var newpath = svgedit.utilities.getElem(id);
- var newseg;
- var s_seg;
- var len = seglist.numberOfItems;
- // if we clicked on an existing point, then we are done this path, commit it
- // (i, i+1) are the x,y that were clicked on
- if (clickOnPoint) {
- // if clicked on any other point but the first OR
- // the first point was clicked on and there are less than 3 points
- // then leave the path open
- // otherwise, close the path
- if (i <= 1 && len >= 2) {
- // Create end segment
- var abs_x = seglist.getItem(0).x;
- var abs_y = seglist.getItem(0).y;
-
- s_seg = stretchy.pathSegList.getItem(1);
- if (s_seg.pathSegType === 4) {
- newseg = drawn_path.createSVGPathSegLinetoAbs(abs_x, abs_y);
- } else {
- newseg = drawn_path.createSVGPathSegCurvetoCubicAbs(
- abs_x,
- abs_y,
- s_seg.x1 / current_zoom,
- s_seg.y1 / current_zoom,
- abs_x,
- abs_y
- );
- }
-
- var endseg = drawn_path.createSVGPathSegClosePath();
- seglist.appendItem(newseg);
- seglist.appendItem(endseg);
- } else if (len < 3) {
- keep = false;
- return keep;
- }
- $(stretchy).remove();
-
- // This will signal to commit the path
- element = newpath;
- drawn_path = null;
- started = false;
-
- if (subpath) {
- if (svgedit.path.path.matrix) {
- svgedit.coords.remapElement(newpath, {}, svgedit.path.path.matrix.inverse());
- }
-
- var new_d = newpath.getAttribute("d");
- var orig_d = $(svgedit.path.path.elem).attr("d");
- $(svgedit.path.path.elem).attr("d", orig_d + new_d);
- $(newpath).remove();
- if (svgedit.path.path.matrix) {
- svgedit.path.recalcRotatedPath();
- }
- svgedit.path.path.init();
- pathActions.toEditMode(svgedit.path.path.elem);
- svgedit.path.path.selectPt();
- return false;
- }
- }
- // else, create a new point, update path element
- else {
- // Checks if current target or parents are #svgcontent
- if (!$.contains(container, getMouseTarget(evt))) {
- // Clicked outside canvas, so don't make point
- console.log("Clicked outside canvas");
- return false;
- }
- var num = drawn_path.pathSegList.numberOfItems;
- var last = drawn_path.pathSegList.getItem(num -1);
- var lastx = last.x, lasty = last.y;
- if (evt.shiftKey) {
- var xya = svgedit.math.snapToAngle(lastx, lasty, x, y);
- x = xya.x;
- y = xya.y;
- }
-
- // Use the segment defined by stretchy
- s_seg = stretchy.pathSegList.getItem(1);
- if (s_seg.pathSegType === 4) {
- newseg = drawn_path.createSVGPathSegLinetoAbs(round(x), round(y));
- } else {
- newseg = drawn_path.createSVGPathSegCurvetoCubicAbs(
- round(x),
- round(y),
- s_seg.x1 / current_zoom,
- s_seg.y1 / current_zoom,
- s_seg.x2 / current_zoom,
- s_seg.y2 / current_zoom
- );
- }
-
- drawn_path.pathSegList.appendItem(newseg);
-
- x *= current_zoom;
- y *= current_zoom;
-
- // set stretchy line to latest point
- stretchy.setAttribute('d', ['M', x, y, x, y].join(' '));
- index = num;
- if (subpath) {index += svgedit.path.path.segs.length;}
- svgedit.path.addPointGrip(index, x, y);
- }
- // keep = true;
- }
-
- return;
- }
-
- // TODO: Make sure current_path isn't null at this point
- if (!svgedit.path.path) {return;}
-
- svgedit.path.path.storeD();
-
- id = evt.target.id;
- var cur_pt;
- if (id.substr(0,14) == "pathpointgrip_") {
- // Select this point
- cur_pt = svgedit.path.path.cur_pt = parseInt(id.substr(14));
- svgedit.path.path.dragging = [start_x, start_y];
- var seg = svgedit.path.path.segs[cur_pt];
-
- // only clear selection if shift is not pressed (otherwise, add
- // node to selection)
- if (!evt.shiftKey) {
- if (svgedit.path.path.selected_pts.length <= 1 || !seg.selected) {
- svgedit.path.path.clearSelection();
- }
- svgedit.path.path.addPtsToSelection(cur_pt);
- } else if (seg.selected) {
- svgedit.path.path.removePtFromSelection(cur_pt);
- } else {
- svgedit.path.path.addPtsToSelection(cur_pt);
- }
- } else if (id.indexOf("ctrlpointgrip_") == 0) {
- svgedit.path.path.dragging = [start_x, start_y];
-
- var parts = id.split('_')[1].split('c');
- cur_pt = Number(parts[0]);
- var ctrl_num = Number(parts[1]);
- svgedit.path.path.selectPt(cur_pt, ctrl_num);
- }
- // Start selection box
- if (!svgedit.path.path.dragging) {
- if (rubberBox == null) {
- rubberBox = selectorManager.getRubberBandBox();
- }
- svgedit.utilities.assignAttributes(rubberBox, {
- 'x': start_x * current_zoom,
- 'y': start_y * current_zoom,
- 'width': 0,
- 'height': 0,
- 'display': 'inline'
- }, 100);
- }
- },
- mouseMove: function(mouse_x, mouse_y) {
- hasMoved = true;
- if (current_mode === "path") {
- if (!drawn_path) {return;}
- var seglist = drawn_path.pathSegList;
- var index = seglist.numberOfItems - 1;
- if (newPoint) {
- // First point
- // if (!index) {return;}
- // Set control points
- var pointGrip1 = svgedit.path.addCtrlGrip('1c1');
- var pointGrip2 = svgedit.path.addCtrlGrip('0c2');
-
- // dragging pointGrip1
- pointGrip1.setAttribute('cx', mouse_x);
- pointGrip1.setAttribute('cy', mouse_y);
- pointGrip1.setAttribute('display', 'inline');
- var pt_x = newPoint[0];
- var pt_y = newPoint[1];
-
- // set curve
- var seg = seglist.getItem(index);
- var cur_x = mouse_x / current_zoom;
- var cur_y = mouse_y / current_zoom;
- var alt_x = (pt_x + (pt_x - cur_x));
- var alt_y = (pt_y + (pt_y - cur_y));
-
- pointGrip2.setAttribute('cx', alt_x * current_zoom);
- pointGrip2.setAttribute('cy', alt_y * current_zoom);
- pointGrip2.setAttribute('display', 'inline');
-
- var ctrlLine = svgedit.path.getCtrlLine(1);
- svgedit.utilities.assignAttributes(ctrlLine, {
- x1: mouse_x,
- y1: mouse_y,
- x2: alt_x * current_zoom,
- y2: alt_y * current_zoom,
- display: 'inline'
- });
- if (index === 0) {
- firstCtrl = [mouse_x, mouse_y];
- } else {
- var last = seglist.getItem(index - 1);
- var last_x = last.x;
- var last_y = last.y;
-
- if (last.pathSegType === 6) {
- last_x += (last_x - last.x2);
- last_y += (last_y - last.y2);
- } else if (firstCtrl) {
- last_x = firstCtrl[0]/current_zoom;
- last_y = firstCtrl[1]/current_zoom;
- }
- svgedit.path.replacePathSeg(6, index, [pt_x, pt_y, last_x, last_y, alt_x, alt_y], drawn_path);
- }
- } else {
- var stretchy = svgedit.utilities.getElem("path_stretch_line");
- if (stretchy) {
- var prev = seglist.getItem(index);
- if (prev.pathSegType === 6) {
- var prev_x = prev.x + (prev.x - prev.x2);
- var prev_y = prev.y + (prev.y - prev.y2);
- svgedit.path.replacePathSeg(6, 1, [mouse_x, mouse_y, prev_x * current_zoom, prev_y * current_zoom, mouse_x, mouse_y], stretchy);
- } else if (firstCtrl) {
- svgedit.path.replacePathSeg(6, 1, [mouse_x, mouse_y, firstCtrl[0], firstCtrl[1], mouse_x, mouse_y], stretchy);
- } else {
- svgedit.path.replacePathSeg(4, 1, [mouse_x, mouse_y], stretchy);
- }
- }
- }
- return;
- }
- // if we are dragging a point, let's move it
- if (svgedit.path.path.dragging) {
- var pt = svgedit.path.getPointFromGrip({
- x: svgedit.path.path.dragging[0],
- y: svgedit.path.path.dragging[1]
- }, svgedit.path.path);
- var mpt = svgedit.path.getPointFromGrip({
- x: mouse_x,
- y: mouse_y
- }, svgedit.path.path);
- var diff_x = mpt.x - pt.x;
- var diff_y = mpt.y - pt.y;
- svgedit.path.path.dragging = [mouse_x, mouse_y];
-
- if (svgedit.path.path.dragctrl) {
- svgedit.path.path.moveCtrl(diff_x, diff_y);
- } else {
- svgedit.path.path.movePts(diff_x, diff_y);
- }
- } else {
- svgedit.path.path.selected_pts = [];
- svgedit.path.path.eachSeg(function(i) {
- var seg = this;
- if (!seg.next && !seg.prev) {return;}
-
- var item = seg.item;
- var rbb = rubberBox.getBBox();
-
- var pt = svgedit.path.getGripPt(seg);
- var pt_bb = {
- x: pt.x,
- y: pt.y,
- width: 0,
- height: 0
- };
-
- var sel = svgedit.math.rectsIntersect(rbb, pt_bb);
- this.select(sel);
- //Note that addPtsToSelection is not being run
- if (sel) {svgedit.path.path.selected_pts.push(seg.index);}
- });
- }
- },
- mouseUp: function(evt, element, mouse_x, mouse_y) {
-
- // Create mode
- if (current_mode === "path") {
- newPoint = null;
- if (!drawn_path) {
- element = svgedit.utilities.getElem(getId());
- started = false;
- firstCtrl = null;
- }
- return {
- keep: true,
- element: element
- };
- }
-
- // Edit mode
-
- if (svgedit.path.path.dragging) {
- var last_pt = svgedit.path.path.cur_pt;
- svgedit.path.path.dragging = false;
- svgedit.path.path.dragctrl = false;
- svgedit.path.path.update();
-
- if (hasMoved) {
- svgedit.path.path.endChanges("Move path point(s)");
- }
-
- if (!evt.shiftKey && !hasMoved) {
- svgedit.path.path.selectPt(last_pt);
- }
- } else if (rubberBox && rubberBox.getAttribute('display') != 'none') {
- // Done with multi-node-select
- rubberBox.setAttribute("display", "none");
-
- if (rubberBox.getAttribute('width') <= 2 && rubberBox.getAttribute('height') <= 2) {
- pathActions.toSelectMode(evt.target);
- }
-
- // else, move back to select mode
- } else {
- pathActions.toSelectMode(evt.target);
- }
- hasMoved = false;
- },
- toEditMode: function(element) {
- svgedit.path.path = svgedit.path.getPath_(element);
- current_mode = "pathedit";
- clearSelection();
- svgedit.path.path.show(true).update();
- svgedit.path.path.oldbbox = svgedit.utilities.getBBox(svgedit.path.path.elem);
- subpath = false;
- },
- toSelectMode: function(elem) {
- var selPath = (elem == svgedit.path.path.elem);
- current_mode = "select";
- svgedit.path.path.show(false);
- current_path = false;
- clearSelection();
-
- if (svgedit.path.path.matrix) {
- // Rotated, so may need to re-calculate the center
- svgedit.path.recalcRotatedPath();
- }
-
- if (selPath) {
- call("selected", [elem]);
- addToSelection([elem], true);
- }
- },
- addSubPath: function(on) {
- if (on) {
- // Internally we go into "path" mode, but in the UI it will
- // still appear as if in "pathedit" mode.
- current_mode = "path";
- subpath = true;
- } else {
- pathActions.clear(true);
- pathActions.toEditMode(svgedit.path.path.elem);
- }
- },
- select: function(target) {
- if (current_path === target) {
- pathActions.toEditMode(target);
- current_mode = "pathedit";
- } // going into pathedit mode
- else {
- current_path = target;
- }
- },
- reorient: function() {
- var elem = selectedElements[0];
- if (!elem) {return;}
- var angle = svgedit.utilities.getRotationAngle(elem);
- if (angle == 0) {return;}
-
- var batchCmd = new svgedit.history.BatchCommand("Reorient path");
- var changes = {
- d: elem.getAttribute('d'),
- transform: elem.getAttribute('transform')
- };
- batchCmd.addSubCommand(new svgedit.history.ChangeElementCommand(elem, changes));
- clearSelection();
- this.resetOrientation(elem);
-
- addCommandToHistory(batchCmd);
- // Set matrix to null
- svgedit.path.getPath_(elem).show(false).matrix = null;
- this.clear();
-
- addToSelection([elem], true);
- call("changed", selectedElements);
- },
-
- clear: function(remove) {
- current_path = null;
- if (drawn_path) {
- var elem = svgedit.utilities.getElem(getId());
- $(svgedit.utilities.getElem("path_stretch_line")).remove();
- $(elem).remove();
- $(svgedit.utilities.getElem("pathpointgrip_container")).find('*').attr('display', 'none');
- drawn_path = firstCtrl = null;
- started = false;
- } else if (current_mode == "pathedit") {
- this.toSelectMode();
- }
- if (svgedit.path.path) {svgedit.path.path.init().show(false);}
- },
- resetOrientation: function(path) {
- if (path == null || path.nodeName != 'path') {return false;}
- var tlist = svgedit.transformlist.getTransformList(path);
- var m = svgedit.math.transformListToTransform(tlist).matrix;
- tlist.clear();
- path.removeAttribute("transform");
- var segList = path.pathSegList;
-
- // Opera/win/non-EN throws an error here.
- // TODO: Find out why!
- // Presumed fixed in Opera 10.5, so commented out for now
-
- // try {
- var len = segList.numberOfItems;
- // } catch(err) {
- // var fixed_d = pathActions.convertPath(path);
- // path.setAttribute('d', fixed_d);
- // segList = path.pathSegList;
- // var len = segList.numberOfItems;
- // }
- var i, last_x, last_y;
- for (i = 0; i < len; ++i) {
- var seg = segList.getItem(i);
- var type = seg.pathSegType;
- if (type == 1) {continue;}
- var pts = [];
- $.each(['',1,2], function(j, n) {
- var x = seg['x'+n], y = seg['y'+n];
- if (x !== undefined && y !== undefined) {
- var pt = svgedit.math.transformPoint(x, y, m);
- pts.splice(pts.length, 0, pt.x, pt.y);
- }
- });
- svgedit.path.replacePathSeg(type, i, pts, path);
- }
-
- reorientGrads(path, m);
- },
- zoomChange: function() {
- if (current_mode == "pathedit") {
- svgedit.path.path.update();
- }
- },
- getNodePoint: function() {
- var sel_pt = svgedit.path.path.selected_pts.length ? svgedit.path.path.selected_pts[0] : 1;
- var seg = svgedit.path.path.segs[sel_pt];
- return {
- x: seg.item.x,
- y: seg.item.y,
- type: seg.type
- };
- },
- linkControlPoints: function(linkPoints) {
- svgedit.path.setLinkControlPoints(linkPoints);
- },
- clonePathNode: function() {
- svgedit.path.path.storeD();
-
- var sel_pts = svgedit.path.path.selected_pts;
- var segs = svgedit.path.path.segs;
-
- var i = sel_pts.length;
- var nums = [];
- while (i--) {
- var pt = sel_pts[i];
- svgedit.path.path.addSeg(pt);
-
- nums.push(pt + i);
- nums.push(pt + i + 1);
- }
- svgedit.path.path.init().addPtsToSelection(nums);
- svgedit.path.path.endChanges("Clone path node(s)");
- },
- opencloseSubPath: function() {
- var sel_pts = svgedit.path.path.selected_pts;
- // Only allow one selected node for now
- if (sel_pts.length !== 1) {return;}
-
- var elem = svgedit.path.path.elem;
- var list = elem.pathSegList;
- var len = list.numberOfItems;
- var index = sel_pts[0];
-
- var open_pt = null;
- var start_item = null;
- // Check if subpath is already open
- svgedit.path.path.eachSeg(function(i) {
- if (this.type === 2 && i <= index) {
- start_item = this.item;
- }
- if (i <= index) {return true;}
- if (this.type === 2) {
- // Found M first, so open
- open_pt = i;
- return false;
- }
- if (this.type === 1) {
- // Found Z first, so closed
- open_pt = false;
- return false;
- }
- });
-
- if (open_pt == null) {
- // Single path, so close last seg
- open_pt = svgedit.path.path.segs.length - 1;
- }
- if (open_pt !== false) {
- // Close this path
-
- // Create a line going to the previous "M"
- var newseg = elem.createSVGPathSegLinetoAbs(start_item.x, start_item.y);
-
- var closer = elem.createSVGPathSegClosePath();
- if (open_pt == svgedit.path.path.segs.length - 1) {
- list.appendItem(newseg);
- list.appendItem(closer);
- } else {
- svgedit.path.insertItemBefore(elem, closer, open_pt);
- svgedit.path.insertItemBefore(elem, newseg, open_pt);
- }
-
- svgedit.path.path.init().selectPt(open_pt+1);
- return;
- }
-
- // M 1,1 L 2,2 L 3,3 L 1,1 z // open at 2,2
- // M 2,2 L 3,3 L 1,1
-
- // M 1,1 L 2,2 L 1,1 z M 4,4 L 5,5 L6,6 L 5,5 z
- // M 1,1 L 2,2 L 1,1 z [M 4,4] L 5,5 L(M)6,6 L 5,5 z
-
- var seg = svgedit.path.path.segs[index];
-
- if (seg.mate) {
- list.removeItem(index); // Removes last "L"
- list.removeItem(index); // Removes the "Z"
- svgedit.path.path.init().selectPt(index - 1);
- return;
- }
-
- var i, last_m, z_seg;
-
- // Find this sub-path's closing point and remove
- for (i = 0; i<list.numberOfItems; i++) {
- var item = list.getItem(i);
- if (item.pathSegType === 2) {
- // Find the preceding M
- last_m = i;
- } else if (i === index) {
- // Remove it
- list.removeItem(last_m);
- // index--;
- } else if (item.pathSegType === 1 && index < i) {
- // Remove the closing seg of this subpath
- z_seg = i-1;
- list.removeItem(i);
- break;
- }
- }
-
- var num = (index - last_m) - 1;
-
- while (num--) {
- svgedit.path.insertItemBefore(elem, list.getItem(last_m), z_seg);
- }
-
- var pt = list.getItem(last_m);
-
- // Make this point the new "M"
- svgedit.path.replacePathSeg(2, last_m, [pt.x, pt.y]);
-
- i = index; // i is local here, so has no effect; what is the reason for this?
-
- svgedit.path.path.init().selectPt(0);
- },
- deletePathNode: function() {
- if (!pathActions.canDeleteNodes) {return;}
- svgedit.path.path.storeD();
-
- var sel_pts = svgedit.path.path.selected_pts;
- var i = sel_pts.length;
- while (i--) {
- var pt = sel_pts[i];
- svgedit.path.path.deleteSeg(pt);
- }
-
- // Cleanup
- var cleanup = function() {
- var segList = svgedit.path.path.elem.pathSegList;
- var len = segList.numberOfItems;
-
- var remItems = function(pos, count) {
- while (count--) {
- segList.removeItem(pos);
- }
- };
- if (len <= 1) {return true;}
-
- while (len--) {
- var item = segList.getItem(len);
- if (item.pathSegType === 1) {
- var prev = segList.getItem(len-1);
- var nprev = segList.getItem(len-2);
- if (prev.pathSegType === 2) {
- remItems(len-1, 2);
- cleanup();
- break;
- } else if (nprev.pathSegType === 2) {
- remItems(len-2, 3);
- cleanup();
- break;
- }
- } else if (item.pathSegType === 2) {
- if (len > 0) {
- var prev_type = segList.getItem(len-1).pathSegType;
- // Path has M M
- if (prev_type === 2) {
- remItems(len-1, 1);
- cleanup();
- break;
- // Entire path ends with Z M
- } else if (prev_type === 1 && segList.numberOfItems-1 === len) {
- remItems(len, 1);
- cleanup();
- break;
- }
- }
- }
- }
- return false;
- };
-
- cleanup();
-
- // Completely delete a path with 1 or 0 segments
- if (svgedit.path.path.elem.pathSegList.numberOfItems <= 1) {
- pathActions.toSelectMode(svgedit.path.path.elem);
- canvas.deleteSelectedElements();
- return;
- }
-
- svgedit.path.path.init();
- svgedit.path.path.clearSelection();
-
- // TODO: Find right way to select point now
- // path.selectPt(sel_pt);
- if (window.opera) { // Opera repaints incorrectly
- var cp = $(svgedit.path.path.elem);
- cp.attr('d', cp.attr('d'));
- }
- svgedit.path.path.endChanges("Delete path node(s)");
- },
- smoothPolylineIntoPath: smoothPolylineIntoPath,
- setSegType: function(v) {
- svgedit.path.path.setSegType(v);
- },
- moveNode: function(attr, newValue) {
- var sel_pts = svgedit.path.path.selected_pts;
- if (!sel_pts.length) {return;}
-
- svgedit.path.path.storeD();
-
- // Get first selected point
- var seg = svgedit.path.path.segs[sel_pts[0]];
- var diff = {x:0, y:0};
- diff[attr] = newValue - seg.item[attr];
-
- seg.move(diff.x, diff.y);
- svgedit.path.path.endChanges("Move path point");
- },
- fixEnd: function(elem) {
- // Adds an extra segment if the last seg before a Z doesn't end
- // at its M point
- // M0,0 L0,100 L100,100 z
- var segList = elem.pathSegList;
- var len = segList.numberOfItems;
- var i, last_m;
- for (i = 0; i < len; ++i) {
- var item = segList.getItem(i);
- if (item.pathSegType === 2) {
- last_m = item;
- }
-
- if (item.pathSegType === 1) {
- var prev = segList.getItem(i-1);
- if (prev.x != last_m.x || prev.y != last_m.y) {
- // Add an L segment here
- var newseg = elem.createSVGPathSegLinetoAbs(last_m.x, last_m.y);
- svgedit.path.insertItemBefore(elem, newseg, i);
- // Can this be done better?
- pathActions.fixEnd(elem);
- break;
- }
-
- }
- }
- if (svgedit.browser.isWebkit()) {resetD(elem);}
- },
- // Convert a path to one with only absolute or relative values
- convertPath: function(path, toRel) {
- var i;
- var segList = path.pathSegList;
- var len = segList.numberOfItems;
- var curx = 0, cury = 0;
- var d = "";
- var last_m = null;
-
- for (i = 0; i < len; ++i) {
- var seg = segList.getItem(i);
- // if these properties are not in the segment, set them to zero
- var x = seg.x || 0,
- y = seg.y || 0,
- x1 = seg.x1 || 0,
- y1 = seg.y1 || 0,
- x2 = seg.x2 || 0,
- y2 = seg.y2 || 0;
-
- var type = seg.pathSegType;
- var letter = pathMap[type]['to'+(toRel?'Lower':'Upper')+'Case']();
-
- var addToD = function(pnts, more, last) {
- var str = '';
- more = more ? ' ' + more.join(' ') : '';
- last = last ? ' ' + svgedit.units.shortFloat(last) : '';
- $.each(pnts, function(i, pnt) {
- pnts[i] = svgedit.units.shortFloat(pnt);
- });
- d += letter + pnts.join(' ') + more + last;
- };
-
- switch (type) {
- case 1: // z,Z closepath (Z/z)
- d += "z";
- break;
- case 12: // absolute horizontal line (H)
- x -= curx;
- case 13: // relative horizontal line (h)
- if (toRel) {
- curx += x;
- letter = 'l';
- } else {
- x += curx;
- curx = x;
- letter = 'L';
- }
- // Convert to "line" for easier editing
- addToD([[x, cury]]);
- break;
- case 14: // absolute vertical line (V)
- y -= cury;
- case 15: // relative vertical line (v)
- if (toRel) {
- cury += y;
- letter = 'l';
- } else {
- y += cury;
- cury = y;
- letter = 'L';
- }
- // Convert to "line" for easier editing
- addToD([[curx, y]]);
- break;
- case 2: // absolute move (M)
- case 4: // absolute line (L)
- case 18: // absolute smooth quad (T)
- x -= curx;
- y -= cury;
- case 5: // relative line (l)
- case 3: // relative move (m)
- // If the last segment was a "z", this must be relative to
- if (last_m && segList.getItem(i-1).pathSegType === 1 && !toRel) {
- curx = last_m[0];
- cury = last_m[1];
- }
-
- case 19: // relative smooth quad (t)
- if (toRel) {
- curx += x;
- cury += y;
- } else {
- x += curx;
- y += cury;
- curx = x;
- cury = y;
- }
- if (type === 3) {last_m = [curx, cury];}
-
- addToD([[x, y]]);
- break;
- case 6: // absolute cubic (C)
- x -= curx; x1 -= curx; x2 -= curx;
- y -= cury; y1 -= cury; y2 -= cury;
- case 7: // relative cubic (c)
- if (toRel) {
- curx += x;
- cury += y;
- } else {
- x += curx; x1 += curx; x2 += curx;
- y += cury; y1 += cury; y2 += cury;
- curx = x;
- cury = y;
- }
- addToD([[x1, y1], [x2, y2], [x, y]]);
- break;
- case 8: // absolute quad (Q)
- x -= curx; x1 -= curx;
- y -= cury; y1 -= cury;
- case 9: // relative quad (q)
- if (toRel) {
- curx += x;
- cury += y;
- } else {
- x += curx; x1 += curx;
- y += cury; y1 += cury;
- curx = x;
- cury = y;
- }
- addToD([[x1, y1],[x, y]]);
- break;
- case 10: // absolute elliptical arc (A)
- x -= curx;
- y -= cury;
- case 11: // relative elliptical arc (a)
- if (toRel) {
- curx += x;
- cury += y;
- } else {
- x += curx;
- y += cury;
- curx = x;
- cury = y;
- }
- addToD([[seg.r1, seg.r2]], [
- seg.angle,
- (seg.largeArcFlag ? 1 : 0),
- (seg.sweepFlag ? 1 : 0)
- ], [x, y]
- );
- break;
- case 16: // absolute smooth cubic (S)
- x -= curx; x2 -= curx;
- y -= cury; y2 -= cury;
- case 17: // relative smooth cubic (s)
- if (toRel) {
- curx += x;
- cury += y;
- } else {
- x += curx; x2 += curx;
- y += cury; y2 += cury;
- curx = x;
- cury = y;
- }
- addToD([[x2, y2],[x, y]]);
- break;
- } // switch on path segment type
- } // for each segment
- return d;
- }
- };
- }();
- // end pathActions
- // Group: Serialization
- // Function: removeUnusedDefElems
- // Looks at DOM elements inside the <defs> to see if they are referred to,
- // removes them from the DOM if they are not.
- //
- // Returns:
- // The amount of elements that were removed
- var removeUnusedDefElems = this.removeUnusedDefElems = function() {
- var defs = svgcontent.getElementsByTagNameNS(NS.SVG, "defs");
- if (!defs || !defs.length) {return 0;}
-
- // if (!defs.firstChild) {return;}
-
- var defelem_uses = [],
- numRemoved = 0;
- var attrs = ['fill', 'stroke', 'filter', 'marker-start', 'marker-mid', 'marker-end'];
- var alen = attrs.length;
-
- var all_els = svgcontent.getElementsByTagNameNS(NS.SVG, '*');
- var all_len = all_els.length;
-
- var i, j;
- for (i = 0; i < all_len; i++) {
- var el = all_els[i];
- for (j = 0; j < alen; j++) {
- var ref = svgedit.utilities.getUrlFromAttr(el.getAttribute(attrs[j]));
- if (ref) {
- defelem_uses.push(ref.substr(1));
- }
- }
-
- // gradients can refer to other gradients
- var href = getHref(el);
- if (href && href.indexOf('#') === 0) {
- defelem_uses.push(href.substr(1));
- }
- }
-
- var defelems = $(defs).find("linearGradient, radialGradient, filter, marker, svg, symbol");
- i = defelems.length;
- while (i--) {
- var defelem = defelems[i];
- var id = defelem.id;
- if (defelem_uses.indexOf(id) < 0) {
- // Not found, so remove (but remember)
- removedElements[id] = defelem;
- defelem.parentNode.removeChild(defelem);
- numRemoved++;
- }
- }
- return numRemoved;
- };
- // Function: svgCanvasToString
- // Main function to set up the SVG content for output
- //
- // Returns:
- // String containing the SVG image for output
- this.svgCanvasToString = function() {
- // keep calling it until there are none to remove
- while (removeUnusedDefElems() > 0) {}
-
- pathActions.clear(true);
-
- // Keep SVG-Edit comment on top
- $.each(svgcontent.childNodes, function(i, node) {
- if (i && node.nodeType === 8 && node.data.indexOf('Created with') >= 0) {
- svgcontent.insertBefore(node, svgcontent.firstChild);
- }
- });
-
- // Move out of in-group editing mode
- if (current_group) {
- leaveContext();
- selectOnly([current_group]);
- }
-
- var naked_svgs = [];
-
- // Unwrap gsvg if it has no special attributes (only id and style)
- $(svgcontent).find('g:data(gsvg)').each(function() {
- var attrs = this.attributes;
- var len = attrs.length;
- var i;
- for (i = 0; i < len; i++) {
- if (attrs[i].nodeName == 'id' || attrs[i].nodeName == 'style') {
- len--;
- }
- }
- // No significant attributes, so ungroup
- if (len <= 0) {
- var svg = this.firstChild;
- naked_svgs.push(svg);
- $(this).replaceWith(svg);
- }
- });
- var output = this.svgToString(svgcontent, 0);
-
- // Rewrap gsvg
- if (naked_svgs.length) {
- $(naked_svgs).each(function() {
- groupSvgElem(this);
- });
- }
-
- return output;
- };
- // Function: svgToString
- // Sub function ran on each SVG element to convert it to a string as desired
- //
- // Parameters:
- // elem - The SVG element to convert
- // indent - Integer with the amount of spaces to indent this tag
- //
- // Returns:
- // String with the given element as an SVG tag
- this.svgToString = function(elem, indent) {
- var out = [],
- toXml = svgedit.utilities.toXml;
- var unit = curConfig.baseUnit;
- var unit_re = new RegExp('^-?[\\d\\.]+' + unit + '$');
- if (elem) {
- cleanupElement(elem);
- var attrs = elem.attributes,
- attr,
- i,
- childs = elem.childNodes;
-
- for (i = 0; i < indent; i++) {out.push(' ');}
- out.push("<"); out.push(elem.nodeName);
- if (elem.id === 'svgcontent') {
- // Process root element separately
- var res = getResolution();
-
- var vb = "";
- // TODO: Allow this by dividing all values by current baseVal
- // Note that this also means we should properly deal with this on import
- // if (curConfig.baseUnit !== "px") {
- // var unit = curConfig.baseUnit;
- // var unit_m = svgedit.units.getTypeMap()[unit];
- // res.w = svgedit.units.shortFloat(res.w / unit_m)
- // res.h = svgedit.units.shortFloat(res.h / unit_m)
- // vb = ' viewBox="' + [0, 0, res.w, res.h].join(' ') + '"';
- // res.w += unit;
- // res.h += unit;
- // }
-
- if (unit !== "px") {
- res.w = svgedit.units.convertUnit(res.w, unit) + unit;
- res.h = svgedit.units.convertUnit(res.h, unit) + unit;
- }
-
- out.push(' width="' + res.w + '" height="' + res.h + '"' + vb + ' xmlns="'+NS.SVG+'"');
-
- var nsuris = {};
-
- // Check elements for namespaces, add if found
- $(elem).find('*').andSelf().each(function() {
- var el = this;
- // for some elements have no attribute
- var uri = this.namespaceURI;
- if(uri && !nsuris[uri] && nsMap[uri] && nsMap[uri] !== 'xmlns' && nsMap[uri] !== 'xml' ) {
- nsuris[uri] = true;
- out.push(" xmlns:" + nsMap[uri] + '="' + uri +'"');
- }
-
- $.each(this.attributes, function(i, attr) {
- var uri = attr.namespaceURI;
- if (uri && !nsuris[uri] && nsMap[uri] !== 'xmlns' && nsMap[uri] !== 'xml' ) {
- nsuris[uri] = true;
- out.push(" xmlns:" + nsMap[uri] + '="' + uri +'"');
- }
- });
- });
-
- i = attrs.length;
- var attr_names = ['width', 'height', 'xmlns', 'x', 'y', 'viewBox', 'id', 'overflow'];
- while (i--) {
- attr = attrs.item(i);
- var attrVal = toXml(attr.nodeValue);
-
- // Namespaces have already been dealt with, so skip
- if (attr.nodeName.indexOf('xmlns:') === 0) {continue;}
- // only serialize attributes we don't use internally
- if (attrVal != "" && attr_names.indexOf(attr.localName) == -1) {
- if (!attr.namespaceURI || nsMap[attr.namespaceURI]) {
- out.push(' ');
- out.push(attr.nodeName); out.push("=\"");
- out.push(attrVal); out.push("\"");
- }
- }
- }
- } else {
- // Skip empty defs
- if (elem.nodeName === 'defs' && !elem.firstChild) {return;}
-
- var moz_attrs = ['-moz-math-font-style', '_moz-math-font-style'];
- for (i = attrs.length - 1; i >= 0; i--) {
- attr = attrs.item(i);
- var attrVal = toXml(attr.nodeValue);
- //remove bogus attributes added by Gecko
- if (moz_attrs.indexOf(attr.localName) >= 0) {continue;}
- if (attrVal != "") {
- if (attrVal.indexOf('pointer-events') === 0) {continue;}
- if (attr.localName === "class" && attrVal.indexOf('se_') === 0) {continue;}
- out.push(" ");
- if (attr.localName === 'd') {attrVal = pathActions.convertPath(elem, true);}
- if (!isNaN(attrVal)) {
- attrVal = svgedit.units.shortFloat(attrVal);
- } else if (unit_re.test(attrVal)) {
- attrVal = svgedit.units.shortFloat(attrVal) + unit;
- }
-
- // Embed images when saving
- if (save_options.apply
- && elem.nodeName === 'image'
- && attr.localName === 'href'
- && save_options.images
- && save_options.images === 'embed')
- {
- var img = encodableImages[attrVal];
- if (img) {attrVal = img;}
- }
-
- // map various namespaces to our fixed namespace prefixes
- // (the default xmlns attribute itself does not get a prefix)
- if (!attr.namespaceURI || attr.namespaceURI == NS.SVG || nsMap[attr.namespaceURI]) {
- out.push(attr.nodeName); out.push("=\"");
- out.push(attrVal); out.push("\"");
- }
- }
- }
- }
- if (elem.hasChildNodes()) {
- out.push(">");
- indent++;
- var bOneLine = false;
-
- for (i = 0; i < childs.length; i++) {
- var child = childs.item(i);
- switch(child.nodeType) {
- case 1: // element node
- out.push("\n");
- out.push(this.svgToString(childs.item(i), indent));
- break;
- case 3: // text node
- var str = child.nodeValue.replace(/^\s+|\s+$/g, "");
- if (str != "") {
- bOneLine = true;
- out.push(String(toXml(str)));
- }
- break;
- case 4: // cdata node
- out.push("\n");
- out.push(new Array(indent+1).join(" "));
- out.push("<![CDATA[");
- out.push(child.nodeValue);
- out.push("]]>");
- break;
- case 8: // comment
- out.push("\n");
- out.push(new Array(indent+1).join(" "));
- out.push("<!--");
- out.push(child.data);
- out.push("-->");
- break;
- } // switch on node type
- }
- indent--;
- if (!bOneLine) {
- out.push("\n");
- for (i = 0; i < indent; i++) {out.push(' ');}
- }
- out.push("</"); out.push(elem.nodeName); out.push(">");
- } else {
- out.push("/>");
- }
- }
- return out.join('');
- }; // end svgToString()
- // Function: embedImage
- // Converts a given image file to a data URL when possible, then runs a given callback
- //
- // Parameters:
- // val - String with the path/URL of the image
- // callback - Optional function to run when image data is found, supplies the
- // result (data URL or false) as first parameter.
- this.embedImage = function(val, callback) {
- // load in the image and once it's loaded, get the dimensions
- $(new Image()).load(function() {
- // create a canvas the same size as the raster image
- var canvas = document.createElement("canvas");
- canvas.width = this.width;
- canvas.height = this.height;
- // load the raster image into the canvas
- canvas.getContext("2d").drawImage(this, 0, 0);
- // retrieve the data: URL
- try {
- var urldata = ';svgedit_url=' + encodeURIComponent(val);
- urldata = canvas.toDataURL().replace(';base64', urldata+';base64');
- encodableImages[val] = urldata;
- } catch(e) {
- encodableImages[val] = false;
- }
- last_good_img_url = val;
- if (callback) {callback(encodableImages[val]);}
- }).attr('src', val);
- };
- // Function: setGoodImage
- // Sets a given URL to be a "last good image" URL
- this.setGoodImage = function(val) {
- last_good_img_url = val;
- };
- this.open = function() {
- // Nothing by default, handled by optional widget/extension
- };
- // Function: save
- // Serializes the current drawing into SVG XML text and returns it to the 'saved' handler.
- // This function also includes the XML prolog. Clients of the SvgCanvas bind their save
- // function to the 'saved' event.
- //
- // Returns:
- // Nothing
- this.save = function(opts) {
- // remove the selected outline before serializing
- clearSelection();
- // Update save options if provided
- if (opts) {$.extend(save_options, opts);}
- save_options.apply = true;
-
- // no need for doctype, see http://jwatt.org/svg/authoring/#doctype-declaration
- var str = this.svgCanvasToString();
- call("saved", str);
- };
- // Function: rasterExport
- // Generates a Data URL based on the current image, then calls "exported"
- // with an object including the string, image information, and any issues found
- this.rasterExport = function(imgType, quality) {
- var mimeType = 'image/' + imgType.toLowerCase();
- // remove the selected outline before serializing
- clearSelection();
-
- // Check for known CanVG issues
- var issues = [];
-
- // Selector and notice
- var issue_list = {
- 'feGaussianBlur': uiStrings.exportNoBlur,
- 'foreignObject': uiStrings.exportNoforeignObject,
- '[stroke-dasharray]': uiStrings.exportNoDashArray
- };
- var content = $(svgcontent);
-
- // Add font/text check if Canvas Text API is not implemented
- if (!("font" in $('<canvas>')[0].getContext('2d'))) {
- issue_list.text = uiStrings.exportNoText;
- }
-
- $.each(issue_list, function(sel, descr) {
- if (content.find(sel).length) {
- issues.push(descr);
- }
- });
- var str = this.svgCanvasToString();
- call("exported", {svg: str, issues: issues, type: imgType, mimeType: mimeType, quality: quality});
- };
- // Function: getSvgString
- // Returns the current drawing as raw SVG XML text.
- //
- // Returns:
- // The current drawing as raw SVG XML text.
- this.getSvgString = function() {
- save_options.apply = false;
- return this.svgCanvasToString();
- };
- // Function: randomizeIds
- // This function determines whether to use a nonce in the prefix, when
- // generating IDs for future documents in SVG-Edit.
- //
- // Parameters:
- // an optional boolean, which, if true, adds a nonce to the prefix. Thus
- // svgCanvas.randomizeIds() <==> svgCanvas.randomizeIds(true)
- //
- // if you're controlling SVG-Edit externally, and want randomized IDs, call
- // this BEFORE calling svgCanvas.setSvgString
- //
- this.randomizeIds = function(enableRandomization) {
- if (arguments.length > 0 && enableRandomization == false) {
- svgedit.draw.randomizeIds(false, getCurrentDrawing());
- } else {
- svgedit.draw.randomizeIds(true, getCurrentDrawing());
- }
- };
- // Function: uniquifyElems
- // Ensure each element has a unique ID
- //
- // Parameters:
- // g - The parent element of the tree to give unique IDs
- var uniquifyElems = this.uniquifyElems = function(g) {
- var ids = {};
- // TODO: Handle markers and connectors. These are not yet re-identified properly
- // as their referring elements do not get remapped.
- //
- // <marker id='se_marker_end_svg_7'/>
- // <polyline id='svg_7' se:connector='svg_1 svg_6' marker-end='url(#se_marker_end_svg_7)'/>
- //
- // Problem #1: if svg_1 gets renamed, we do not update the polyline's se:connector attribute
- // Problem #2: if the polyline svg_7 gets renamed, we do not update the marker id nor the polyline's marker-end attribute
- var ref_elems = ["filter", "linearGradient", "pattern", "radialGradient", "symbol", "textPath", "use"];
-
- svgedit.utilities.walkTree(g, function(n) {
- // if it's an element node
- if (n.nodeType == 1) {
- // and the element has an ID
- if (n.id) {
- // and we haven't tracked this ID yet
- if (!(n.id in ids)) {
- // add this id to our map
- ids[n.id] = {elem:null, attrs:[], hrefs:[]};
- }
- ids[n.id].elem = n;
- }
-
- // now search for all attributes on this element that might refer
- // to other elements
- $.each(ref_attrs, function(i, attr) {
- var attrnode = n.getAttributeNode(attr);
- if (attrnode) {
- // the incoming file has been sanitized, so we should be able to safely just strip off the leading #
- var url = svgedit.utilities.getUrlFromAttr(attrnode.value),
- refid = url ? url.substr(1) : null;
- if (refid) {
- if (!(refid in ids)) {
- // add this id to our map
- ids[refid] = {elem:null, attrs:[], hrefs:[]};
- }
- ids[refid].attrs.push(attrnode);
- }
- }
- });
-
- // check xlink:href now
- var href = svgedit.utilities.getHref(n);
- // TODO: what if an <image> or <a> element refers to an element internally?
- if (href && ref_elems.indexOf(n.nodeName) >= 0) {
- var refid = href.substr(1);
- if (refid) {
- if (!(refid in ids)) {
- // add this id to our map
- ids[refid] = {elem:null, attrs:[], hrefs:[]};
- }
- ids[refid].hrefs.push(n);
- }
- }
- }
- });
-
- // in ids, we now have a map of ids, elements and attributes, let's re-identify
- var oldid;
- for (oldid in ids) {
- if (!oldid) {continue;}
- var elem = ids[oldid].elem;
- if (elem) {
- var newid = getNextId();
-
- // assign element its new id
- elem.id = newid;
-
- // remap all url() attributes
- var attrs = ids[oldid].attrs;
- var j = attrs.length;
- while (j--) {
- var attr = attrs[j];
- attr.ownerElement.setAttribute(attr.name, "url(#" + newid + ")");
- }
-
- // remap all href attributes
- var hreffers = ids[oldid].hrefs;
- var k = hreffers.length;
- while (k--) {
- var hreffer = hreffers[k];
- svgedit.utilities.setHref(hreffer, "#"+newid);
- }
- }
- }
- };
- // Function setUseData
- // Assigns reference data for each use element
- var setUseData = this.setUseData = function(parent) {
- var elems = $(parent);
-
- if (parent.tagName !== 'use') {
- elems = elems.find('use');
- }
-
- elems.each(function() {
- var id = getHref(this).substr(1);
- var ref_elem = svgedit.utilities.getElem(id);
- if (!ref_elem) {return;}
- $(this).data('ref', ref_elem);
- if (ref_elem.tagName == 'symbol' || ref_elem.tagName == 'svg') {
- $(this).data('symbol', ref_elem).data('ref', ref_elem);
- }
- });
- };
- // Function convertGradients
- // Converts gradients from userSpaceOnUse to objectBoundingBox
- var convertGradients = this.convertGradients = function(elem) {
- var elems = $(elem).find('linearGradient, radialGradient');
- if (!elems.length && svgedit.browser.isWebkit()) {
- // Bug in webkit prevents regular *Gradient selector search
- elems = $(elem).find('*').filter(function() {
- return (this.tagName.indexOf('Gradient') >= 0);
- });
- }
-
- elems.each(function() {
- var grad = this;
- if ($(grad).attr('gradientUnits') === 'userSpaceOnUse') {
- // TODO: Support more than one element with this ref by duplicating parent grad
- var elems = $(svgcontent).find('[fill="url(#' + grad.id + ')"],[stroke="url(#' + grad.id + ')"]');
- if (!elems.length) {return;}
-
- // get object's bounding box
- var bb = svgedit.utilities.getBBox(elems[0]);
-
- // This will occur if the element is inside a <defs> or a <symbol>,
- // in which we shouldn't need to convert anyway.
- if (!bb) {return;}
-
- if (grad.tagName === 'linearGradient') {
- var g_coords = $(grad).attr(['x1', 'y1', 'x2', 'y2']);
-
- // If has transform, convert
- var tlist = grad.gradientTransform.baseVal;
- if (tlist && tlist.numberOfItems > 0) {
- var m = svgedit.math.transformListToTransform(tlist).matrix;
- var pt1 = svgedit.math.transformPoint(g_coords.x1, g_coords.y1, m);
- var pt2 = svgedit.math.transformPoint(g_coords.x2, g_coords.y2, m);
-
- g_coords.x1 = pt1.x;
- g_coords.y1 = pt1.y;
- g_coords.x2 = pt2.x;
- g_coords.y2 = pt2.y;
- grad.removeAttribute('gradientTransform');
- }
-
- $(grad).attr({
- x1: (g_coords.x1 - bb.x) / bb.width,
- y1: (g_coords.y1 - bb.y) / bb.height,
- x2: (g_coords.x2 - bb.x) / bb.width,
- y2: (g_coords.y2 - bb.y) / bb.height
- });
- grad.removeAttribute('gradientUnits');
- }
- // else {
- // Note: radialGradient elements cannot be easily converted
- // because userSpaceOnUse will keep circular gradients, while
- // objectBoundingBox will x/y scale the gradient according to
- // its bbox.
-
- // For now we'll do nothing, though we should probably have
- // the gradient be updated as the element is moved, as
- // inkscape/illustrator do.
-
- // var g_coords = $(grad).attr(['cx', 'cy', 'r']);
- //
- // $(grad).attr({
- // cx: (g_coords.cx - bb.x) / bb.width,
- // cy: (g_coords.cy - bb.y) / bb.height,
- // r: g_coords.r
- // });
- //
- // grad.removeAttribute('gradientUnits');
- // }
- }
- });
- };
- // Function: convertToGroup
- // Converts selected/given <use> or child SVG element to a group
- var convertToGroup = this.convertToGroup = function(elem) {
- if (!elem) {
- elem = selectedElements[0];
- }
- var $elem = $(elem);
- var batchCmd = new svgedit.history.BatchCommand();
- var ts;
-
- if ($elem.data('gsvg')) {
- // Use the gsvg as the new group
- var svg = elem.firstChild;
- var pt = $(svg).attr(['x', 'y']);
-
- $(elem.firstChild.firstChild).unwrap();
- $(elem).removeData('gsvg');
-
- var tlist = svgedit.transformlist.getTransformList(elem);
- var xform = svgroot.createSVGTransform();
- xform.setTranslate(pt.x, pt.y);
- tlist.appendItem(xform);
- svgedit.recalculate.recalculateDimensions(elem);
- call("selected", [elem]);
- } else if ($elem.data('symbol')) {
- elem = $elem.data('symbol');
-
- ts = $elem.attr('transform');
- var pos = $elem.attr(['x', 'y']);
- var vb = elem.getAttribute('viewBox');
-
- if (vb) {
- var nums = vb.split(' ');
- pos.x -= +nums[0];
- pos.y -= +nums[1];
- }
-
- // Not ideal, but works
- ts += " translate(" + (pos.x || 0) + "," + (pos.y || 0) + ")";
-
- var prev = $elem.prev();
-
- // Remove <use> element
- batchCmd.addSubCommand(new svgedit.history.RemoveElementCommand($elem[0], $elem[0].nextSibling, $elem[0].parentNode));
- $elem.remove();
-
- // See if other elements reference this symbol
- var has_more = $(svgcontent).find('use:data(symbol)').length;
-
- var g = svgdoc.createElementNS(NS.SVG, "g");
- var childs = elem.childNodes;
-
- var i;
- for (i = 0; i < childs.length; i++) {
- g.appendChild(childs[i].cloneNode(true));
- }
-
- // Duplicate the gradients for Gecko, since they weren't included in the <symbol>
- if (svgedit.browser.isGecko()) {
- var dupeGrads = $(svgedit.utilities.findDefs()).children('linearGradient,radialGradient,pattern').clone();
- $(g).append(dupeGrads);
- }
-
- if (ts) {
- g.setAttribute("transform", ts);
- }
-
- var parent = elem.parentNode;
-
- uniquifyElems(g);
-
- // Put the dupe gradients back into <defs> (after uniquifying them)
- if (svgedit.browser.isGecko()) {
- $(findDefs()).append( $(g).find('linearGradient,radialGradient,pattern') );
- }
-
- // now give the g itself a new id
- g.id = getNextId();
-
- prev.after(g);
-
- if (parent) {
- if (!has_more) {
- // remove symbol/svg element
- var nextSibling = elem.nextSibling;
- parent.removeChild(elem);
- batchCmd.addSubCommand(new svgedit.history.RemoveElementCommand(elem, nextSibling, parent));
- }
- batchCmd.addSubCommand(new svgedit.history.InsertElementCommand(g));
- }
-
- setUseData(g);
-
- if (svgedit.browser.isGecko()) {
- convertGradients(svgedit.utilities.findDefs());
- } else {
- convertGradients(g);
- }
-
- // recalculate dimensions on the top-level children so that unnecessary transforms
- // are removed
- svgedit.utilities.walkTreePost(g, function(n){
- try {
- svgedit.recalculate.recalculateDimensions(n);
- } catch(e) {
- console.log(e);
- }
- });
-
- // Give ID for any visible element missing one
- $(g).find(visElems).each(function() {
- if (!this.id) {this.id = getNextId();}
- });
-
- selectOnly([g]);
-
- var cm = pushGroupProperties(g, true);
- if (cm) {
- batchCmd.addSubCommand(cm);
- }
- addCommandToHistory(batchCmd);
-
- } else {
- console.log('Unexpected element to ungroup:', elem);
- }
- };
- //
- // Function: setSvgString
- // This function sets the current drawing as the input SVG XML.
- //
- // Parameters:
- // xmlString - The SVG as XML text.
- //
- // Returns:
- // This function returns false if the set was unsuccessful, true otherwise.
- this.setSvgString = function(xmlString) {
- try {
- // convert string into XML document
- var newDoc = svgedit.utilities.text2xml(xmlString);
- this.prepareSvg(newDoc);
- var batchCmd = new svgedit.history.BatchCommand("Change Source");
- // remove old svg document
- var nextSibling = svgcontent.nextSibling;
- var oldzoom = svgroot.removeChild(svgcontent);
- batchCmd.addSubCommand(new svgedit.history.RemoveElementCommand(oldzoom, nextSibling, svgroot));
-
- // set new svg document
- // If DOM3 adoptNode() available, use it. Otherwise fall back to DOM2 importNode()
- if (svgdoc.adoptNode) {
- svgcontent = svgdoc.adoptNode(newDoc.documentElement);
- }
- else {
- svgcontent = svgdoc.importNode(newDoc.documentElement, true);
- }
-
- svgroot.appendChild(svgcontent);
- var content = $(svgcontent);
-
- canvas.current_drawing_ = new svgedit.draw.Drawing(svgcontent, idprefix);
-
- // retrieve or set the nonce
- var nonce = getCurrentDrawing().getNonce();
- if (nonce) {
- call("setnonce", nonce);
- } else {
- call("unsetnonce");
- }
-
- // change image href vals if possible
- content.find('image').each(function() {
- var image = this;
- preventClickDefault(image);
- var val = getHref(this);
- if (val) {
- if (val.indexOf('data:') === 0) {
- // Check if an SVG-edit data URI
- var m = val.match(/svgedit_url=(.*?);/);
- if (m) {
- var url = decodeURIComponent(m[1]);
- $(new Image()).load(function () {
- image.setAttributeNS(NS.XLINK, 'xlink:href', url);
- }).attr('src', url);
- }
- }
- // Add to encodableImages if it loads
- canvas.embedImage(val);
- }
- });
-
- // Wrap child SVGs in group elements
- content.find('svg').each(function() {
- // Skip if it's in a <defs>
- if ($(this).closest('defs').length) {return;}
-
- uniquifyElems(this);
-
- // Check if it already has a gsvg group
- var pa = this.parentNode;
- if (pa.childNodes.length === 1 && pa.nodeName === 'g') {
- $(pa).data('gsvg', this);
- pa.id = pa.id || getNextId();
- } else {
- groupSvgElem(this);
- }
- });
-
- // For Firefox: Put all paint elems in defs
- if (svgedit.browser.isGecko()) {
- content.find('linearGradient, radialGradient, pattern').appendTo(svgedit.utilities.findDefs());
- }
- // Set ref element for <use> elements
-
- // TODO: This should also be done if the object is re-added through "redo"
- setUseData(content);
-
- convertGradients(content[0]);
-
- // recalculate dimensions on the top-level children so that unnecessary transforms
- // are removed
- svgedit.utilities.walkTreePost(svgcontent, function(n) {
- try {
- svgedit.recalculate.recalculateDimensions(n);
- } catch(e) {
- console.log(e);
- }
- });
-
- var attrs = {
- id: 'svgcontent',
- overflow: curConfig.show_outside_canvas ? 'visible' : 'hidden'
- };
-
- var percs = false;
-
- // determine proper size
- if (content.attr("viewBox")) {
- var vb = content.attr("viewBox").split(' ');
- attrs.width = vb[2];
- attrs.height = vb[3];
- }
- // handle content that doesn't have a viewBox
- else {
- $.each(['width', 'height'], function(i, dim) {
- // Set to 100 if not given
- var val = content.attr(dim);
-
- if (!val) {val = '100%';}
-
- if (String(val).substr(-1) === "%") {
- // Use user units if percentage given
- percs = true;
- } else {
- attrs[dim] = svgedit.units.convertToNum(dim, val);
- }
- });
- }
-
- // identify layers
- identifyLayers();
-
- // Give ID for any visible layer children missing one
- content.children().find(visElems).each(function() {
- if (!this.id) {this.id = getNextId();}
- });
-
- // Percentage width/height, so let's base it on visible elements
- if (percs) {
- var bb = getStrokedBBox();
- attrs.width = bb.width + bb.x;
- attrs.height = bb.height + bb.y;
- }
-
- // Just in case negative numbers are given or
- // result from the percs calculation
- if (attrs.width <= 0) {attrs.width = 100;}
- if (attrs.height <= 0) {attrs.height = 100;}
-
- content.attr(attrs);
- this.contentW = attrs.width;
- this.contentH = attrs.height;
-
- batchCmd.addSubCommand(new svgedit.history.InsertElementCommand(svgcontent));
- // update root to the correct size
- var changes = content.attr(["width", "height"]);
- batchCmd.addSubCommand(new svgedit.history.ChangeElementCommand(svgroot, changes));
-
- // reset zoom
- current_zoom = 1;
-
- // reset transform lists
- svgedit.transformlist.resetListMap();
- clearSelection();
- svgedit.path.clearData();
- svgroot.appendChild(selectorManager.selectorParentGroup);
-
- addCommandToHistory(batchCmd);
- call("changed", [svgcontent]);
- } catch(e) {
- console.log(e);
- return false;
- }
- return true;
- };
- // Function: importSvgString
- // This function imports the input SVG XML as a <symbol> in the <defs>, then adds a
- // <use> to the current layer.
- //
- // Parameters:
- // xmlString - The SVG as XML text.
- //
- // Returns:
- // This function returns false if the import was unsuccessful, true otherwise.
- // TODO:
- // * properly handle if namespace is introduced by imported content (must add to svgcontent
- // and update all prefixes in the imported node)
- // * properly handle recalculating dimensions, recalculateDimensions() doesn't handle
- // arbitrary transform lists, but makes some assumptions about how the transform list
- // was obtained
- // * import should happen in top-left of current zoomed viewport
- this.importSvgString = function(xmlString) {
- var j, ts;
- try {
- // Get unique ID
- var uid = svgedit.utilities.encode64(xmlString.length + xmlString).substr(0,32);
-
- var useExisting = false;
- // Look for symbol and make sure symbol exists in image
- if (import_ids[uid]) {
- if ( $(import_ids[uid].symbol).parents('#svgroot').length ) {
- useExisting = true;
- }
- }
-
- var batchCmd = new svgedit.history.BatchCommand("Import SVG");
- var symbol;
- if (useExisting) {
- symbol = import_ids[uid].symbol;
- ts = import_ids[uid].xform;
- } else {
- // convert string into XML document
- var newDoc = svgedit.utilities.text2xml(xmlString);
-
- this.prepareSvg(newDoc);
-
- // import new svg document into our document
- var svg;
- // If DOM3 adoptNode() available, use it. Otherwise fall back to DOM2 importNode()
- if (svgdoc.adoptNode) {
- svg = svgdoc.adoptNode(newDoc.documentElement);
- } else {
- svg = svgdoc.importNode(newDoc.documentElement, true);
- }
-
- uniquifyElems(svg);
-
- var innerw = svgedit.units.convertToNum('width', svg.getAttribute("width")),
- innerh = svgedit.units.convertToNum('height', svg.getAttribute("height")),
- innervb = svg.getAttribute("viewBox"),
- // if no explicit viewbox, create one out of the width and height
- vb = innervb ? innervb.split(" ") : [0, 0, innerw, innerh];
- for (j = 0; j < 4; ++j) {
- vb[j] = +(vb[j]);
- }
-
- // TODO: properly handle preserveAspectRatio
- var canvasw = +svgcontent.getAttribute("width"),
- canvash = +svgcontent.getAttribute("height");
- // imported content should be 1/3 of the canvas on its largest dimension
-
- if (innerh > innerw) {
- ts = "scale(" + (canvash/3)/vb[3] + ")";
- } else {
- ts = "scale(" + (canvash/3)/vb[2] + ")";
- }
-
- // Hack to make recalculateDimensions understand how to scale
- ts = "translate(0) " + ts + " translate(0)";
-
- symbol = svgdoc.createElementNS(NS.SVG, "symbol");
- var defs = svgedit.utilities.findDefs();
-
- if (svgedit.browser.isGecko()) {
- // Move all gradients into root for Firefox, workaround for this bug:
- // https://bugzilla.mozilla.org/show_bug.cgi?id=353575
- // TODO: Make this properly undo-able.
- $(svg).find('linearGradient, radialGradient, pattern').appendTo(defs);
- }
-
- while (svg.firstChild) {
- var first = svg.firstChild;
- symbol.appendChild(first);
- }
- var attrs = svg.attributes;
- var i;
- for (i = 0; i < attrs.length; i++) {
- var attr = attrs[i];
- symbol.setAttribute(attr.nodeName, attr.nodeValue);
- }
- symbol.id = getNextId();
-
- // Store data
- import_ids[uid] = {
- symbol: symbol,
- xform: ts
- };
-
- svgedit.utilities.findDefs().appendChild(symbol);
- batchCmd.addSubCommand(new svgedit.history.InsertElementCommand(symbol));
- }
-
- var use_el = svgdoc.createElementNS(NS.SVG, "use");
- use_el.id = getNextId();
- setHref(use_el, "#" + symbol.id);
-
- (current_group || getCurrentDrawing().getCurrentLayer()).appendChild(use_el);
- batchCmd.addSubCommand(new svgedit.history.InsertElementCommand(use_el));
- clearSelection();
-
- use_el.setAttribute("transform", ts);
- svgedit.recalculate.recalculateDimensions(use_el);
- $(use_el).data('symbol', symbol).data('ref', symbol);
- addToSelection([use_el]);
-
- // TODO: Find way to add this in a recalculateDimensions-parsable way
- // if (vb[0] != 0 || vb[1] != 0)
- // ts = "translate(" + (-vb[0]) + "," + (-vb[1]) + ") " + ts;
- addCommandToHistory(batchCmd);
- call("changed", [svgcontent]);
- } catch(e) {
- console.log(e);
- return false;
- }
- return true;
- };
- // TODO(codedread): Move all layer/context functions in draw.js
- // Layer API Functions
- // Group: Layers
- // Function: identifyLayers
- // Updates layer system
- var identifyLayers = canvas.identifyLayers = function() {
- leaveContext();
- getCurrentDrawing().identifyLayers();
- };
- // Function: createLayer
- // Creates a new top-level layer in the drawing with the given name, sets the current layer
- // to it, and then clears the selection. This function then calls the 'changed' handler.
- // This is an undoable action.
- //
- // Parameters:
- // name - The given name
- this.createLayer = function(name) {
- var batchCmd = new svgedit.history.BatchCommand("Create Layer");
- var new_layer = getCurrentDrawing().createLayer(name);
- batchCmd.addSubCommand(new svgedit.history.InsertElementCommand(new_layer));
- addCommandToHistory(batchCmd);
- clearSelection();
- call("changed", [new_layer]);
- };
- // Function: cloneLayer
- // Creates a new top-level layer in the drawing with the given name, copies all the current layer's contents
- // to it, and then clears the selection. This function then calls the 'changed' handler.
- // This is an undoable action.
- //
- // Parameters:
- // name - The given name
- this.cloneLayer = function(name) {
- var batchCmd = new svgedit.history.BatchCommand("Duplicate Layer");
- var new_layer = svgdoc.createElementNS(NS.SVG, "g");
- var layer_title = svgdoc.createElementNS(NS.SVG, "title");
- layer_title.textContent = name;
- new_layer.appendChild(layer_title);
- var current_layer = getCurrentDrawing().getCurrentLayer();
- $(current_layer).after(new_layer);
- var childs = current_layer.childNodes;
- var i;
- for (i = 0; i < childs.length; i++) {
- var ch = childs[i];
- if (ch.localName == 'title') {continue;}
- new_layer.appendChild(copyElem(ch));
- }
-
- clearSelection();
- identifyLayers();
- batchCmd.addSubCommand(new svgedit.history.InsertElementCommand(new_layer));
- addCommandToHistory(batchCmd);
- canvas.setCurrentLayer(name);
- call("changed", [new_layer]);
- };
- // Function: deleteCurrentLayer
- // Deletes the current layer from the drawing and then clears the selection. This function
- // then calls the 'changed' handler. This is an undoable action.
- this.deleteCurrentLayer = function() {
- var current_layer = getCurrentDrawing().getCurrentLayer();
- var nextSibling = current_layer.nextSibling;
- var parent = current_layer.parentNode;
- current_layer = getCurrentDrawing().deleteCurrentLayer();
- if (current_layer) {
- var batchCmd = new svgedit.history.BatchCommand("Delete Layer");
- // store in our Undo History
- batchCmd.addSubCommand(new svgedit.history.RemoveElementCommand(current_layer, nextSibling, parent));
- addCommandToHistory(batchCmd);
- clearSelection();
- call("changed", [parent]);
- return true;
- }
- return false;
- };
- // Function: setCurrentLayer
- // Sets the current layer. If the name is not a valid layer name, then this function returns
- // false. Otherwise it returns true. This is not an undo-able action.
- //
- // Parameters:
- // name - the name of the layer you want to switch to.
- //
- // Returns:
- // true if the current layer was switched, otherwise false
- this.setCurrentLayer = function(name) {
- var result = getCurrentDrawing().setCurrentLayer(svgedit.utilities.toXml(name));
- if (result) {
- clearSelection();
- }
- return result;
- };
- // Function: renameCurrentLayer
- // Renames the current layer. If the layer name is not valid (i.e. unique), then this function
- // does nothing and returns false, otherwise it returns true. This is an undo-able action.
- //
- // Parameters:
- // newname - the new name you want to give the current layer. This name must be unique
- // among all layer names.
- //
- // Returns:
- // true if the rename succeeded, false otherwise.
- this.renameCurrentLayer = function(newname) {
- var i;
- var drawing = getCurrentDrawing();
- if (drawing.current_layer) {
- var oldLayer = drawing.current_layer;
- // setCurrentLayer will return false if the name doesn't already exist
- // this means we are free to rename our oldLayer
- if (!canvas.setCurrentLayer(newname)) {
- var batchCmd = new svgedit.history.BatchCommand("Rename Layer");
- // find the index of the layer
- for (i = 0; i < drawing.getNumLayers(); ++i) {
- if (drawing.all_layers[i][1] == oldLayer) {break;}
- }
- var oldname = drawing.getLayerName(i);
- drawing.all_layers[i][0] = svgedit.utilities.toXml(newname);
-
- // now change the underlying title element contents
- var len = oldLayer.childNodes.length;
- for (i = 0; i < len; ++i) {
- var child = oldLayer.childNodes.item(i);
- // found the <title> element, now append all the
- if (child && child.tagName == "title") {
- // wipe out old name
- while (child.firstChild) { child.removeChild(child.firstChild); }
- child.textContent = newname;
- batchCmd.addSubCommand(new svgedit.history.ChangeElementCommand(child, {"#text":oldname}));
- addCommandToHistory(batchCmd);
- call("changed", [oldLayer]);
- return true;
- }
- }
- }
- drawing.current_layer = oldLayer;
- }
- return false;
- };
- // Function: setCurrentLayerPosition
- // Changes the position of the current layer to the new value. If the new index is not valid,
- // this function does nothing and returns false, otherwise it returns true. This is an
- // undo-able action.
- //
- // Parameters:
- // newpos - The zero-based index of the new position of the layer. This should be between
- // 0 and (number of layers - 1)
- //
- // Returns:
- // true if the current layer position was changed, false otherwise.
- this.setCurrentLayerPosition = function(newpos) {
- var oldpos, drawing = getCurrentDrawing();
- if (drawing.current_layer && newpos >= 0 && newpos < drawing.getNumLayers()) {
- for (oldpos = 0; oldpos < drawing.getNumLayers(); ++oldpos) {
- if (drawing.all_layers[oldpos][1] == drawing.current_layer) {break;}
- }
- // some unknown error condition (current_layer not in all_layers)
- if (oldpos == drawing.getNumLayers()) { return false; }
-
- if (oldpos != newpos) {
- // if our new position is below us, we need to insert before the node after newpos
- var refLayer = null;
- var oldNextSibling = drawing.current_layer.nextSibling;
- if (newpos > oldpos ) {
- if (newpos < drawing.getNumLayers()-1) {
- refLayer = drawing.all_layers[newpos+1][1];
- }
- }
- // if our new position is above us, we need to insert before the node at newpos
- else {
- refLayer = drawing.all_layers[newpos][1];
- }
- svgcontent.insertBefore(drawing.current_layer, refLayer);
- addCommandToHistory(new svgedit.history.MoveElementCommand(drawing.current_layer, oldNextSibling, svgcontent));
-
- identifyLayers();
- canvas.setCurrentLayer(drawing.getLayerName(newpos));
-
- return true;
- }
- }
-
- return false;
- };
- // Function: setLayerVisibility
- // Sets the visibility of the layer. If the layer name is not valid, this function return
- // false, otherwise it returns true. This is an undo-able action.
- //
- // Parameters:
- // layername - the name of the layer to change the visibility
- // bVisible - true/false, whether the layer should be visible
- //
- // Returns:
- // true if the layer's visibility was set, false otherwise
- this.setLayerVisibility = function(layername, bVisible) {
- var drawing = getCurrentDrawing();
- var prevVisibility = drawing.getLayerVisibility(layername);
- var layer = drawing.setLayerVisibility(layername, bVisible);
- if (layer) {
- var oldDisplay = prevVisibility ? 'inline' : 'none';
- addCommandToHistory(new svgedit.history.ChangeElementCommand(layer, {'display':oldDisplay}, 'Layer Visibility'));
- } else {
- return false;
- }
-
- if (layer == drawing.getCurrentLayer()) {
- clearSelection();
- pathActions.clear();
- }
- // call("changed", [selected]);
- return true;
- };
- // Function: moveSelectedToLayer
- // Moves the selected elements to layername. If the name is not a valid layer name, then false
- // is returned. Otherwise it returns true. This is an undo-able action.
- //
- // Parameters:
- // layername - the name of the layer you want to which you want to move the selected elements
- //
- // Returns:
- // true if the selected elements were moved to the layer, false otherwise.
- this.moveSelectedToLayer = function(layername) {
- // find the layer
- var i;
- var layer = null;
- var drawing = getCurrentDrawing();
- for (i = 0; i < drawing.getNumLayers(); ++i) {
- if (drawing.getLayerName(i) == layername) {
- layer = drawing.all_layers[i][1];
- break;
- }
- }
- if (!layer) {return false;}
-
- var batchCmd = new svgedit.history.BatchCommand("Move Elements to Layer");
-
- // loop for each selected element and move it
- var selElems = selectedElements;
- i = selElems.length;
- while (i--) {
- var elem = selElems[i];
- if (!elem) {continue;}
- var oldNextSibling = elem.nextSibling;
- // TODO: this is pretty brittle!
- var oldLayer = elem.parentNode;
- layer.appendChild(elem);
- batchCmd.addSubCommand(new svgedit.history.MoveElementCommand(elem, oldNextSibling, oldLayer));
- }
-
- addCommandToHistory(batchCmd);
-
- return true;
- };
- this.mergeLayer = function(skipHistory) {
- var batchCmd = new svgedit.history.BatchCommand("Merge Layer");
- var drawing = getCurrentDrawing();
- var prev = $(drawing.current_layer).prev()[0];
- if (!prev) {return;}
- var childs = drawing.current_layer.childNodes;
- var len = childs.length;
- var layerNextSibling = drawing.current_layer.nextSibling;
- batchCmd.addSubCommand(new svgedit.history.RemoveElementCommand(drawing.current_layer, layerNextSibling, svgcontent));
- while (drawing.current_layer.firstChild) {
- var ch = drawing.current_layer.firstChild;
- if (ch.localName == 'title') {
- var chNextSibling = ch.nextSibling;
- batchCmd.addSubCommand(new svgedit.history.RemoveElementCommand(ch, chNextSibling, drawing.current_layer));
- drawing.current_layer.removeChild(ch);
- continue;
- }
- var oldNextSibling = ch.nextSibling;
- prev.appendChild(ch);
- batchCmd.addSubCommand(new svgedit.history.MoveElementCommand(ch, oldNextSibling, drawing.current_layer));
- }
-
- // Remove current layer
- svgcontent.removeChild(drawing.current_layer);
-
- if (!skipHistory) {
- clearSelection();
- identifyLayers();
- call("changed", [svgcontent]);
-
- addCommandToHistory(batchCmd);
- }
-
- drawing.current_layer = prev;
- return batchCmd;
- };
- this.mergeAllLayers = function() {
- var batchCmd = new svgedit.history.BatchCommand("Merge all Layers");
- var drawing = getCurrentDrawing();
- drawing.current_layer = drawing.all_layers[drawing.getNumLayers()-1][1];
- while ($(svgcontent).children('g').length > 1) {
- batchCmd.addSubCommand(canvas.mergeLayer(true));
- }
-
- clearSelection();
- identifyLayers();
- call("changed", [svgcontent]);
- addCommandToHistory(batchCmd);
- };
- // Function: leaveContext
- // Return from a group context to the regular kind, make any previously
- // disabled elements enabled again
- var leaveContext = this.leaveContext = function() {
- var i, len = disabled_elems.length;
- if (len) {
- for (i = 0; i < len; i++) {
- var elem = disabled_elems[i];
- var orig = elData(elem, 'orig_opac');
- if (orig !== 1) {
- elem.setAttribute('opacity', orig);
- } else {
- elem.removeAttribute('opacity');
- }
- elem.setAttribute('style', 'pointer-events: inherit');
- }
- disabled_elems = [];
- clearSelection(true);
- call("contextset", null);
- }
- current_group = null;
- };
- // Function: setContext
- // Set the current context (for in-group editing)
- var setContext = this.setContext = function(elem) {
- leaveContext();
- if (typeof elem === 'string') {
- elem = svgedit.utilities.getElem(elem);
- }
- // Edit inside this group
- current_group = elem;
-
- // Disable other elements
- $(elem).parentsUntil('#svgcontent').andSelf().siblings().each(function() {
- var opac = this.getAttribute('opacity') || 1;
- // Store the original's opacity
- elData(this, 'orig_opac', opac);
- this.setAttribute('opacity', opac * 0.33);
- this.setAttribute('style', 'pointer-events: none');
- disabled_elems.push(this);
- });
- clearSelection();
- call("contextset", current_group);
- };
- // Group: Document functions
- // Function: clear
- // Clears the current document. This is not an undoable action.
- this.clear = function() {
- pathActions.clear();
- clearSelection();
- // clear the svgcontent node
- canvas.clearSvgContentElement();
- // create new document
- canvas.current_drawing_ = new svgedit.draw.Drawing(svgcontent);
- // create empty first layer
- canvas.createLayer("Layer 1");
-
- // clear the undo stack
- canvas.undoMgr.resetUndoStack();
- // reset the selector manager
- selectorManager.initGroup();
- // reset the rubber band box
- rubberBox = selectorManager.getRubberBandBox();
- call("cleared");
- };
- // Function: linkControlPoints
- // Alias function
- this.linkControlPoints = pathActions.linkControlPoints;
- // Function: getContentElem
- // Returns the content DOM element
- this.getContentElem = function() { return svgcontent; };
- // Function: getRootElem
- // Returns the root DOM element
- this.getRootElem = function() { return svgroot; };
- // Function: getSelectedElems
- // Returns the array with selected DOM elements
- this.getSelectedElems = function() { return selectedElements; };
- // Function: getResolution
- // Returns the current dimensions and zoom level in an object
- var getResolution = this.getResolution = function() {
- // var vb = svgcontent.getAttribute("viewBox").split(' ');
- // return {'w':vb[2], 'h':vb[3], 'zoom': current_zoom};
-
- var width = svgcontent.getAttribute("width")/current_zoom;
- var height = svgcontent.getAttribute("height")/current_zoom;
-
- return {
- 'w': width,
- 'h': height,
- 'zoom': current_zoom
- };
- };
- // Function: getZoom
- // Returns the current zoom level
- this.getZoom = function(){return current_zoom;};
- // Function: getVersion
- // Returns a string which describes the revision number of SvgCanvas.
- this.getVersion = function() {
- return "svgcanvas.js ($Rev: 2705 $)";
- };
- // Function: setUiStrings
- // Update interface strings with given values
- //
- // Parameters:
- // strs - Object with strings (see uiStrings for examples)
- this.setUiStrings = function(strs) {
- $.extend(uiStrings, strs.notification);
- };
- // Function: setConfig
- // Update configuration options with given values
- //
- // Parameters:
- // opts - Object with options (see curConfig for examples)
- this.setConfig = function(opts) {
- $.extend(curConfig, opts);
- };
- // Function: getTitle
- // Returns the current group/SVG's title contents
- this.getTitle = function(elem) {
- var i;
- elem = elem || selectedElements[0];
- if (!elem) {return;}
- elem = $(elem).data('gsvg') || $(elem).data('symbol') || elem;
- var childs = elem.childNodes;
- for (i = 0; i < childs.length; i++) {
- if (childs[i].nodeName == 'title') {
- return childs[i].textContent;
- }
- }
- return '';
- };
- // Function: setGroupTitle
- // Sets the group/SVG's title content
- // TODO: Combine this with setDocumentTitle
- this.setGroupTitle = function(val) {
- var elem = selectedElements[0];
- elem = $(elem).data('gsvg') || elem;
-
- var ts = $(elem).children('title');
-
- var batchCmd = new svgedit.history.BatchCommand("Set Label");
-
- if (!val.length) {
- // Remove title element
- var tsNextSibling = ts.nextSibling;
- batchCmd.addSubCommand(new svgedit.history.RemoveElementCommand(ts[0], tsNextSibling, elem));
- ts.remove();
- } else if (ts.length) {
- // Change title contents
- var title = ts[0];
- batchCmd.addSubCommand(new svgedit.history.ChangeElementCommand(title, {'#text': title.textContent}));
- title.textContent = val;
- } else {
- // Add title element
- title = svgdoc.createElementNS(NS.SVG, "title");
- title.textContent = val;
- $(elem).prepend(title);
- batchCmd.addSubCommand(new svgedit.history.InsertElementCommand(title));
- }
- addCommandToHistory(batchCmd);
- };
- // Function: getDocumentTitle
- // Returns the current document title or an empty string if not found
- this.getDocumentTitle = function() {
- return canvas.getTitle(svgcontent);
- };
- // Function: setDocumentTitle
- // Adds/updates a title element for the document with the given name.
- // This is an undoable action
- //
- // Parameters:
- // newtitle - String with the new title
- this.setDocumentTitle = function(newtitle) {
- var i;
- var childs = svgcontent.childNodes, doc_title = false, old_title = '';
-
- var batchCmd = new svgedit.history.BatchCommand("Change Image Title");
-
- for (i = 0; i < childs.length; i++) {
- if (childs[i].nodeName == 'title') {
- doc_title = childs[i];
- old_title = doc_title.textContent;
- break;
- }
- }
- if (!doc_title) {
- doc_title = svgdoc.createElementNS(NS.SVG, "title");
- svgcontent.insertBefore(doc_title, svgcontent.firstChild);
- }
-
- if (newtitle.length) {
- doc_title.textContent = newtitle;
- } else {
- // No title given, so element is not necessary
- doc_title.parentNode.removeChild(doc_title);
- }
- batchCmd.addSubCommand(new svgedit.history.ChangeElementCommand(doc_title, {'#text': old_title}));
- addCommandToHistory(batchCmd);
- };
- // Function: getEditorNS
- // Returns the editor's namespace URL, optionally adds it to root element
- //
- // Parameters:
- // add - Boolean to indicate whether or not to add the namespace value
- this.getEditorNS = function(add) {
- if (add) {
- svgcontent.setAttribute('xmlns:se', NS.SE);
- }
- return NS.SE;
- };
- // Function: setResolution
- // Changes the document's dimensions to the given size
- //
- // Parameters:
- // x - Number with the width of the new dimensions in user units.
- // Can also be the string "fit" to indicate "fit to content"
- // y - Number with the height of the new dimensions in user units.
- //
- // Returns:
- // Boolean to indicate if resolution change was succesful.
- // It will fail on "fit to content" option with no content to fit to.
- this.setResolution = function(x, y) {
- var res = getResolution();
- var w = res.w, h = res.h;
- var batchCmd;
- if (x == 'fit') {
- // Get bounding box
- var bbox = getStrokedBBox();
-
- if (bbox) {
- batchCmd = new svgedit.history.BatchCommand("Fit Canvas to Content");
- var visEls = getVisibleElements();
- addToSelection(visEls);
- var dx = [], dy = [];
- $.each(visEls, function(i, item) {
- dx.push(bbox.x*-1);
- dy.push(bbox.y*-1);
- });
-
- var cmd = canvas.moveSelectedElements(dx, dy, true);
- batchCmd.addSubCommand(cmd);
- clearSelection();
-
- x = Math.round(bbox.width);
- y = Math.round(bbox.height);
- } else {
- return false;
- }
- }
- if (x != w || y != h) {
- var handle = svgroot.suspendRedraw(1000);
- if (!batchCmd) {
- batchCmd = new svgedit.history.BatchCommand("Change Image Dimensions");
- }
- x = svgedit.units.convertToNum('width', x);
- y = svgedit.units.convertToNum('height', y);
-
- svgcontent.setAttribute('width', x);
- svgcontent.setAttribute('height', y);
-
- this.contentW = x;
- this.contentH = y;
- batchCmd.addSubCommand(new svgedit.history.ChangeElementCommand(svgcontent, {"width":w, "height":h}));
- svgcontent.setAttribute("viewBox", [0, 0, x/current_zoom, y/current_zoom].join(' '));
- batchCmd.addSubCommand(new svgedit.history.ChangeElementCommand(svgcontent, {"viewBox": ["0 0", w, h].join(' ')}));
-
- addCommandToHistory(batchCmd);
- svgroot.unsuspendRedraw(handle);
- call("changed", [svgcontent]);
- }
- return true;
- };
- // Function: getOffset
- // Returns an object with x, y values indicating the svgcontent element's
- // position in the editor's canvas.
- this.getOffset = function() {
- return $(svgcontent).attr(['x', 'y']);
- };
- // Function: setBBoxZoom
- // Sets the zoom level on the canvas-side based on the given value
- //
- // Parameters:
- // val - Bounding box object to zoom to or string indicating zoom option
- // editor_w - Integer with the editor's workarea box's width
- // editor_h - Integer with the editor's workarea box's height
- this.setBBoxZoom = function(val, editor_w, editor_h) {
- var spacer = 0.85;
- var bb;
- var calcZoom = function(bb) {
- if (!bb) {return false;}
- var w_zoom = Math.round((editor_w / bb.width)*100 * spacer)/100;
- var h_zoom = Math.round((editor_h / bb.height)*100 * spacer)/100;
- var zoomlevel = Math.min(w_zoom, h_zoom);
- canvas.setZoom(zoomlevel);
- return {'zoom': zoomlevel, 'bbox': bb};
- };
-
- if (typeof val == 'object') {
- bb = val;
- if (bb.width == 0 || bb.height == 0) {
- var newzoom = bb.zoom ? bb.zoom : current_zoom * bb.factor;
- canvas.setZoom(newzoom);
- return {'zoom': current_zoom, 'bbox': bb};
- }
- return calcZoom(bb);
- }
- switch (val) {
- case 'selection':
- if (!selectedElements[0]) {return;}
- var sel_elems = $.map(selectedElements, function(n){ if (n) {return n;} });
- bb = getStrokedBBox(sel_elems);
- break;
- case 'canvas':
- var res = getResolution();
- spacer = 0.95;
- bb = {width:res.w, height:res.h , x:0, y:0};
- break;
- case 'content':
- bb = getStrokedBBox();
- break;
- case 'layer':
- bb = getStrokedBBox(getVisibleElements(getCurrentDrawing().getCurrentLayer()));
- break;
- default:
- return;
- }
- return calcZoom(bb);
- };
- // Function: setZoom
- // Sets the zoom to the given level
- //
- // Parameters:
- // zoomlevel - Float indicating the zoom level to change to
- this.setZoom = function(zoomlevel) {
- var res = getResolution();
- svgcontent.setAttribute("viewBox", "0 0 " + res.w/zoomlevel + " " + res.h/zoomlevel);
- current_zoom = zoomlevel;
- $.each(selectedElements, function(i, elem) {
- if (!elem) {return;}
- selectorManager.requestSelector(elem).resize();
- });
- pathActions.zoomChange();
- runExtensions("zoomChanged", zoomlevel);
- };
- // Function: getMode
- // Returns the current editor mode string
- this.getMode = function() {
- return current_mode;
- };
- // Function: setMode
- // Sets the editor's mode to the given string
- //
- // Parameters:
- // name - String with the new mode to change to
- this.setMode = function(name) {
- pathActions.clear(true);
- textActions.clear();
- cur_properties = (selectedElements[0] && selectedElements[0].nodeName == 'text') ? cur_text : cur_shape;
- current_mode = name;
- };
- // Group: Element Styling
- // Function: getColor
- // Returns the current fill/stroke option
- this.getColor = function(type) {
- return cur_properties[type];
- };
- // Function: setColor
- // Change the current stroke/fill color/gradient value
- //
- // Parameters:
- // type - String indicating fill or stroke
- // val - The value to set the stroke attribute to
- // preventUndo - Boolean indicating whether or not this should be and undoable option
- this.setColor = function(type, val, preventUndo) {
- cur_shape[type] = val;
- cur_properties[type + '_paint'] = {type:"solidColor"};
- var elems = [];
- function addNonG (e) {
- if (e.nodeName != "g") {
- elems.push(e);
- }
- }
- var i = selectedElements.length;
- while (i--) {
- var elem = selectedElements[i];
- if (elem) {
- if (elem.tagName == "g") {
- svgedit.utilities.walkTree(elem, addNonG);
- } else {
- if (type == 'fill') {
- if (elem.tagName != "polyline" && elem.tagName != "line") {
- elems.push(elem);
- }
- } else {
- elems.push(elem);
- }
- }
- }
- }
- if (elems.length > 0) {
- if (!preventUndo) {
- changeSelectedAttribute(type, val, elems);
- call("changed", elems);
- } else {
- changeSelectedAttributeNoUndo(type, val, elems);
- }
- }
- };
- // Function: setGradient
- // Apply the current gradient to selected element's fill or stroke
- //
- // Parameters
- // type - String indicating "fill" or "stroke" to apply to an element
- var setGradient = this.setGradient = function(type) {
- if (!cur_properties[type + '_paint'] || cur_properties[type + '_paint'].type == "solidColor") {return;}
- var grad = canvas[type + 'Grad'];
- // find out if there is a duplicate gradient already in the defs
- var duplicate_grad = findDuplicateGradient(grad);
- var defs = svgedit.utilities.findDefs();
- // no duplicate found, so import gradient into defs
- if (!duplicate_grad) {
- var orig_grad = grad;
- grad = defs.appendChild( svgdoc.importNode(grad, true) );
- // get next id and set it on the grad
- grad.id = getNextId();
- } else { // use existing gradient
- grad = duplicate_grad;
- }
- canvas.setColor(type, "url(#" + grad.id + ")");
- };
- // Function: findDuplicateGradient
- // Check if exact gradient already exists
- //
- // Parameters:
- // grad - The gradient DOM element to compare to others
- //
- // Returns:
- // The existing gradient if found, null if not
- var findDuplicateGradient = function(grad) {
- var defs = svgedit.utilities.findDefs();
- var existing_grads = $(defs).find("linearGradient, radialGradient");
- var i = existing_grads.length;
- var rad_attrs = ['r', 'cx', 'cy', 'fx', 'fy'];
- while (i--) {
- var og = existing_grads[i];
- if (grad.tagName == "linearGradient") {
- if (grad.getAttribute('x1') != og.getAttribute('x1') ||
- grad.getAttribute('y1') != og.getAttribute('y1') ||
- grad.getAttribute('x2') != og.getAttribute('x2') ||
- grad.getAttribute('y2') != og.getAttribute('y2'))
- {
- continue;
- }
- } else {
- var grad_attrs = $(grad).attr(rad_attrs);
- var og_attrs = $(og).attr(rad_attrs);
-
- var diff = false;
- $.each(rad_attrs, function(i, attr) {
- if (grad_attrs[attr] != og_attrs[attr]) {diff = true;}
- });
-
- if (diff) {continue;}
- }
-
- // else could be a duplicate, iterate through stops
- var stops = grad.getElementsByTagNameNS(NS.SVG, "stop");
- var ostops = og.getElementsByTagNameNS(NS.SVG, "stop");
- if (stops.length != ostops.length) {
- continue;
- }
- var j = stops.length;
- while (j--) {
- var stop = stops[j];
- var ostop = ostops[j];
- if (stop.getAttribute('offset') != ostop.getAttribute('offset') ||
- stop.getAttribute('stop-opacity') != ostop.getAttribute('stop-opacity') ||
- stop.getAttribute('stop-color') != ostop.getAttribute('stop-color'))
- {
- break;
- }
- }
- if (j == -1) {
- return og;
- }
- } // for each gradient in defs
- return null;
- };
- function reorientGrads(elem, m) {
- var i;
- var bb = svgedit.utilities.getBBox(elem);
- for (i = 0; i < 2; i++) {
- var type = i === 0 ? 'fill' : 'stroke';
- var attrVal = elem.getAttribute(type);
- if (attrVal && attrVal.indexOf('url(') === 0) {
- var grad = svgedit.utilities.getRefElem(attrVal);
- if (grad.tagName === 'linearGradient') {
- var x1 = grad.getAttribute('x1') || 0;
- var y1 = grad.getAttribute('y1') || 0;
- var x2 = grad.getAttribute('x2') || 1;
- var y2 = grad.getAttribute('y2') || 0;
-
- // Convert to USOU points
- x1 = (bb.width * x1) + bb.x;
- y1 = (bb.height * y1) + bb.y;
- x2 = (bb.width * x2) + bb.x;
- y2 = (bb.height * y2) + bb.y;
-
- // Transform those points
- var pt1 = svgedit.math.transformPoint(x1, y1, m);
- var pt2 = svgedit.math.transformPoint(x2, y2, m);
-
- // Convert back to BB points
- var g_coords = {};
-
- g_coords.x1 = (pt1.x - bb.x) / bb.width;
- g_coords.y1 = (pt1.y - bb.y) / bb.height;
- g_coords.x2 = (pt2.x - bb.x) / bb.width;
- g_coords.y2 = (pt2.y - bb.y) / bb.height;
-
- var newgrad = grad.cloneNode(true);
- $(newgrad).attr(g_coords);
-
- newgrad.id = getNextId();
- svgedit.utilities.findDefs().appendChild(newgrad);
- elem.setAttribute(type, 'url(#' + newgrad.id + ')');
- }
- }
- }
- }
- // Function: setPaint
- // Set a color/gradient to a fill/stroke
- //
- // Parameters:
- // type - String with "fill" or "stroke"
- // paint - The jGraduate paint object to apply
- this.setPaint = function(type, paint) {
- // make a copy
- var p = new $.jGraduate.Paint(paint);
- this.setPaintOpacity(type, p.alpha / 100, true);
- // now set the current paint object
- cur_properties[type + '_paint'] = p;
- switch (p.type) {
- case 'solidColor':
- this.setColor(type, p.solidColor != 'none' ? '#' + p.solidColor : 'none');
- break;
- case 'linearGradient':
- case 'radialGradient':
- canvas[type + 'Grad'] = p[p.type];
- setGradient(type);
- break;
- }
- };
- // alias
- this.setStrokePaint = function(paint) {
- this.setPaint('stroke', paint);
- };
- this.setFillPaint = function(paint) {
- this.setPaint('fill', paint);
- };
- // Function: getStrokeWidth
- // Returns the current stroke-width value
- this.getStrokeWidth = function() {
- return cur_properties.stroke_width;
- };
- // Function: setStrokeWidth
- // Sets the stroke width for the current selected elements
- // When attempting to set a line's width to 0, this changes it to 1 instead
- //
- // Parameters:
- // val - A Float indicating the new stroke width value
- this.setStrokeWidth = function(val) {
- if (val == 0 && ['line', 'path'].indexOf(current_mode) >= 0) {
- canvas.setStrokeWidth(1);
- return;
- }
- cur_properties.stroke_width = val;
- var elems = [];
- function addNonG (e) {
- if (e.nodeName != 'g') {
- elems.push(e);
- }
- }
- var i = selectedElements.length;
- while (i--) {
- var elem = selectedElements[i];
- if (elem) {
- if (elem.tagName == "g") {
- svgedit.utilities.walkTree(elem, addNonG);
- }
- else {
- elems.push(elem);
- }
- }
- }
- if (elems.length > 0) {
- changeSelectedAttribute("stroke-width", val, elems);
- call("changed", selectedElements);
- }
- };
- // Function: setStrokeAttr
- // Set the given stroke-related attribute the given value for selected elements
- //
- // Parameters:
- // attr - String with the attribute name
- // val - String or number with the attribute value
- this.setStrokeAttr = function(attr, val) {
- cur_shape[attr.replace('-', '_')] = val;
- var elems = [];
- function addNonG (e) {
- if (e.nodeName != 'g') {
- elems.push(e);
- }
- }
- var i = selectedElements.length;
- while (i--) {
- var elem = selectedElements[i];
- if (elem) {
- if (elem.tagName == "g") {
- svgedit.utilities.walkTree(elem, function(e){if (e.nodeName!="g") {elems.push(e);}});
- }
- else {
- elems.push(elem);
- }
- }
- }
- if (elems.length > 0) {
- changeSelectedAttribute(attr, val, elems);
- call("changed", selectedElements);
- }
- };
- // Function: getStyle
- // Returns current style options
- this.getStyle = function() {
- return cur_shape;
- };
- // Function: getOpacity
- // Returns the current opacity
- this.getOpacity = function() {
- return cur_shape.opacity;
- };
- // Function: setOpacity
- // Sets the given opacity to the current selected elements
- this.setOpacity = function(val) {
- cur_shape.opacity = val;
- changeSelectedAttribute("opacity", val);
- };
- // Function: getOpacity
- // Returns the current fill opacity
- this.getFillOpacity = function() {
- return cur_shape.fill_opacity;
- };
- // Function: getStrokeOpacity
- // Returns the current stroke opacity
- this.getStrokeOpacity = function() {
- return cur_shape.stroke_opacity;
- };
- // Function: setPaintOpacity
- // Sets the current fill/stroke opacity
- //
- // Parameters:
- // type - String with "fill" or "stroke"
- // val - Float with the new opacity value
- // preventUndo - Boolean indicating whether or not this should be an undoable action
- this.setPaintOpacity = function(type, val, preventUndo) {
- cur_shape[type + '_opacity'] = val;
- if (!preventUndo) {
- changeSelectedAttribute(type + "-opacity", val);
- }
- else {
- changeSelectedAttributeNoUndo(type + "-opacity", val);
- }
- };
- // Function: getPaintOpacity
- // Gets the current fill/stroke opacity
- //
- // Parameters:
- // type - String with "fill" or "stroke"
- this.getPaintOpacity = function(type) {
- return type === 'fill' ? this.getFillOpacity() : this.getStrokeOpacity();
- };
- // Function: getBlur
- // Gets the stdDeviation blur value of the given element
- //
- // Parameters:
- // elem - The element to check the blur value for
- this.getBlur = function(elem) {
- var val = 0;
- // var elem = selectedElements[0];
- if (elem) {
- var filter_url = elem.getAttribute('filter');
- if (filter_url) {
- var blur = svgedit.utilities.getElem(elem.id + '_blur');
- if (blur) {
- val = blur.firstChild.getAttribute('stdDeviation');
- }
- }
- }
- return val;
- };
- (function() {
- var cur_command = null;
- var filter = null;
- var filterHidden = false;
-
- // Function: setBlurNoUndo
- // Sets the stdDeviation blur value on the selected element without being undoable
- //
- // Parameters:
- // val - The new stdDeviation value
- canvas.setBlurNoUndo = function(val) {
- if (!filter) {
- canvas.setBlur(val);
- return;
- }
- if (val === 0) {
- // Don't change the StdDev, as that will hide the element.
- // Instead, just remove the value for "filter"
- changeSelectedAttributeNoUndo("filter", "");
- filterHidden = true;
- } else {
- var elem = selectedElements[0];
- if (filterHidden) {
- changeSelectedAttributeNoUndo("filter", 'url(#' + elem.id + '_blur)');
- }
- if (svgedit.browser.isWebkit()) {
- console.log('e', elem);
- elem.removeAttribute('filter');
- elem.setAttribute('filter', 'url(#' + elem.id + '_blur)');
- }
- changeSelectedAttributeNoUndo("stdDeviation", val, [filter.firstChild]);
- canvas.setBlurOffsets(filter, val);
- }
- };
-
- function finishChange() {
- var bCmd = canvas.undoMgr.finishUndoableChange();
- cur_command.addSubCommand(bCmd);
- addCommandToHistory(cur_command);
- cur_command = null;
- filter = null;
- }
- // Function: setBlurOffsets
- // Sets the x, y, with, height values of the filter element in order to
- // make the blur not be clipped. Removes them if not neeeded
- //
- // Parameters:
- // filter - The filter DOM element to update
- // stdDev - The standard deviation value on which to base the offset size
- canvas.setBlurOffsets = function(filter, stdDev) {
- if (stdDev > 3) {
- // TODO: Create algorithm here where size is based on expected blur
- svgedit.utilities.assignAttributes(filter, {
- x: '-50%',
- y: '-50%',
- width: '200%',
- height: '200%'
- }, 100);
- } else {
- // Removing these attributes hides text in Chrome (see Issue 579)
- if (!svgedit.browser.isWebkit()) {
- filter.removeAttribute('x');
- filter.removeAttribute('y');
- filter.removeAttribute('width');
- filter.removeAttribute('height');
- }
- }
- };
- // Function: setBlur
- // Adds/updates the blur filter to the selected element
- //
- // Parameters:
- // val - Float with the new stdDeviation blur value
- // complete - Boolean indicating whether or not the action should be completed (to add to the undo manager)
- canvas.setBlur = function(val, complete) {
- if (cur_command) {
- finishChange();
- return;
- }
-
- // Looks for associated blur, creates one if not found
- var elem = selectedElements[0];
- var elem_id = elem.id;
- filter = svgedit.utilities.getElem(elem_id + '_blur');
-
- val -= 0;
-
- var batchCmd = new svgedit.history.BatchCommand();
-
- // Blur found!
- if (filter) {
- if (val === 0) {
- filter = null;
- }
- } else {
- // Not found, so create
- var newblur = addSvgElementFromJson({ "element": "feGaussianBlur",
- "attr": {
- "in": 'SourceGraphic',
- "stdDeviation": val
- }
- });
-
- filter = addSvgElementFromJson({ "element": "filter",
- "attr": {
- "id": elem_id + '_blur'
- }
- });
-
- filter.appendChild(newblur);
- svgedit.utilities.findDefs().appendChild(filter);
-
- batchCmd.addSubCommand(new svgedit.history.InsertElementCommand(filter));
- }
- var changes = {filter: elem.getAttribute('filter')};
-
- if (val === 0) {
- elem.removeAttribute("filter");
- batchCmd.addSubCommand(new svgedit.history.ChangeElementCommand(elem, changes));
- return;
- }
- changeSelectedAttribute("filter", 'url(#' + elem_id + '_blur)');
- batchCmd.addSubCommand(new svgedit.history.ChangeElementCommand(elem, changes));
- canvas.setBlurOffsets(filter, val);
-
- cur_command = batchCmd;
- canvas.undoMgr.beginUndoableChange("stdDeviation", [filter?filter.firstChild:null]);
- if (complete) {
- canvas.setBlurNoUndo(val);
- finishChange();
- }
- };
- }());
- // Function: getBold
- // Check whether selected element is bold or not
- //
- // Returns:
- // Boolean indicating whether or not element is bold
- this.getBold = function() {
- // should only have one element selected
- var selected = selectedElements[0];
- if (selected != null && selected.tagName == "text" &&
- selectedElements[1] == null)
- {
- return (selected.getAttribute("font-weight") == "bold");
- }
- return false;
- };
- // Function: setBold
- // Make the selected element bold or normal
- //
- // Parameters:
- // b - Boolean indicating bold (true) or normal (false)
- this.setBold = function(b) {
- var selected = selectedElements[0];
- if (selected != null && selected.tagName == "text" &&
- selectedElements[1] == null)
- {
- changeSelectedAttribute("font-weight", b ? "bold" : "normal");
- }
- if (!selectedElements[0].textContent) {
- textActions.setCursor();
- }
- };
- // Function: getItalic
- // Check whether selected element is italic or not
- //
- // Returns:
- // Boolean indicating whether or not element is italic
- this.getItalic = function() {
- var selected = selectedElements[0];
- if (selected != null && selected.tagName == "text" &&
- selectedElements[1] == null)
- {
- return (selected.getAttribute("font-style") == "italic");
- }
- return false;
- };
- // Function: setItalic
- // Make the selected element italic or normal
- //
- // Parameters:
- // b - Boolean indicating italic (true) or normal (false)
- this.setItalic = function(i) {
- var selected = selectedElements[0];
- if (selected != null && selected.tagName == "text" &&
- selectedElements[1] == null)
- {
- changeSelectedAttribute("font-style", i ? "italic" : "normal");
- }
- if (!selectedElements[0].textContent) {
- textActions.setCursor();
- }
- };
- // Function: getFontFamily
- // Returns the current font family
- this.getFontFamily = function() {
- return cur_text.font_family;
- };
- // Function: setFontFamily
- // Set the new font family
- //
- // Parameters:
- // val - String with the new font family
- this.setFontFamily = function(val) {
- cur_text.font_family = val;
- changeSelectedAttribute("font-family", val);
- if (selectedElements[0] && !selectedElements[0].textContent) {
- textActions.setCursor();
- }
- };
- // Function: setFontColor
- // Set the new font color
- //
- // Parameters:
- // val - String with the new font color
- this.setFontColor = function(val) {
- cur_text.fill = val;
- changeSelectedAttribute("fill", val);
- };
- // Function: getFontColor
- // Returns the current font color
- this.getFontColor = function() {
- return cur_text.fill;
- };
- // Function: getFontSize
- // Returns the current font size
- this.getFontSize = function() {
- return cur_text.font_size;
- };
- // Function: setFontSize
- // Applies the given font size to the selected element
- //
- // Parameters:
- // val - Float with the new font size
- this.setFontSize = function(val) {
- cur_text.font_size = val;
- changeSelectedAttribute("font-size", val);
- if (!selectedElements[0].textContent) {
- textActions.setCursor();
- }
- };
- // Function: getText
- // Returns the current text (textContent) of the selected element
- this.getText = function() {
- var selected = selectedElements[0];
- if (selected == null) { return ""; }
- return selected.textContent;
- };
- // Function: setTextContent
- // Updates the text element with the given string
- //
- // Parameters:
- // val - String with the new text
- this.setTextContent = function(val) {
- changeSelectedAttribute("#text", val);
- textActions.init(val);
- textActions.setCursor();
- };
- // Function: setImageURL
- // Sets the new image URL for the selected image element. Updates its size if
- // a new URL is given
- //
- // Parameters:
- // val - String with the image URL/path
- this.setImageURL = function(val) {
- var elem = selectedElements[0];
- if (!elem) {return;}
-
- var attrs = $(elem).attr(['width', 'height']);
- var setsize = (!attrs.width || !attrs.height);
- var cur_href = getHref(elem);
-
- // Do nothing if no URL change or size change
- if (cur_href !== val) {
- setsize = true;
- } else if (!setsize) {return;}
- var batchCmd = new svgedit.history.BatchCommand("Change Image URL");
- setHref(elem, val);
- batchCmd.addSubCommand(new svgedit.history.ChangeElementCommand(elem, {
- "#href": cur_href
- }));
- if (setsize) {
- $(new Image()).load(function() {
- var changes = $(elem).attr(['width', 'height']);
-
- $(elem).attr({
- width: this.width,
- height: this.height
- });
-
- selectorManager.requestSelector(elem).resize();
-
- batchCmd.addSubCommand(new svgedit.history.ChangeElementCommand(elem, changes));
- addCommandToHistory(batchCmd);
- call("changed", [elem]);
- }).attr('src', val);
- } else {
- addCommandToHistory(batchCmd);
- }
- };
- // Function: setLinkURL
- // Sets the new link URL for the selected anchor element.
- //
- // Parameters:
- // val - String with the link URL/path
- this.setLinkURL = function(val) {
- var elem = selectedElements[0];
- if (!elem) {return;}
- if (elem.tagName !== 'a') {
- // See if parent is an anchor
- var parents_a = $(elem).parents('a');
- if (parents_a.length) {
- elem = parents_a[0];
- } else {
- return;
- }
- }
-
- var cur_href = getHref(elem);
-
- if (cur_href === val) {return;}
-
- var batchCmd = new svgedit.history.BatchCommand("Change Link URL");
- setHref(elem, val);
- batchCmd.addSubCommand(new svgedit.history.ChangeElementCommand(elem, {
- "#href": cur_href
- }));
- addCommandToHistory(batchCmd);
- };
- // Function: setRectRadius
- // Sets the rx & ry values to the selected rect element to change its corner radius
- //
- // Parameters:
- // val - The new radius
- this.setRectRadius = function(val) {
- var selected = selectedElements[0];
- if (selected != null && selected.tagName == "rect") {
- var r = selected.getAttribute("rx");
- if (r != val) {
- selected.setAttribute("rx", val);
- selected.setAttribute("ry", val);
- addCommandToHistory(new svgedit.history.ChangeElementCommand(selected, {"rx":r, "ry":r}, "Radius"));
- call("changed", [selected]);
- }
- }
- };
- // Function: makeHyperlink
- // Wraps the selected element(s) in an anchor element or converts group to one
- this.makeHyperlink = function(url) {
- canvas.groupSelectedElements('a', url);
-
- // TODO: If element is a single "g", convert to "a"
- // if (selectedElements.length > 1 && selectedElements[1]) {
- };
- // Function: removeHyperlink
- this.removeHyperlink = function() {
- canvas.ungroupSelectedElement();
- };
- // Group: Element manipulation
- // Function: setSegType
- // Sets the new segment type to the selected segment(s).
- //
- // Parameters:
- // new_type - Integer with the new segment type
- // See http://www.w3.org/TR/SVG/paths.html#InterfaceSVGPathSeg for list
- this.setSegType = function(new_type) {
- pathActions.setSegType(new_type);
- };
- // TODO(codedread): Remove the getBBox argument and split this function into two.
- // Function: convertToPath
- // Convert selected element to a path, or get the BBox of an element-as-path
- //
- // Parameters:
- // elem - The DOM element to be converted
- // getBBox - Boolean on whether or not to only return the path's BBox
- //
- // Returns:
- // If the getBBox flag is true, the resulting path's bounding box object.
- // Otherwise the resulting path element is returned.
- this.convertToPath = function(elem, getBBox) {
- if (elem == null) {
- var elems = selectedElements;
- $.each(selectedElements, function(i, elem) {
- if (elem) {canvas.convertToPath(elem);}
- });
- return;
- }
-
- if (!getBBox) {
- var batchCmd = new svgedit.history.BatchCommand("Convert element to Path");
- }
-
- var attrs = getBBox?{}:{
- "fill": cur_shape.fill,
- "fill-opacity": cur_shape.fill_opacity,
- "stroke": cur_shape.stroke,
- "stroke-width": cur_shape.stroke_width,
- "stroke-dasharray": cur_shape.stroke_dasharray,
- "stroke-linejoin": cur_shape.stroke_linejoin,
- "stroke-linecap": cur_shape.stroke_linecap,
- "stroke-opacity": cur_shape.stroke_opacity,
- "opacity": cur_shape.opacity,
- "visibility":"hidden"
- };
-
- // any attribute on the element not covered by the above
- // TODO: make this list global so that we can properly maintain it
- // TODO: what about @transform, @clip-rule, @fill-rule, etc?
- $.each(['marker-start', 'marker-end', 'marker-mid', 'filter', 'clip-path'], function() {
- if (elem.getAttribute(this)) {
- attrs[this] = elem.getAttribute(this);
- }
- });
-
- var path = addSvgElementFromJson({
- "element": "path",
- "attr": attrs
- });
-
- var eltrans = elem.getAttribute("transform");
- if (eltrans) {
- path.setAttribute("transform", eltrans);
- }
-
- var id = elem.id;
- var parent = elem.parentNode;
- if (elem.nextSibling) {
- parent.insertBefore(path, elem);
- } else {
- parent.appendChild(path);
- }
-
- var d = '';
-
- var joinSegs = function(segs) {
- $.each(segs, function(j, seg) {
- var i;
- var l = seg[0], pts = seg[1];
- d += l;
- for (i = 0; i < pts.length; i+=2) {
- d += (pts[i] +','+pts[i+1]) + ' ';
- }
- });
- };
- // Possibly the cubed root of 6, but 1.81 works best
- var num = 1.81;
- var a, rx;
- switch (elem.tagName) {
- case 'ellipse':
- case 'circle':
- a = $(elem).attr(['rx', 'ry', 'cx', 'cy']);
- var cx = a.cx, cy = a.cy;
- rx = a.rx;
- ry = a.ry;
- if (elem.tagName == 'circle') {
- rx = ry = $(elem).attr('r');
- }
-
- joinSegs([
- ['M',[(cx-rx),(cy)]],
- ['C',[(cx-rx),(cy-ry/num), (cx-rx/num),(cy-ry), (cx),(cy-ry)]],
- ['C',[(cx+rx/num),(cy-ry), (cx+rx),(cy-ry/num), (cx+rx),(cy)]],
- ['C',[(cx+rx),(cy+ry/num), (cx+rx/num),(cy+ry), (cx),(cy+ry)]],
- ['C',[(cx-rx/num),(cy+ry), (cx-rx),(cy+ry/num), (cx-rx),(cy)]],
- ['Z',[]]
- ]);
- break;
- case 'path':
- d = elem.getAttribute('d');
- break;
- case 'line':
- a = $(elem).attr(["x1", "y1", "x2", "y2"]);
- d = "M"+a.x1+","+a.y1+"L"+a.x2+","+a.y2;
- break;
- case 'polyline':
- case 'polygon':
- d = "M" + elem.getAttribute('points');
- break;
- case 'rect':
- var r = $(elem).attr(['rx', 'ry']);
- rx = r.rx;
- ry = r.ry;
- var b = elem.getBBox();
- var x = b.x, y = b.y, w = b.width, h = b.height;
- num = 4 - num; // Why? Because!
-
- if (!rx && !ry) {
- // Regular rect
- joinSegs([
- ['M',[x, y]],
- ['L',[x+w, y]],
- ['L',[x+w, y+h]],
- ['L',[x, y+h]],
- ['L',[x, y]],
- ['Z',[]]
- ]);
- } else {
- joinSegs([
- ['M',[x, y+ry]],
- ['C',[x, y+ry/num, x+rx/num, y, x+rx, y]],
- ['L',[x+w-rx, y]],
- ['C',[x+w-rx/num, y, x+w, y+ry/num, x+w, y+ry]],
- ['L',[x+w, y+h-ry]],
- ['C',[x+w, y+h-ry/num, x+w-rx/num, y+h, x+w-rx, y+h]],
- ['L',[x+rx, y+h]],
- ['C',[x+rx/num, y+h, x, y+h-ry/num, x, y+h-ry]],
- ['L',[x, y+ry]],
- ['Z',[]]
- ]);
- }
- break;
- default:
- path.parentNode.removeChild(path);
- break;
- }
-
- if (d) {
- path.setAttribute('d', d);
- }
-
- if (!getBBox) {
- // Replace the current element with the converted one
-
- // Reorient if it has a matrix
- if (eltrans) {
- var tlist = svgedit.transformlist.getTransformList(path);
- if (svgedit.math.hasMatrixTransform(tlist)) {
- pathActions.resetOrientation(path);
- }
- }
-
- var nextSibling = elem.nextSibling;
- batchCmd.addSubCommand(new svgedit.history.RemoveElementCommand(elem, nextSibling, parent));
- batchCmd.addSubCommand(new svgedit.history.InsertElementCommand(path));
- clearSelection();
- elem.parentNode.removeChild(elem);
- path.setAttribute('id', id);
- path.removeAttribute("visibility");
- addToSelection([path], true);
-
- addCommandToHistory(batchCmd);
-
- } else {
- // Get the correct BBox of the new path, then discard it
- pathActions.resetOrientation(path);
- var bb = false;
- try {
- bb = path.getBBox();
- } catch(e) {
- // Firefox fails
- }
- path.parentNode.removeChild(path);
- return bb;
- }
- };
- // Function: changeSelectedAttributeNoUndo
- // This function makes the changes to the elements. It does not add the change
- // to the history stack.
- //
- // Parameters:
- // attr - String with the attribute name
- // newValue - String or number with the new attribute value
- // elems - The DOM elements to apply the change to
- var changeSelectedAttributeNoUndo = function(attr, newValue, elems) {
- var handle = svgroot.suspendRedraw(1000);
- if (current_mode == 'pathedit') {
- // Editing node
- pathActions.moveNode(attr, newValue);
- }
- elems = elems || selectedElements;
- var i = elems.length;
- var no_xy_elems = ['g', 'polyline', 'path'];
- var good_g_attrs = ['transform', 'opacity', 'filter'];
-
- while (i--) {
- var elem = elems[i];
- if (elem == null) {continue;}
-
- // Set x,y vals on elements that don't have them
- if ((attr === 'x' || attr === 'y') && no_xy_elems.indexOf(elem.tagName) >= 0) {
- var bbox = getStrokedBBox([elem]);
- var diff_x = attr === 'x' ? newValue - bbox.x : 0;
- var diff_y = attr === 'y' ? newValue - bbox.y : 0;
- canvas.moveSelectedElements(diff_x*current_zoom, diff_y*current_zoom, true);
- continue;
- }
-
- // only allow the transform/opacity/filter attribute to change on <g> elements, slightly hacky
- // TODO: FIXME: This doesn't seem right. Where's the body of this if statement?
- if (elem.tagName === "g" && good_g_attrs.indexOf(attr) >= 0) {}
- var oldval = attr === "#text" ? elem.textContent : elem.getAttribute(attr);
- if (oldval == null) {oldval = "";}
- if (oldval !== String(newValue)) {
- if (attr == "#text") {
- var old_w = svgedit.utilities.getBBox(elem).width;
- elem.textContent = newValue;
-
- // FF bug occurs on on rotated elements
- if (/rotate/.test(elem.getAttribute('transform'))) {
- elem = ffClone(elem);
- }
-
- // Hoped to solve the issue of moving text with text-anchor="start",
- // but this doesn't actually fix it. Hopefully on the right track, though. -Fyrd
-
- // var box=getBBox(elem), left=box.x, top=box.y, width=box.width,
- // height=box.height, dx = width - old_w, dy=0;
- // var angle = svgedit.utilities.getRotationAngle(elem, true);
- // if (angle) {
- // var r = Math.sqrt( dx*dx + dy*dy );
- // var theta = Math.atan2(dy,dx) - angle;
- // dx = r * Math.cos(theta);
- // dy = r * Math.sin(theta);
- //
- // elem.setAttribute('x', elem.getAttribute('x')-dx);
- // elem.setAttribute('y', elem.getAttribute('y')-dy);
- // }
-
- } else if (attr == "#href") {
- setHref(elem, newValue);
- }
- else {elem.setAttribute(attr, newValue);}
- // Go into "select" mode for text changes
- // NOTE: Important that this happens AFTER elem.setAttribute() or else attributes like
- // font-size can get reset to their old value, ultimately by svgEditor.updateContextPanel(),
- // after calling textActions.toSelectMode() below
- if (current_mode === "textedit" && attr !== "#text" && elem.textContent.length) {
- textActions.toSelectMode(elem);
- }
- // if (i==0)
- // selectedBBoxes[0] = svgedit.utilities.getBBox(elem);
- // Use the Firefox ffClone hack for text elements with gradients or
- // where other text attributes are changed.
- if (svgedit.browser.isGecko() && elem.nodeName === 'text' && /rotate/.test(elem.getAttribute('transform'))) {
- if (String(newValue).indexOf('url') === 0 || (['font-size', 'font-family', 'x', 'y'].indexOf(attr) >= 0 && elem.textContent)) {
- elem = ffClone(elem);
- }
- }
- // Timeout needed for Opera & Firefox
- // codedread: it is now possible for this function to be called with elements
- // that are not in the selectedElements array, we need to only request a
- // selector if the element is in that array
- if (selectedElements.indexOf(elem) >= 0) {
- setTimeout(function() {
- // Due to element replacement, this element may no longer
- // be part of the DOM
- if (!elem.parentNode) {return;}
- selectorManager.requestSelector(elem).resize();
- }, 0);
- }
- // if this element was rotated, and we changed the position of this element
- // we need to update the rotational transform attribute
- var angle = svgedit.utilities.getRotationAngle(elem);
- if (angle != 0 && attr != "transform") {
- var tlist = svgedit.transformlist.getTransformList(elem);
- var n = tlist.numberOfItems;
- while (n--) {
- var xform = tlist.getItem(n);
- if (xform.type == 4) {
- // remove old rotate
- tlist.removeItem(n);
-
- var box = svgedit.utilities.getBBox(elem);
- var center = svgedit.math.transformPoint(box.x+box.width/2, box.y+box.height/2, svgedit.math.transformListToTransform(tlist).matrix);
- var cx = center.x,
- cy = center.y;
- var newrot = svgroot.createSVGTransform();
- newrot.setRotate(angle, cx, cy);
- tlist.insertItemBefore(newrot, n);
- break;
- }
- }
- }
- } // if oldValue != newValue
- } // for each elem
- svgroot.unsuspendRedraw(handle);
- };
- // Function: changeSelectedAttribute
- // Change the given/selected element and add the original value to the history stack
- // If you want to change all selectedElements, ignore the elems argument.
- // If you want to change only a subset of selectedElements, then send the
- // subset to this function in the elems argument.
- //
- // Parameters:
- // attr - String with the attribute name
- // newValue - String or number with the new attribute value
- // elems - The DOM elements to apply the change to
- var changeSelectedAttribute = this.changeSelectedAttribute = function(attr, val, elems) {
- elems = elems || selectedElements;
- canvas.undoMgr.beginUndoableChange(attr, elems);
- var i = elems.length;
- changeSelectedAttributeNoUndo(attr, val, elems);
- var batchCmd = canvas.undoMgr.finishUndoableChange();
- if (!batchCmd.isEmpty()) {
- addCommandToHistory(batchCmd);
- }
- };
- // Function: deleteSelectedElements
- // Removes all selected elements from the DOM and adds the change to the
- // history stack
- this.deleteSelectedElements = function() {
- var i;
- var batchCmd = new svgedit.history.BatchCommand("Delete Elements");
- var len = selectedElements.length;
- var selectedCopy = []; //selectedElements is being deleted
- for (i = 0; i < len; ++i) {
- var selected = selectedElements[i];
- if (selected == null) {break;}
- var parent = selected.parentNode;
- var t = selected;
-
- // this will unselect the element and remove the selectedOutline
- selectorManager.releaseSelector(t);
-
- // Remove the path if present.
- svgedit.path.removePath_(t.id);
-
- // Get the parent if it's a single-child anchor
- if (parent.tagName === 'a' && parent.childNodes.length === 1) {
- t = parent;
- parent = parent.parentNode;
- }
-
- var nextSibling = t.nextSibling;
- var elem = parent.removeChild(t);
- selectedCopy.push(selected); //for the copy
- selectedElements[i] = null;
- batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, parent));
- }
- if (!batchCmd.isEmpty()) {addCommandToHistory(batchCmd);}
- call("changed", selectedCopy);
- clearSelection();
- };
- // Function: cutSelectedElements
- // Removes all selected elements from the DOM and adds the change to the
- // history stack. Remembers removed elements on the clipboard
- // TODO: Combine similar code with deleteSelectedElements
- this.cutSelectedElements = function() {
- var i;
- var batchCmd = new svgedit.history.BatchCommand("Cut Elements");
- var len = selectedElements.length;
- var selectedCopy = []; //selectedElements is being deleted
- for (i = 0; i < len; ++i) {
- var selected = selectedElements[i];
- if (selected == null) {break;}
- var parent = selected.parentNode;
- var t = selected;
- // this will unselect the element and remove the selectedOutline
- selectorManager.releaseSelector(t);
- // Remove the path if present.
- svgedit.path.removePath_(t.id);
- var nextSibling = t.nextSibling;
- var elem = parent.removeChild(t);
- selectedCopy.push(selected); //for the copy
- selectedElements[i] = null;
- batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, parent));
- }
- if (!batchCmd.isEmpty()) {addCommandToHistory(batchCmd);}
- call("changed", selectedCopy);
- clearSelection();
-
- canvas.clipBoard = selectedCopy;
- };
- // Function: copySelectedElements
- // Remembers the current selected elements on the clipboard
- this.copySelectedElements = function() {
- canvas.clipBoard = $.merge([], selectedElements);
- };
- this.pasteElements = function(type, x, y) {
- var cb = canvas.clipBoard;
- var len = cb.length;
- if (!len) {return;}
-
- var pasted = [];
- var batchCmd = new svgedit.history.BatchCommand('Paste elements');
-
- // Move elements to lastClickPoint
- while (len--) {
- var elem = cb[len];
- if (!elem) {continue;}
- var copy = copyElem(elem);
- // See if elem with elem ID is in the DOM already
- if (!svgedit.utilities.getElem(elem.id)) {copy.id = elem.id;}
-
- pasted.push(copy);
- (current_group || getCurrentDrawing().getCurrentLayer()).appendChild(copy);
- batchCmd.addSubCommand(new svgedit.history.InsertElementCommand(copy));
- }
-
- selectOnly(pasted);
-
- if (type !== 'in_place') {
-
- var ctr_x, ctr_y;
-
- if (!type) {
- ctr_x = lastClickPoint.x;
- ctr_y = lastClickPoint.y;
- } else if (type === 'point') {
- ctr_x = x;
- ctr_y = y;
- }
-
- var bbox = getStrokedBBox(pasted);
- var cx = ctr_x - (bbox.x + bbox.width/2),
- cy = ctr_y - (bbox.y + bbox.height/2),
- dx = [],
- dy = [];
-
- $.each(pasted, function(i, item) {
- dx.push(cx);
- dy.push(cy);
- });
-
- var cmd = canvas.moveSelectedElements(dx, dy, false);
- batchCmd.addSubCommand(cmd);
- }
- addCommandToHistory(batchCmd);
- call("changed", pasted);
- };
- // Function: groupSelectedElements
- // Wraps all the selected elements in a group (g) element
- // Parameters:
- // type - type of element to group into, defaults to <g>
- this.groupSelectedElements = function(type, urlArg) {
- if (!type) {type = 'g';}
- var cmd_str = '';
-
- switch (type) {
- case "a":
- cmd_str = "Make hyperlink";
- var url = '';
- if (arguments.length > 1) {
- url = urlArg;
- }
- break;
- default:
- type = 'g';
- cmd_str = "Group Elements";
- break;
- }
-
- var batchCmd = new svgedit.history.BatchCommand(cmd_str);
-
- // create and insert the group element
- var g = addSvgElementFromJson({
- "element": type,
- "attr": {
- "id": getNextId()
- }
- });
- if (type === 'a') {
- setHref(g, url);
- }
- batchCmd.addSubCommand(new svgedit.history.InsertElementCommand(g));
-
- // now move all children into the group
- var i = selectedElements.length;
- while (i--) {
- var elem = selectedElements[i];
- if (elem == null) {continue;}
-
- if (elem.parentNode.tagName === 'a' && elem.parentNode.childNodes.length === 1) {
- elem = elem.parentNode;
- }
-
- var oldNextSibling = elem.nextSibling;
- var oldParent = elem.parentNode;
- g.appendChild(elem);
- batchCmd.addSubCommand(new svgedit.history.MoveElementCommand(elem, oldNextSibling, oldParent));
- }
- if (!batchCmd.isEmpty()) {addCommandToHistory(batchCmd);}
-
- // update selection
- selectOnly([g], true);
- };
- // Function: pushGroupProperties
- // Pushes all appropriate parent group properties down to its children, then
- // removes them from the group
- var pushGroupProperties = this.pushGroupProperties = function(g, undoable) {
- var children = g.childNodes;
- var len = children.length;
- var xform = g.getAttribute("transform");
- var glist = svgedit.transformlist.getTransformList(g);
- var m = svgedit.math.transformListToTransform(glist).matrix;
-
- var batchCmd = new svgedit.history.BatchCommand("Push group properties");
- // TODO: get all fill/stroke properties from the group that we are about to destroy
- // "fill", "fill-opacity", "fill-rule", "stroke", "stroke-dasharray", "stroke-dashoffset",
- // "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity",
- // "stroke-width"
- // and then for each child, if they do not have the attribute (or the value is 'inherit')
- // then set the child's attribute
-
- var i = 0;
- var gangle = svgedit.utilities.getRotationAngle(g);
-
- var gattrs = $(g).attr(['filter', 'opacity']);
- var gfilter, gblur, changes;
-
- for (i = 0; i < len; i++) {
- var elem = children[i];
-
- if (elem.nodeType !== 1) {continue;}
-
- if (gattrs.opacity !== null && gattrs.opacity !== 1) {
- var c_opac = elem.getAttribute('opacity') || 1;
- var new_opac = Math.round((elem.getAttribute('opacity') || 1) * gattrs.opacity * 100)/100;
- changeSelectedAttribute('opacity', new_opac, [elem]);
- }
- if (gattrs.filter) {
- var cblur = this.getBlur(elem);
- var orig_cblur = cblur;
- if (!gblur) {gblur = this.getBlur(g);}
- if (cblur) {
- // Is this formula correct?
- cblur = Number(gblur) + Number(cblur);
- } else if (cblur === 0) {
- cblur = gblur;
- }
-
- // If child has no current filter, get group's filter or clone it.
- if (!orig_cblur) {
- // Set group's filter to use first child's ID
- if (!gfilter) {
- gfilter = svgedit.utilities.getRefElem(gattrs.filter);
- } else {
- // Clone the group's filter
- gfilter = copyElem(gfilter);
- svgedit.utilities.findDefs().appendChild(gfilter);
- }
- } else {
- gfilter = svgedit.utilities.getRefElem(elem.getAttribute('filter'));
- }
- // Change this in future for different filters
- var suffix = (gfilter.firstChild.tagName === 'feGaussianBlur')?'blur':'filter';
- gfilter.id = elem.id + '_' + suffix;
- changeSelectedAttribute('filter', 'url(#' + gfilter.id + ')', [elem]);
-
- // Update blur value
- if (cblur) {
- changeSelectedAttribute('stdDeviation', cblur, [gfilter.firstChild]);
- canvas.setBlurOffsets(gfilter, cblur);
- }
- }
-
- var chtlist = svgedit.transformlist.getTransformList(elem);
- // Don't process gradient transforms
- if (~elem.tagName.indexOf('Gradient')) {chtlist = null;}
-
- // Hopefully not a problem to add this. Necessary for elements like <desc/>
- if (!chtlist) {continue;}
-
- // Apparently <defs> can get get a transformlist, but we don't want it to have one!
- if (elem.tagName === 'defs') {continue;}
-
- if (glist.numberOfItems) {
- // TODO: if the group's transform is just a rotate, we can always transfer the
- // rotate() down to the children (collapsing consecutive rotates and factoring
- // out any translates)
- if (gangle && glist.numberOfItems == 1) {
- // [Rg] [Rc] [Mc]
- // we want [Tr] [Rc2] [Mc] where:
- // - [Rc2] is at the child's current center but has the
- // sum of the group and child's rotation angles
- // - [Tr] is the equivalent translation that this child
- // undergoes if the group wasn't there
-
- // [Tr] = [Rg] [Rc] [Rc2_inv]
-
- // get group's rotation matrix (Rg)
- var rgm = glist.getItem(0).matrix;
-
- // get child's rotation matrix (Rc)
- var rcm = svgroot.createSVGMatrix();
- var cangle = svgedit.utilities.getRotationAngle(elem);
- if (cangle) {
- rcm = chtlist.getItem(0).matrix;
- }
-
- // get child's old center of rotation
- var cbox = svgedit.utilities.getBBox(elem);
- var ceqm = svgedit.math.transformListToTransform(chtlist).matrix;
- var coldc = svgedit.math.transformPoint(cbox.x+cbox.width/2, cbox.y+cbox.height/2, ceqm);
-
- // sum group and child's angles
- var sangle = gangle + cangle;
-
- // get child's rotation at the old center (Rc2_inv)
- var r2 = svgroot.createSVGTransform();
- r2.setRotate(sangle, coldc.x, coldc.y);
-
- // calculate equivalent translate
- var trm = svgedit.math.matrixMultiply(rgm, rcm, r2.matrix.inverse());
-
- // set up tlist
- if (cangle) {
- chtlist.removeItem(0);
- }
-
- if (sangle) {
- if (chtlist.numberOfItems) {
- chtlist.insertItemBefore(r2, 0);
- } else {
- chtlist.appendItem(r2);
- }
- }
- if (trm.e || trm.f) {
- var tr = svgroot.createSVGTransform();
- tr.setTranslate(trm.e, trm.f);
- if (chtlist.numberOfItems) {
- chtlist.insertItemBefore(tr, 0);
- } else {
- chtlist.appendItem(tr);
- }
- }
- } else { // more complicated than just a rotate
-
- // transfer the group's transform down to each child and then
- // call svgedit.recalculate.recalculateDimensions()
- var oldxform = elem.getAttribute("transform");
- changes = {};
- changes.transform = oldxform || '';
- var newxform = svgroot.createSVGTransform();
- // [ gm ] [ chm ] = [ chm ] [ gm' ]
- // [ gm' ] = [ chm_inv ] [ gm ] [ chm ]
- var chm = svgedit.math.transformListToTransform(chtlist).matrix,
- chm_inv = chm.inverse();
- var gm = svgedit.math.matrixMultiply( chm_inv, m, chm );
- newxform.setMatrix(gm);
- chtlist.appendItem(newxform);
- }
- var cmd = svgedit.recalculate.recalculateDimensions(elem);
- if (cmd) {batchCmd.addSubCommand(cmd);}
- }
- }
-
- // remove transform and make it undo-able
- if (xform) {
- changes = {};
- changes["transform"] = xform;
- g.setAttribute("transform", "");
- g.removeAttribute("transform");
- batchCmd.addSubCommand(new svgedit.history.ChangeElementCommand(g, changes));
- }
-
- if (undoable && !batchCmd.isEmpty()) {
- return batchCmd;
- }
- };
- // Function: ungroupSelectedElement
- // Unwraps all the elements in a selected group (g) element. This requires
- // significant recalculations to apply group's transforms, etc to its children
- this.ungroupSelectedElement = function() {
- var g = selectedElements[0];
- if ($(g).data('gsvg') || $(g).data('symbol')) {
- // Is svg, so actually convert to group
- convertToGroup(g);
- return;
- }
- if (g.tagName === 'use') {
- // Somehow doesn't have data set, so retrieve
- var symbol = svgedit.utilities.getElem(getHref(g).substr(1));
- $(g).data('symbol', symbol).data('ref', symbol);
- convertToGroup(g);
- return;
- }
- var parents_a = $(g).parents('a');
- if (parents_a.length) {
- g = parents_a[0];
- }
-
- // Look for parent "a"
- if (g.tagName === "g" || g.tagName === "a") {
-
- var batchCmd = new svgedit.history.BatchCommand("Ungroup Elements");
- var cmd = pushGroupProperties(g, true);
- if (cmd) {batchCmd.addSubCommand(cmd);}
-
- var parent = g.parentNode;
- var anchor = g.nextSibling;
- var children = new Array(g.childNodes.length);
-
- var i = 0;
-
- while (g.firstChild) {
- var elem = g.firstChild;
- var oldNextSibling = elem.nextSibling;
- var oldParent = elem.parentNode;
-
- // Remove child title elements
- if (elem.tagName === 'title') {
- var nextSibling = elem.nextSibling;
- batchCmd.addSubCommand(new svgedit.history.RemoveElementCommand(elem, nextSibling, oldParent));
- oldParent.removeChild(elem);
- continue;
- }
-
- children[i++] = elem = parent.insertBefore(elem, anchor);
- batchCmd.addSubCommand(new svgedit.history.MoveElementCommand(elem, oldNextSibling, oldParent));
- }
- // remove the group from the selection
- clearSelection();
-
- // delete the group element (but make undo-able)
- var gNextSibling = g.nextSibling;
- g = parent.removeChild(g);
- batchCmd.addSubCommand(new svgedit.history.RemoveElementCommand(g, gNextSibling, parent));
- if (!batchCmd.isEmpty()) {addCommandToHistory(batchCmd);}
-
- // update selection
- addToSelection(children);
- }
- };
- // Function: moveToTopSelectedElement
- // Repositions the selected element to the bottom in the DOM to appear on top of
- // other elements
- this.moveToTopSelectedElement = function() {
- var selected = selectedElements[0];
- if (selected != null) {
- var t = selected;
- var oldParent = t.parentNode;
- var oldNextSibling = t.nextSibling;
- t = t.parentNode.appendChild(t);
- // If the element actually moved position, add the command and fire the changed
- // event handler.
- if (oldNextSibling != t.nextSibling) {
- addCommandToHistory(new svgedit.history.MoveElementCommand(t, oldNextSibling, oldParent, "top"));
- call("changed", [t]);
- }
- }
- };
- // Function: moveToBottomSelectedElement
- // Repositions the selected element to the top in the DOM to appear under
- // other elements
- this.moveToBottomSelectedElement = function() {
- var selected = selectedElements[0];
- if (selected != null) {
- var t = selected;
- var oldParent = t.parentNode;
- var oldNextSibling = t.nextSibling;
- var firstChild = t.parentNode.firstChild;
- if (firstChild.tagName == 'title') {
- firstChild = firstChild.nextSibling;
- }
- // This can probably be removed, as the defs should not ever apppear
- // inside a layer group
- if (firstChild.tagName == 'defs') {
- firstChild = firstChild.nextSibling;
- }
- t = t.parentNode.insertBefore(t, firstChild);
- // If the element actually moved position, add the command and fire the changed
- // event handler.
- if (oldNextSibling != t.nextSibling) {
- addCommandToHistory(new svgedit.history.MoveElementCommand(t, oldNextSibling, oldParent, "bottom"));
- call("changed", [t]);
- }
- }
- };
- // Function: moveUpDownSelected
- // Moves the select element up or down the stack, based on the visibly
- // intersecting elements
- //
- // Parameters:
- // dir - String that's either 'Up' or 'Down'
- this.moveUpDownSelected = function(dir) {
- var selected = selectedElements[0];
- if (!selected) {return;}
-
- curBBoxes = [];
- var closest, found_cur;
- // jQuery sorts this list
- var list = $(getIntersectionList(getStrokedBBox([selected]))).toArray();
- if (dir == 'Down') {list.reverse();}
- $.each(list, function() {
- if (!found_cur) {
- if (this == selected) {
- found_cur = true;
- }
- return;
- }
- closest = this;
- return false;
- });
- if (!closest) {return;}
-
- var t = selected;
- var oldParent = t.parentNode;
- var oldNextSibling = t.nextSibling;
- $(closest)[dir == 'Down'?'before':'after'](t);
- // If the element actually moved position, add the command and fire the changed
- // event handler.
- if (oldNextSibling != t.nextSibling) {
- addCommandToHistory(new svgedit.history.MoveElementCommand(t, oldNextSibling, oldParent, "Move " + dir));
- call("changed", [t]);
- }
- };
- // Function: moveSelectedElements
- // Moves selected elements on the X/Y axis
- //
- // Parameters:
- // dx - Float with the distance to move on the x-axis
- // dy - Float with the distance to move on the y-axis
- // undoable - Boolean indicating whether or not the action should be undoable
- //
- // Returns:
- // Batch command for the move
- this.moveSelectedElements = function(dx, dy, undoable) {
- // if undoable is not sent, default to true
- // if single values, scale them to the zoom
- if (dx.constructor != Array) {
- dx /= current_zoom;
- dy /= current_zoom;
- }
- undoable = undoable || true;
- var batchCmd = new svgedit.history.BatchCommand("position");
- var i = selectedElements.length;
- while (i--) {
- var selected = selectedElements[i];
- if (selected != null) {
- // if (i==0)
- // selectedBBoxes[0] = svgedit.utilities.getBBox(selected);
-
- // var b = {};
- // for (var j in selectedBBoxes[i]) b[j] = selectedBBoxes[i][j];
- // selectedBBoxes[i] = b;
-
- var xform = svgroot.createSVGTransform();
- var tlist = svgedit.transformlist.getTransformList(selected);
-
- // dx and dy could be arrays
- if (dx.constructor == Array) {
- // if (i==0) {
- // selectedBBoxes[0].x += dx[0];
- // selectedBBoxes[0].y += dy[0];
- // }
- xform.setTranslate(dx[i], dy[i]);
- } else {
- // if (i==0) {
- // selectedBBoxes[0].x += dx;
- // selectedBBoxes[0].y += dy;
- // }
- xform.setTranslate(dx, dy);
- }
- if (tlist.numberOfItems) {
- tlist.insertItemBefore(xform, 0);
- } else {
- tlist.appendItem(xform);
- }
-
- var cmd = svgedit.recalculate.recalculateDimensions(selected);
- if (cmd) {
- batchCmd.addSubCommand(cmd);
- }
-
- selectorManager.requestSelector(selected).resize();
- }
- }
- if (!batchCmd.isEmpty()) {
- if (undoable) {
- addCommandToHistory(batchCmd);
- }
- call("changed", selectedElements);
- return batchCmd;
- }
- };
- // Function: cloneSelectedElements
- // Create deep DOM copies (clones) of all selected elements and move them slightly
- // from their originals
- this.cloneSelectedElements = function(x, y) {
- var i, elem;
- var batchCmd = new svgedit.history.BatchCommand("Clone Elements");
- // find all the elements selected (stop at first null)
- var len = selectedElements.length;
- function sortfunction(a, b){
- return ($(b).index() - $(a).index()); //causes an array to be sorted numerically and ascending
- }
- selectedElements.sort(sortfunction);
- for (i = 0; i < len; ++i) {
- elem = selectedElements[i];
- if (elem == null) {break;}
- }
- // use slice to quickly get the subset of elements we need
- var copiedElements = selectedElements.slice(0, i);
- this.clearSelection(true);
- // note that we loop in the reverse way because of the way elements are added
- // to the selectedElements array (top-first)
- i = copiedElements.length;
- while (i--) {
- // clone each element and replace it within copiedElements
- elem = copiedElements[i] = copyElem(copiedElements[i]);
- (current_group || getCurrentDrawing().getCurrentLayer()).appendChild(elem);
- batchCmd.addSubCommand(new svgedit.history.InsertElementCommand(elem));
- }
-
- if (!batchCmd.isEmpty()) {
- addToSelection(copiedElements.reverse()); // Need to reverse for correct selection-adding
- this.moveSelectedElements(x, y, false);
- addCommandToHistory(batchCmd);
- }
- };
- // Function: alignSelectedElements
- // Aligns selected elements
- //
- // Parameters:
- // type - String with single character indicating the alignment type
- // relative_to - String that must be one of the following:
- // "selected", "largest", "smallest", "page"
- this.alignSelectedElements = function(type, relative_to) {
- var i, elem;
- var bboxes = [], angles = [];
- var minx = Number.MAX_VALUE, maxx = Number.MIN_VALUE, miny = Number.MAX_VALUE, maxy = Number.MIN_VALUE;
- var curwidth = Number.MIN_VALUE, curheight = Number.MIN_VALUE;
- var len = selectedElements.length;
- if (!len) {return;}
- for (i = 0; i < len; ++i) {
- if (selectedElements[i] == null) {break;}
- elem = selectedElements[i];
- bboxes[i] = getStrokedBBox([elem]);
-
- // now bbox is axis-aligned and handles rotation
- switch (relative_to) {
- case 'smallest':
- if ( (type == 'l' || type == 'c' || type == 'r') && (curwidth == Number.MIN_VALUE || curwidth > bboxes[i].width) ||
- (type == 't' || type == 'm' || type == 'b') && (curheight == Number.MIN_VALUE || curheight > bboxes[i].height) ) {
- minx = bboxes[i].x;
- miny = bboxes[i].y;
- maxx = bboxes[i].x + bboxes[i].width;
- maxy = bboxes[i].y + bboxes[i].height;
- curwidth = bboxes[i].width;
- curheight = bboxes[i].height;
- }
- break;
- case 'largest':
- if ( (type == 'l' || type == 'c' || type == 'r') && (curwidth == Number.MIN_VALUE || curwidth < bboxes[i].width) ||
- (type == 't' || type == 'm' || type == 'b') && (curheight == Number.MIN_VALUE || curheight < bboxes[i].height) ) {
- minx = bboxes[i].x;
- miny = bboxes[i].y;
- maxx = bboxes[i].x + bboxes[i].width;
- maxy = bboxes[i].y + bboxes[i].height;
- curwidth = bboxes[i].width;
- curheight = bboxes[i].height;
- }
- break;
- default: // 'selected'
- if (bboxes[i].x < minx) {minx = bboxes[i].x;}
- if (bboxes[i].y < miny) {miny = bboxes[i].y;}
- if (bboxes[i].x + bboxes[i].width > maxx) {maxx = bboxes[i].x + bboxes[i].width;}
- if (bboxes[i].y + bboxes[i].height > maxy) {maxy = bboxes[i].y + bboxes[i].height;}
- break;
- }
- } // loop for each element to find the bbox and adjust min/max
- if (relative_to == 'page') {
- minx = 0;
- miny = 0;
- maxx = canvas.contentW;
- maxy = canvas.contentH;
- }
- var dx = new Array(len);
- var dy = new Array(len);
- for (i = 0; i < len; ++i) {
- if (selectedElements[i] == null) {break;}
- elem = selectedElements[i];
- var bbox = bboxes[i];
- dx[i] = 0;
- dy[i] = 0;
- switch (type) {
- case 'l': // left (horizontal)
- dx[i] = minx - bbox.x;
- break;
- case 'c': // center (horizontal)
- dx[i] = (minx+maxx)/2 - (bbox.x + bbox.width/2);
- break;
- case 'r': // right (horizontal)
- dx[i] = maxx - (bbox.x + bbox.width);
- break;
- case 't': // top (vertical)
- dy[i] = miny - bbox.y;
- break;
- case 'm': // middle (vertical)
- dy[i] = (miny+maxy)/2 - (bbox.y + bbox.height/2);
- break;
- case 'b': // bottom (vertical)
- dy[i] = maxy - (bbox.y + bbox.height);
- break;
- }
- }
- this.moveSelectedElements(dx, dy);
- };
- // Group: Additional editor tools
- this.contentW = getResolution().w;
- this.contentH = getResolution().h;
- // Function: updateCanvas
- // Updates the editor canvas width/height/position after a zoom has occurred
- //
- // Parameters:
- // w - Float with the new width
- // h - Float with the new height
- //
- // Returns:
- // Object with the following values:
- // * x - The canvas' new x coordinate
- // * y - The canvas' new y coordinate
- // * old_x - The canvas' old x coordinate
- // * old_y - The canvas' old y coordinate
- // * d_x - The x position difference
- // * d_y - The y position difference
- this.updateCanvas = function(w, h) {
- svgroot.setAttribute("width", w);
- svgroot.setAttribute("height", h);
- var bg = $('#canvasBackground')[0];
- var old_x = svgcontent.getAttribute('x');
- var old_y = svgcontent.getAttribute('y');
- var x = (w/2 - this.contentW*current_zoom/2);
- var y = (h/2 - this.contentH*current_zoom/2);
- svgedit.utilities.assignAttributes(svgcontent, {
- width: this.contentW*current_zoom,
- height: this.contentH*current_zoom,
- 'x': x,
- 'y': y,
- "viewBox" : "0 0 " + this.contentW + " " + this.contentH
- });
-
- svgedit.utilities.assignAttributes(bg, {
- width: svgcontent.getAttribute('width'),
- height: svgcontent.getAttribute('height'),
- x: x,
- y: y
- });
- var bg_img = svgedit.utilities.getElem('background_image');
- if (bg_img) {
- svgedit.utilities.assignAttributes(bg_img, {
- 'width': '100%',
- 'height': '100%'
- });
- }
-
- selectorManager.selectorParentGroup.setAttribute("transform", "translate(" + x + "," + y + ")");
- runExtensions("canvasUpdated",{new_x:x, new_y:y, old_x:old_x, old_y:old_y, d_x:x - old_x, d_y:y - old_y});
- return {x:x, y:y, old_x:old_x, old_y:old_y, d_x:x - old_x, d_y:y - old_y};
- };
- // Function: setBackground
- // Set the background of the editor (NOT the actual document)
- //
- // Parameters:
- // color - String with fill color to apply
- // url - URL or path to image to use
- this.setBackground = function(color, url) {
- var bg = svgedit.utilities.getElem('canvasBackground');
- var border = $(bg).find('rect')[0];
- var bg_img = svgedit.utilities.getElem('background_image');
- border.setAttribute('fill', color);
- if (url) {
- if (!bg_img) {
- bg_img = svgdoc.createElementNS(NS.SVG, "image");
- svgedit.utilities.assignAttributes(bg_img, {
- 'id': 'background_image',
- 'width': '100%',
- 'height': '100%',
- 'preserveAspectRatio': 'xMinYMin',
- 'style':'pointer-events:none'
- });
- }
- setHref(bg_img, url);
- bg.appendChild(bg_img);
- } else if (bg_img) {
- bg_img.parentNode.removeChild(bg_img);
- }
- };
- // Function: cycleElement
- // Select the next/previous element within the current layer
- //
- // Parameters:
- // next - Boolean where true = next and false = previous element
- this.cycleElement = function(next) {
- var num;
- var cur_elem = selectedElements[0];
- var elem = false;
- var all_elems = getVisibleElements(current_group || getCurrentDrawing().getCurrentLayer());
- if (!all_elems.length) {return;}
- if (cur_elem == null) {
- num = next?all_elems.length-1:0;
- elem = all_elems[num];
- } else {
- var i = all_elems.length;
- while (i--) {
- if (all_elems[i] == cur_elem) {
- num = next ? i - 1 : i + 1;
- if (num >= all_elems.length) {
- num = 0;
- } else if (num < 0) {
- num = all_elems.length-1;
- }
- elem = all_elems[num];
- break;
- }
- }
- }
- selectOnly([elem], true);
- call("selected", selectedElements);
- };
- this.clear();
- // DEPRECATED: getPrivateMethods
- // Since all methods are/should be public somehow, this function should be removed
- // Being able to access private methods publicly seems wrong somehow,
- // but currently appears to be the best way to allow testing and provide
- // access to them to plugins.
- this.getPrivateMethods = function() {
- var obj = {
- addCommandToHistory: addCommandToHistory,
- setGradient: setGradient,
- addSvgElementFromJson: addSvgElementFromJson,
- assignAttributes: assignAttributes,
- BatchCommand: BatchCommand,
- call: call,
- ChangeElementCommand: ChangeElementCommand,
- copyElem: copyElem,
- ffClone: ffClone,
- findDefs: findDefs,
- findDuplicateGradient: findDuplicateGradient,
- getElem: getElem,
- getId: getId,
- getIntersectionList: getIntersectionList,
- getMouseTarget: getMouseTarget,
- getNextId: getNextId,
- getPathBBox: getPathBBox,
- getUrlFromAttr: getUrlFromAttr,
- hasMatrixTransform: hasMatrixTransform,
- identifyLayers: identifyLayers,
- InsertElementCommand: InsertElementCommand,
- isIdentity: svgedit.math.isIdentity,
- logMatrix: logMatrix,
- matrixMultiply: matrixMultiply,
- MoveElementCommand: MoveElementCommand,
- preventClickDefault: preventClickDefault,
- recalculateAllSelectedDimensions: recalculateAllSelectedDimensions,
- recalculateDimensions: recalculateDimensions,
- remapElement: remapElement,
- RemoveElementCommand: RemoveElementCommand,
- removeUnusedDefElems: removeUnusedDefElems,
- round: round,
- runExtensions: runExtensions,
- sanitizeSvg: sanitizeSvg,
- SVGEditTransformList: svgedit.transformlist.SVGTransformList,
- toString: toString,
- transformBox: svgedit.math.transformBox,
- transformListToTransform: transformListToTransform,
- transformPoint: transformPoint,
- walkTree: svgedit.utilities.walkTree
- };
- return obj;
- };
- };
|