diff options
| author | Bobby <[email protected]> | 2013-06-13 01:41:58 -0500 |
|---|---|---|
| committer | Imtiyaz S. Momin <[email protected]> | 2013-06-13 01:41:58 -0500 |
| commit | 3a02e993ef59cfdde447e2bd95dca34142a776b1 (patch) | |
| tree | f55740990a700f28f08ef0269e850036f66f66e4 | |
| download | TShirtDesigner-3a02e993ef59cfdde447e2bd95dca34142a776b1.tar.xz TShirtDesigner-3a02e993ef59cfdde447e2bd95dca34142a776b1.zip | |
Committing local files
| -rw-r--r-- | .gitattributes | 22 | ||||
| -rw-r--r-- | .gitignore | 215 | ||||
| -rw-r--r-- | case.html | 271 | ||||
| -rw-r--r-- | css/bootstrap-responsive.css | 1058 | ||||
| -rw-r--r-- | css/bootstrap-responsive.min.css | 9 | ||||
| -rw-r--r-- | css/bootstrap.min.css | 854 | ||||
| -rw-r--r-- | css/jquery.miniColors.css | 125 | ||||
| -rw-r--r-- | css/jquery.simplecolorpicker.css | 95 | ||||
| -rw-r--r-- | img/Actions-format-text-bold-icon.png | bin | 0 -> 384 bytes | |||
| -rw-r--r-- | img/Actions-format-text-italic-icon.png | bin | 0 -> 351 bytes | |||
| -rw-r--r-- | img/Actions-format-text-strikethrough-icon.png | bin | 0 -> 393 bytes | |||
| -rw-r--r-- | img/Actions-format-text-underline-icon.png | bin | 0 -> 446 bytes | |||
| -rw-r--r-- | img/colors.png | bin | 0 -> 12973 bytes | |||
| -rw-r--r-- | img/crew_back.png | bin | 0 -> 245584 bytes | |||
| -rw-r--r-- | img/crew_front.png | bin | 0 -> 195349 bytes | |||
| -rw-r--r-- | img/delete.png | bin | 0 -> 1203 bytes | |||
| -rw-r--r-- | img/drag.png | bin | 0 -> 25830 bytes | |||
| -rw-r--r-- | img/font_bold.png | bin | 0 -> 235 bytes | |||
| -rw-r--r-- | img/font_italic.png | bin | 0 -> 276 bytes | |||
| -rw-r--r-- | img/font_strikethrough.png | bin | 0 -> 248 bytes | |||
| -rw-r--r-- | img/font_underline.png | bin | 0 -> 238 bytes | |||
| -rw-r--r-- | img/glyphicons-halflings-white.png | bin | 0 -> 8777 bytes | |||
| -rw-r--r-- | img/glyphicons-halflings.png | bin | 0 -> 12799 bytes | |||
| -rw-r--r-- | img/invisibleman.jpg | bin | 0 -> 2827 bytes | |||
| -rw-r--r-- | img/mens_hoodie_back.png | bin | 0 -> 310377 bytes | |||
| -rw-r--r-- | img/mens_hoodie_front.png | bin | 0 -> 305684 bytes | |||
| -rw-r--r-- | img/mens_longsleeve_back.png | bin | 0 -> 347046 bytes | |||
| -rw-r--r-- | img/mens_longsleeve_front.png | bin | 0 -> 313138 bytes | |||
| -rw-r--r-- | img/mens_tank_back.png | bin | 0 -> 209060 bytes | |||
| -rw-r--r-- | img/mens_tank_front.png | bin | 0 -> 99658 bytes | |||
| -rw-r--r-- | img/phones/GalaxyS3.png | bin | 0 -> 75833 bytes | |||
| -rw-r--r-- | img/phones/GalaxyS3A.png | bin | 0 -> 90785 bytes | |||
| -rw-r--r-- | img/phones/GalaxyS3Base.png | bin | 0 -> 108172 bytes | |||
| -rw-r--r-- | img/phones/GalaxyS3Mask.png | bin | 0 -> 9474 bytes | |||
| -rw-r--r-- | img/phones/designs/1.jpg | bin | 0 -> 110003 bytes | |||
| -rw-r--r-- | img/phones/designs/2.jpg | bin | 0 -> 74369 bytes | |||
| -rw-r--r-- | img/phones/designs/3.jpg | bin | 0 -> 153796 bytes | |||
| -rw-r--r-- | img/phones/designs/4.jpg | bin | 0 -> 178689 bytes | |||
| -rw-r--r-- | img/phones/designs/5.jpg | bin | 0 -> 139460 bytes | |||
| -rw-r--r-- | img/phones/designs/6.jpg | bin | 0 -> 106741 bytes | |||
| -rw-r--r-- | img/phones/designs/7.jpg | bin | 0 -> 157946 bytes | |||
| -rw-r--r-- | img/phones/designs/8.jpg | bin | 0 -> 117747 bytes | |||
| -rw-r--r-- | img/phones/iPhone3A.png | bin | 0 -> 67714 bytes | |||
| -rw-r--r-- | img/phones/iPhone3GHCOverlay.png | bin | 0 -> 232052 bytes | |||
| -rw-r--r-- | img/phones/iPhone4.png | bin | 0 -> 25391 bytes | |||
| -rw-r--r-- | img/phones/iPhone4A.png | bin | 0 -> 67089 bytes | |||
| -rw-r--r-- | img/phones/iPhone4HCOverlay.png | bin | 0 -> 134629 bytes | |||
| -rw-r--r-- | img/phones/iPhone5A.png | bin | 0 -> 303978 bytes | |||
| -rw-r--r-- | img/phones/iphone4Base.png | bin | 0 -> 45919 bytes | |||
| -rw-r--r-- | img/phones/iphone4Mask.png | bin | 0 -> 5879 bytes | |||
| -rw-r--r-- | img/phones/iphone5.png | bin | 0 -> 12970 bytes | |||
| -rw-r--r-- | img/phones/iphone5Base.png | bin | 0 -> 104854 bytes | |||
| -rw-r--r-- | img/phones/iphone5Mask.png | bin | 0 -> 7183 bytes | |||
| -rw-r--r-- | img/rotate.png | bin | 0 -> 1157 bytes | |||
| -rw-r--r-- | img/sample.jpg | bin | 0 -> 21010 bytes | |||
| -rw-r--r-- | img/scale.png | bin | 0 -> 1113 bytes | |||
| -rw-r--r-- | img/trigger.png | bin | 0 -> 706 bytes | |||
| -rw-r--r-- | img/womens_crew_back.png | bin | 0 -> 233558 bytes | |||
| -rw-r--r-- | img/womens_crew_front.png | bin | 0 -> 227604 bytes | |||
| -rw-r--r-- | index.html | 279 | ||||
| -rw-r--r-- | js/bootstrap.js | 2027 | ||||
| -rw-r--r-- | js/bootstrap.min.js | 6 | ||||
| -rw-r--r-- | js/caseEditor.js | 344 | ||||
| -rw-r--r-- | js/excanvas.js | 924 | ||||
| -rw-r--r-- | js/fabric.js | 13962 | ||||
| -rw-r--r-- | js/jquery.miniColors.min.js | 9 | ||||
| -rw-r--r-- | js/tshirtEditor.js | 355 | ||||
| -rw-r--r-- | template.html | 113 | ||||
| -rw-r--r-- | test.html | 15 |
69 files changed, 20683 insertions, 0 deletions
diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..412eeda --- /dev/null +++ b/.gitattributes @@ -0,0 +1,22 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp +*.sln merge=union +*.csproj merge=union +*.vbproj merge=union +*.fsproj merge=union +*.dbproj merge=union + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b9d6bd9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,215 @@ +################# +## Eclipse +################# + +*.pydevproject +.project +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.classpath +.settings/ +.loadpath + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath + + +################# +## Visual Studio +################# + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml +*.pubxml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +#packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + +############# +## Windows detritus +############# + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac crap +.DS_Store + + +############# +## Python +############# + +*.py[co] + +# Packages +*.egg +*.egg-info +dist/ +build/ +eggs/ +parts/ +var/ +sdist/ +develop-eggs/ +.installed.cfg + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox + +#Translations +*.mo + +#Mr Developer +.mr.developer.cfg diff --git a/case.html b/case.html new file mode 100644 index 0000000..2975a8d --- /dev/null +++ b/case.html @@ -0,0 +1,271 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <title>Title</title> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content=""> + <meta name="author" content=""> + + <!-- Le HTML5 shim, for IE6-8 support of HTML elements --> + <!--[if lt IE 9]> + <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script> + <![endif]--> + <!--[if IE]><script type="text/javascript" src="js/excanvas.js"></script><![endif]--> + <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> + <script type="text/javascript" src="js/fabric.js"></script> + <script type="text/javascript" src="js/caseEditor.js"></script> + <script type="text/javascript" src="js/jquery.miniColors.min.js"></script> + + <!-- Le styles --> + <link type="text/css" rel="stylesheet" href="css/jquery.miniColors.css" /> + <link href="css/bootstrap.min.css" rel="stylesheet"> + <link href="css/bootstrap-responsive.min.css" rel="stylesheet"> + <script type="text/javascript"> + </script> + <style type="text/css"> + .footer { + padding: 70px 0; + margin-top: 70px; + border-top: 1px solid #E5E5E5; + background-color: whiteSmoke; + } + body { + padding-top: 60px; +/* background-color: #000000; */ + } + .color-preview { + border: 1px solid #CCC; + margin: 2px; + zoom: 1; + vertical-align: top; + display: inline-block; + cursor: pointer; + overflow: hidden; + width: 20px; + height: 20px; + } + .rotate { + -webkit-transform:rotate(90deg); + -moz-transform:rotate(90deg); + -o-transform:rotate(90deg); + /* filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1.5); */ + -ms-transform:rotate(90deg); + } + .Arial{font-family:"Arial";} + .Helvetica{font-family:"Helvetica";} + .MyriadPro{font-family:"Myriad Pro";} + .Delicious{font-family:"Delicious";} + .Verdana{font-family:"Verdana";} + .Georgia{font-family:"Georgia";} + .Courier{font-family:"Courier";} + .ComicSansMS{font-family:"Comic Sans MS";} + .Impact{font-family:"Impact";} + .Monaco{font-family:"Monaco";} + .Optima{font-family:"Optima";} + .HoeflerText{font-family:"Hoefler Text";} + .Plaster{font-family:"Plaster";} + .Engagement{font-family:"Engagement";} + + </style> + </head> + + <body class="preview" data-spy="scroll" data-target=".subnav" data-offset="80"> + + <!-- Navbar + ================================================== --> + <div class="navbar navbar-fixed-top"> + <div class="navbar-inner"> + <div class="container"> + <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + </a> + <a class="brand" href="#">Custom Phone Case</a> + <div class="nav-collapse" id="main-menu"> + <ul class="nav" id="main-menu-left"> + <li><a href="index.html">T-Shirt</a></li> + </ul> + </div> + </div> + </div> + </div> + + <div class="container"> + <section id="typography"> + <div class="page-header"> + <h1>iPhone Case</h1> + </div> + + <!-- Headings & Paragraph Copy --> + <div class="row"> + <div class="span3"> + + <div class="tabbable"> <!-- Only required for left/right tabs --> + <ul class="nav nav-tabs"> + <li class="active"><a href="#tab1" data-toggle="tab">Case Options</a></li> + <li><a href="#tab2" data-toggle="tab">Design and Text</a></li> + </ul> + <div class="tab-content"> + <div class="tab-pane active" id="tab1"> + <div class="well"> +<!-- <h3>Tee Styles</h3>--> +<!-- <p>--> + <select id="phoneTypes"> + <option value="1" selected="selected">iPhone 5</option> + <option value="2">iPhone 4</option> + <option value="3">Samsumg III</option> + </select> +<!-- </p>--> + </div> + <div class="well"> + <ul class="nav"> + <li class="color-preview" title="White" style="background-color:#ffffff;"></li> + <li class="color-preview" title="Dark Heather" style="background-color:#616161;"></li> + <li class="color-preview" title="Gray" style="background-color:#f0f0f0;"></li> + <li class="color-preview" title="Charcoal" style="background-color:#5b5b5b;"></li> + <li class="color-preview" title="Black" style="background-color:#222222;"></li> + <li class="color-preview" title="Heather Orange" style="background-color:#fc8d74;"></li> + <li class="color-preview" title="Heather Dark Chocolate" style="background-color:#432d26;"></li> + <li class="color-preview" title="Salmon" style="background-color:#eead91;"></li> + <li class="color-preview" title="Chesnut" style="background-color:#806355;"></li> + <li class="color-preview" title="Dark Chocolate" style="background-color:#382d21;"></li> + <li class="color-preview" title="Citrus Yellow" style="background-color:#faef93;"></li> + <li class="color-preview" title="Avocado" style="background-color:#aeba5e;"></li> + <li class="color-preview" title="Kiwi" style="background-color:#8aa140;"></li> + <li class="color-preview" title="Irish Green" style="background-color:#1f6522;"></li> + <li class="color-preview" title="Scrub Green" style="background-color:#13afa2;"></li> + <li class="color-preview" title="Teal Ice" style="background-color:#b8d5d7;"></li> + <li class="color-preview" title="Heather Sapphire" style="background-color:#15aeda;"></li> + <li class="color-preview" title="Sky" style="background-color:#a5def8;"></li> + <li class="color-preview" title="Antique Sapphire" style="background-color:#0f77c0;"></li> + <li class="color-preview" title="Heather Navy" style="background-color:#3469b7;"></li> + <li class="color-preview" title="Cherry Red" style="background-color:#c50404;"></li> + </ul> + </div> + </div> + <div class="tab-pane" id="tab2"> + <div class="well" style="height:400px;"> + <div class="input-append"> + <input class="span2" id="text-string" type="text" placeholder="add text here..."><button id="add-text" class="btn" title="Add text"><i class="icon-share-alt"></i></button> + <hr> + <button class="btn btn-primary">Upload</button> + <hr> + </div> + <img style="cursor:pointer;width:90px;height:120px;" class="img-polaroid" src="img/phones/designs/1.jpg"> + <img style="cursor:pointer;width:90px;height:120px;" class="img-polaroid" src="img/phones/designs/7.jpg"> + <img style="cursor:pointer;width:90px;height:120px;" class="img-polaroid" src="img/phones/designs/8.jpg"> + <img style="cursor:pointer;width:90px;height:120px;" class="img-polaroid" src="img/phones/designs/2.jpg"> + <img style="cursor:pointer;width:90px;height:120px;" class="img-polaroid" src="img/phones/designs/3.jpg"> + <img style="cursor:pointer;width:90px;height:120px;" class="img-polaroid" src="img/phones/designs/4.jpg"> + <img style="cursor:pointer;width:90px;height:120px;" class="img-polaroid" src="img/phones/designs/5.jpg"> + <img style="cursor:pointer;width:90px;height:120px;" class="img-polaroid" src="img/phones/designs/6.jpg"> + </div> + </div> + </div> + </div> + </div> + <div class="span6"> + <div align="center" style="min-height: 32px;"> + <div class="clearfix"> + <div class="btn-group inline pull-left" id="texteditor" style="display:none;"> + <button id="font-family" class="btn dropdown-toggle" data-toggle="dropdown" title="Font Style"><i class="icon-font" style="width:19px;height:19px;"></i></button> + <ul class="dropdown-menu" role="menu" aria-labelledby="font-family-X"> + <li><a tabindex="-1" href="#" onclick="setFont('Arial');" class="Arial">Arial</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Helvetica');" class="Helvetica">Helvetica</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Myriad Pro');" class="MyriadPro">Myriad Pro</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Delicious');" class="Delicious">Delicious</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Verdana');" class="Verdana">Verdana</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Georgia');" class="Georgia">Georgia</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Courier');" class="Courier">Courier</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Comic Sans MS');" class="ComicSansMS">Comic Sans MS</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Impact');" class="Impact">Impact</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Monaco');" class="Monaco">Monaco</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Optima');" class="Optima">Optima</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Hoefler Text');" class="Hoefler Text">Hoefler Text</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Plaster');" class="Plaster">Plaster</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Engagement');" class="Engagement">Engagement</a></li> + </ul> + <button id="text-bold" class="btn" data-original-title="Bold"><img src="img/font_bold.png" height="" width=""></button> + <button id="text-italic" class="btn" data-original-title="Italic"><img src="img/font_italic.png" height="" width=""></button> + <button id="text-strike" class="btn" title="Strike" style=""><img src="img/font_strikethrough.png" height="" width=""></button> + <button id="text-underline" class="btn" title="Underline" style=""><img src="img/font_underline.png"></button> + <a class="btn" href="#" rel="tooltip" data-placement="top" data-original-title="Font Color"><input type="hidden" id="text-fontcolor" class="color-picker" size="7" value="#000000"></a> + <a class="btn" href="#" rel="tooltip" data-placement="top" data-original-title="Font Border Color"><input type="hidden" id="text-strokecolor" class="color-picker" size="7" value="#000000"></a> + <!--- Background <input type="hidden" id="text-bgcolor" class="color-picker" size="7" value="#ffffff"> ---> + </div> + <div class="pull-right" align="" id="imageeditor" style="display:none;"> + <div class="btn-group"> + <button class="btn" id="bring-to-front" title="Bring to Front"><i class="icon-fast-backward rotate" style="height:19px;"></i></button> + <button class="btn" id="send-to-back" title="Send to Back"><i class="icon-fast-forward rotate" style="height:19px;"></i></button> + <button id="remove-selected" class="btn" title="Delete selected item"><i class="icon-trash" style="height:19px;"></i></button> + </div> + </div> + </div> + </div> + <!-- EDITOR --> + <div style="background-color: #ffffff;position: relative; top:20px;height:560px;"> + <div id="phoneDiv" class="page" style="width: 282px; height: 590px; position: relative;left:25%; background-color: rgb(255, 255, 255);"> + <img id="phone" src="img/phones/iphone5A.png"></img> + <div id="drawingArea" style="position: absolute;top:70px;left:30px;z-index: 10;width: 230px; height:450px;"> + <canvas id="tcanvas" width=230 height="450" class="hover" style="-webkit-user-select: none;"></canvas> + </div> + </div> + </div> + <!-- /EDITOR --> + </div> + + <div class="span3"> + <div class="well"> + <h3>Total Prices</h3> + <p> + <table class="table"> + <tr> + <td>Phone Case</td> + <td align="right">$19.99</td> + </tr> + <tr> + <td>Head Phone</td> + <td align="right">$4.99</td> + </tr> + <tr> + <td><strong>Total</strong></td> + <td align="right"><strong>$24.99</strong></td> + </tr> + </table> + </p> + <button type="button" class="btn btn-large btn-block btn-success" name="addToTheBag" id="addToTheBag">Add to bag <i class="icon-briefcase icon-white"></i></button> + </div> + </div> + + </div> + + </section> + </div><!-- /container --> + +<!-- Footer ================================================== --> + <footer class="footer"> + <div class="container"> + <p class="pull-right"><a href="#">Back to top</a></p> + </div> + </footer> + <!-- Le javascript + ================================================== --> + <!-- Placed at the end of the document so the pages load faster --> + <script src="js/bootstrap.min.js"></script> + <script type="text/javascript"> + + var _gaq = _gaq || []; + _gaq.push(['_setAccount', 'UA-35639689-1']); + _gaq.push(['_trackPageview']); + + (function() { + var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; + ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; + var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); + })(); + +</script> + </body> +</html>
\ No newline at end of file diff --git a/css/bootstrap-responsive.css b/css/bootstrap-responsive.css new file mode 100644 index 0000000..9259d26 --- /dev/null +++ b/css/bootstrap-responsive.css @@ -0,0 +1,1058 @@ +/*! + * Bootstrap Responsive v2.1.1 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */ + +.clearfix { + *zoom: 1; +} + +.clearfix:before, +.clearfix:after { + display: table; + line-height: 0; + content: ""; +} + +.clearfix:after { + clear: both; +} + +.hide-text { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + +.input-block-level { + display: block; + width: 100%; + min-height: 30px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.hidden { + display: none; + visibility: hidden; +} + +.visible-phone { + display: none !important; +} + +.visible-tablet { + display: none !important; +} + +.hidden-desktop { + display: none !important; +} + +.visible-desktop { + display: inherit !important; +} + +@media (min-width: 768px) and (max-width: 979px) { + .hidden-desktop { + display: inherit !important; + } + .visible-desktop { + display: none !important ; + } + .visible-tablet { + display: inherit !important; + } + .hidden-tablet { + display: none !important; + } +} + +@media (max-width: 767px) { + .hidden-desktop { + display: inherit !important; + } + .visible-desktop { + display: none !important; + } + .visible-phone { + display: inherit !important; + } + .hidden-phone { + display: none !important; + } +} + +@media (min-width: 1200px) { + .row { + margin-left: -30px; + *zoom: 1; + } + .row:before, + .row:after { + display: table; + line-height: 0; + content: ""; + } + .row:after { + clear: both; + } + [class*="span"] { + float: left; + min-height: 1px; + margin-left: 30px; + } + .container, + .navbar-static-top .container, + .navbar-fixed-top .container, + .navbar-fixed-bottom .container { + width: 1170px; + } + .span12 { + width: 1170px; + } + .span11 { + width: 1070px; + } + .span10 { + width: 970px; + } + .span9 { + width: 870px; + } + .span8 { + width: 770px; + } + .span7 { + width: 670px; + } + .span6 { + width: 570px; + } + .span5 { + width: 470px; + } + .span4 { + width: 370px; + } + .span3 { + width: 270px; + } + .span2 { + width: 170px; + } + .span1 { + width: 70px; + } + .offset12 { + margin-left: 1230px; + } + .offset11 { + margin-left: 1130px; + } + .offset10 { + margin-left: 1030px; + } + .offset9 { + margin-left: 930px; + } + .offset8 { + margin-left: 830px; + } + .offset7 { + margin-left: 730px; + } + .offset6 { + margin-left: 630px; + } + .offset5 { + margin-left: 530px; + } + .offset4 { + margin-left: 430px; + } + .offset3 { + margin-left: 330px; + } + .offset2 { + margin-left: 230px; + } + .offset1 { + margin-left: 130px; + } + .row-fluid { + width: 100%; + *zoom: 1; + } + .row-fluid:before, + .row-fluid:after { + display: table; + line-height: 0; + content: ""; + } + .row-fluid:after { + clear: both; + } + .row-fluid [class*="span"] { + display: block; + float: left; + width: 100%; + min-height: 30px; + margin-left: 2.564102564102564%; + *margin-left: 2.5109110747408616%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + .row-fluid [class*="span"]:first-child { + margin-left: 0; + } + .row-fluid .span12 { + width: 100%; + *width: 99.94680851063829%; + } + .row-fluid .span11 { + width: 91.45299145299145%; + *width: 91.39979996362975%; + } + .row-fluid .span10 { + width: 82.90598290598291%; + *width: 82.8527914166212%; + } + .row-fluid .span9 { + width: 74.35897435897436%; + *width: 74.30578286961266%; + } + .row-fluid .span8 { + width: 65.81196581196582%; + *width: 65.75877432260411%; + } + .row-fluid .span7 { + width: 57.26495726495726%; + *width: 57.21176577559556%; + } + .row-fluid .span6 { + width: 48.717948717948715%; + *width: 48.664757228587014%; + } + .row-fluid .span5 { + width: 40.17094017094017%; + *width: 40.11774868157847%; + } + .row-fluid .span4 { + width: 31.623931623931625%; + *width: 31.570740134569924%; + } + .row-fluid .span3 { + width: 23.076923076923077%; + *width: 23.023731587561375%; + } + .row-fluid .span2 { + width: 14.52991452991453%; + *width: 14.476723040552828%; + } + .row-fluid .span1 { + width: 5.982905982905983%; + *width: 5.929714493544281%; + } + .row-fluid .offset12 { + margin-left: 105.12820512820512%; + *margin-left: 105.02182214948171%; + } + .row-fluid .offset12:first-child { + margin-left: 102.56410256410257%; + *margin-left: 102.45771958537915%; + } + .row-fluid .offset11 { + margin-left: 96.58119658119658%; + *margin-left: 96.47481360247316%; + } + .row-fluid .offset11:first-child { + margin-left: 94.01709401709402%; + *margin-left: 93.91071103837061%; + } + .row-fluid .offset10 { + margin-left: 88.03418803418803%; + *margin-left: 87.92780505546462%; + } + .row-fluid .offset10:first-child { + margin-left: 85.47008547008548%; + *margin-left: 85.36370249136206%; + } + .row-fluid .offset9 { + margin-left: 79.48717948717949%; + *margin-left: 79.38079650845607%; + } + .row-fluid .offset9:first-child { + margin-left: 76.92307692307693%; + *margin-left: 76.81669394435352%; + } + .row-fluid .offset8 { + margin-left: 70.94017094017094%; + *margin-left: 70.83378796144753%; + } + .row-fluid .offset8:first-child { + margin-left: 68.37606837606839%; + *margin-left: 68.26968539734497%; + } + .row-fluid .offset7 { + margin-left: 62.393162393162385%; + *margin-left: 62.28677941443899%; + } + .row-fluid .offset7:first-child { + margin-left: 59.82905982905982%; + *margin-left: 59.72267685033642%; + } + .row-fluid .offset6 { + margin-left: 53.84615384615384%; + *margin-left: 53.739770867430444%; + } + .row-fluid .offset6:first-child { + margin-left: 51.28205128205128%; + *margin-left: 51.175668303327875%; + } + .row-fluid .offset5 { + margin-left: 45.299145299145295%; + *margin-left: 45.1927623204219%; + } + .row-fluid .offset5:first-child { + margin-left: 42.73504273504273%; + *margin-left: 42.62865975631933%; + } + .row-fluid .offset4 { + margin-left: 36.75213675213675%; + *margin-left: 36.645753773413354%; + } + .row-fluid .offset4:first-child { + margin-left: 34.18803418803419%; + *margin-left: 34.081651209310785%; + } + .row-fluid .offset3 { + margin-left: 28.205128205128204%; + *margin-left: 28.0987452264048%; + } + .row-fluid .offset3:first-child { + margin-left: 25.641025641025642%; + *margin-left: 25.53464266230224%; + } + .row-fluid .offset2 { + margin-left: 19.65811965811966%; + *margin-left: 19.551736679396257%; + } + .row-fluid .offset2:first-child { + margin-left: 17.094017094017094%; + *margin-left: 16.98763411529369%; + } + .row-fluid .offset1 { + margin-left: 11.11111111111111%; + *margin-left: 11.004728132387708%; + } + .row-fluid .offset1:first-child { + margin-left: 8.547008547008547%; + *margin-left: 8.440625568285142%; + } + input, + textarea, + .uneditable-input { + margin-left: 0; + } + .controls-row [class*="span"] + [class*="span"] { + margin-left: 30px; + } + input.span12, + textarea.span12, + .uneditable-input.span12 { + width: 1156px; + } + input.span11, + textarea.span11, + .uneditable-input.span11 { + width: 1056px; + } + input.span10, + textarea.span10, + .uneditable-input.span10 { + width: 956px; + } + input.span9, + textarea.span9, + .uneditable-input.span9 { + width: 856px; + } + input.span8, + textarea.span8, + .uneditable-input.span8 { + width: 756px; + } + input.span7, + textarea.span7, + .uneditable-input.span7 { + width: 656px; + } + input.span6, + textarea.span6, + .uneditable-input.span6 { + width: 556px; + } + input.span5, + textarea.span5, + .uneditable-input.span5 { + width: 456px; + } + input.span4, + textarea.span4, + .uneditable-input.span4 { + width: 356px; + } + input.span3, + textarea.span3, + .uneditable-input.span3 { + width: 256px; + } + input.span2, + textarea.span2, + .uneditable-input.span2 { + width: 156px; + } + input.span1, + textarea.span1, + .uneditable-input.span1 { + width: 56px; + } + .thumbnails { + margin-left: -30px; + } + .thumbnails > li { + margin-left: 30px; + } + .row-fluid .thumbnails { + margin-left: 0; + } +} + +@media (min-width: 768px) and (max-width: 979px) { + .row { + margin-left: -20px; + *zoom: 1; + } + .row:before, + .row:after { + display: table; + line-height: 0; + content: ""; + } + .row:after { + clear: both; + } + [class*="span"] { + float: left; + min-height: 1px; + margin-left: 20px; + } + .container, + .navbar-static-top .container, + .navbar-fixed-top .container, + .navbar-fixed-bottom .container { + width: 724px; + } + .span12 { + width: 724px; + } + .span11 { + width: 662px; + } + .span10 { + width: 600px; + } + .span9 { + width: 538px; + } + .span8 { + width: 476px; + } + .span7 { + width: 414px; + } + .span6 { + width: 352px; + } + .span5 { + width: 290px; + } + .span4 { + width: 228px; + } + .span3 { + width: 166px; + } + .span2 { + width: 104px; + } + .span1 { + width: 42px; + } + .offset12 { + margin-left: 764px; + } + .offset11 { + margin-left: 702px; + } + .offset10 { + margin-left: 640px; + } + .offset9 { + margin-left: 578px; + } + .offset8 { + margin-left: 516px; + } + .offset7 { + margin-left: 454px; + } + .offset6 { + margin-left: 392px; + } + .offset5 { + margin-left: 330px; + } + .offset4 { + margin-left: 268px; + } + .offset3 { + margin-left: 206px; + } + .offset2 { + margin-left: 144px; + } + .offset1 { + margin-left: 82px; + } + .row-fluid { + width: 100%; + *zoom: 1; + } + .row-fluid:before, + .row-fluid:after { + display: table; + line-height: 0; + content: ""; + } + .row-fluid:after { + clear: both; + } + .row-fluid [class*="span"] { + display: block; + float: left; + width: 100%; + min-height: 30px; + margin-left: 2.7624309392265194%; + *margin-left: 2.709239449864817%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + .row-fluid [class*="span"]:first-child { + margin-left: 0; + } + .row-fluid .span12 { + width: 100%; + *width: 99.94680851063829%; + } + .row-fluid .span11 { + width: 91.43646408839778%; + *width: 91.38327259903608%; + } + .row-fluid .span10 { + width: 82.87292817679558%; + *width: 82.81973668743387%; + } + .row-fluid .span9 { + width: 74.30939226519337%; + *width: 74.25620077583166%; + } + .row-fluid .span8 { + width: 65.74585635359117%; + *width: 65.69266486422946%; + } + .row-fluid .span7 { + width: 57.18232044198895%; + *width: 57.12912895262725%; + } + .row-fluid .span6 { + width: 48.61878453038674%; + *width: 48.56559304102504%; + } + .row-fluid .span5 { + width: 40.05524861878453%; + *width: 40.00205712942283%; + } + .row-fluid .span4 { + width: 31.491712707182323%; + *width: 31.43852121782062%; + } + .row-fluid .span3 { + width: 22.92817679558011%; + *width: 22.87498530621841%; + } + .row-fluid .span2 { + width: 14.3646408839779%; + *width: 14.311449394616199%; + } + .row-fluid .span1 { + width: 5.801104972375691%; + *width: 5.747913483013988%; + } + .row-fluid .offset12 { + margin-left: 105.52486187845304%; + *margin-left: 105.41847889972962%; + } + .row-fluid .offset12:first-child { + margin-left: 102.76243093922652%; + *margin-left: 102.6560479605031%; + } + .row-fluid .offset11 { + margin-left: 96.96132596685082%; + *margin-left: 96.8549429881274%; + } + .row-fluid .offset11:first-child { + margin-left: 94.1988950276243%; + *margin-left: 94.09251204890089%; + } + .row-fluid .offset10 { + margin-left: 88.39779005524862%; + *margin-left: 88.2914070765252%; + } + .row-fluid .offset10:first-child { + margin-left: 85.6353591160221%; + *margin-left: 85.52897613729868%; + } + .row-fluid .offset9 { + margin-left: 79.8342541436464%; + *margin-left: 79.72787116492299%; + } + .row-fluid .offset9:first-child { + margin-left: 77.07182320441989%; + *margin-left: 76.96544022569647%; + } + .row-fluid .offset8 { + margin-left: 71.2707182320442%; + *margin-left: 71.16433525332079%; + } + .row-fluid .offset8:first-child { + margin-left: 68.50828729281768%; + *margin-left: 68.40190431409427%; + } + .row-fluid .offset7 { + margin-left: 62.70718232044199%; + *margin-left: 62.600799341718584%; + } + .row-fluid .offset7:first-child { + margin-left: 59.94475138121547%; + *margin-left: 59.838368402492065%; + } + .row-fluid .offset6 { + margin-left: 54.14364640883978%; + *margin-left: 54.037263430116376%; + } + .row-fluid .offset6:first-child { + margin-left: 51.38121546961326%; + *margin-left: 51.27483249088986%; + } + .row-fluid .offset5 { + margin-left: 45.58011049723757%; + *margin-left: 45.47372751851417%; + } + .row-fluid .offset5:first-child { + margin-left: 42.81767955801105%; + *margin-left: 42.71129657928765%; + } + .row-fluid .offset4 { + margin-left: 37.01657458563536%; + *margin-left: 36.91019160691196%; + } + .row-fluid .offset4:first-child { + margin-left: 34.25414364640884%; + *margin-left: 34.14776066768544%; + } + .row-fluid .offset3 { + margin-left: 28.45303867403315%; + *margin-left: 28.346655695309746%; + } + .row-fluid .offset3:first-child { + margin-left: 25.69060773480663%; + *margin-left: 25.584224756083227%; + } + .row-fluid .offset2 { + margin-left: 19.88950276243094%; + *margin-left: 19.783119783707537%; + } + .row-fluid .offset2:first-child { + margin-left: 17.12707182320442%; + *margin-left: 17.02068884448102%; + } + .row-fluid .offset1 { + margin-left: 11.32596685082873%; + *margin-left: 11.219583872105325%; + } + .row-fluid .offset1:first-child { + margin-left: 8.56353591160221%; + *margin-left: 8.457152932878806%; + } + input, + textarea, + .uneditable-input { + margin-left: 0; + } + .controls-row [class*="span"] + [class*="span"] { + margin-left: 20px; + } + input.span12, + textarea.span12, + .uneditable-input.span12 { + width: 710px; + } + input.span11, + textarea.span11, + .uneditable-input.span11 { + width: 648px; + } + input.span10, + textarea.span10, + .uneditable-input.span10 { + width: 586px; + } + input.span9, + textarea.span9, + .uneditable-input.span9 { + width: 524px; + } + input.span8, + textarea.span8, + .uneditable-input.span8 { + width: 462px; + } + input.span7, + textarea.span7, + .uneditable-input.span7 { + width: 400px; + } + input.span6, + textarea.span6, + .uneditable-input.span6 { + width: 338px; + } + input.span5, + textarea.span5, + .uneditable-input.span5 { + width: 276px; + } + input.span4, + textarea.span4, + .uneditable-input.span4 { + width: 214px; + } + input.span3, + textarea.span3, + .uneditable-input.span3 { + width: 152px; + } + input.span2, + textarea.span2, + .uneditable-input.span2 { + width: 90px; + } + input.span1, + textarea.span1, + .uneditable-input.span1 { + width: 28px; + } +} + +@media (max-width: 767px) { + body { + padding-right: 20px; + padding-left: 20px; + } + .navbar-fixed-top, + .navbar-fixed-bottom, + .navbar-static-top { + margin-right: -20px; + margin-left: -20px; + } + .container-fluid { + padding: 0; + } + .dl-horizontal dt { + float: none; + width: auto; + clear: none; + text-align: left; + } + .dl-horizontal dd { + margin-left: 0; + } + .container { + width: auto; + } + .row-fluid { + width: 100%; + } + .row, + .thumbnails { + margin-left: 0; + } + .thumbnails > li { + float: none; + margin-left: 0; + } + [class*="span"], + .row-fluid [class*="span"] { + display: block; + float: none; + width: 100%; + margin-left: 0; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + .span12, + .row-fluid .span12 { + width: 100%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + .input-large, + .input-xlarge, + .input-xxlarge, + input[class*="span"], + select[class*="span"], + textarea[class*="span"], + .uneditable-input { + display: block; + width: 100%; + min-height: 30px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + .input-prepend input, + .input-append input, + .input-prepend input[class*="span"], + .input-append input[class*="span"] { + display: inline-block; + width: auto; + } + .controls-row [class*="span"] + [class*="span"] { + margin-left: 0; + } + .modal { + position: fixed; + top: 20px; + right: 20px; + left: 20px; + width: auto; + margin: 0; + } + .modal.fade.in { + top: auto; + } +} + +@media (max-width: 480px) { + .nav-collapse { + -webkit-transform: translate3d(0, 0, 0); + } + .page-header h1 small { + display: block; + line-height: 20px; + } + input[type="checkbox"], + input[type="radio"] { + border: 1px solid #ccc; + } + .form-horizontal .control-label { + float: none; + width: auto; + padding-top: 0; + text-align: left; + } + .form-horizontal .controls { + margin-left: 0; + } + .form-horizontal .control-list { + padding-top: 0; + } + .form-horizontal .form-actions { + padding-right: 10px; + padding-left: 10px; + } + .modal { + top: 10px; + right: 10px; + left: 10px; + } + .modal-header .close { + padding: 10px; + margin: -10px; + } + .carousel-caption { + position: static; + } +} + +@media (max-width: 979px) { + body { + padding-top: 0; + } + .navbar-fixed-top, + .navbar-fixed-bottom { + position: static; + } + .navbar-fixed-top { + margin-bottom: 20px; + } + .navbar-fixed-bottom { + margin-top: 20px; + } + .navbar-fixed-top .navbar-inner, + .navbar-fixed-bottom .navbar-inner { + padding: 5px; + } + .navbar .container { + width: auto; + padding: 0; + } + .navbar .brand { + padding-right: 10px; + padding-left: 10px; + margin: 0 0 0 -5px; + } + .nav-collapse { + clear: both; + } + .nav-collapse .nav { + float: none; + margin: 0 0 10px; + } + .nav-collapse .nav > li { + float: none; + } + .nav-collapse .nav > li > a { + margin-bottom: 2px; + } + .nav-collapse .nav > .divider-vertical { + display: none; + } + .nav-collapse .nav .nav-header { + color: #777777; + text-shadow: none; + } + .nav-collapse .nav > li > a, + .nav-collapse .dropdown-menu a { + padding: 9px 15px; + font-weight: bold; + color: #777777; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + } + .nav-collapse .btn { + padding: 4px 10px 4px; + font-weight: normal; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + } + .nav-collapse .dropdown-menu li + li a { + margin-bottom: 2px; + } + .nav-collapse .nav > li > a:hover, + .nav-collapse .dropdown-menu a:hover { + background-color: #f2f2f2; + } + .navbar-inverse .nav-collapse .nav > li > a:hover, + .navbar-inverse .nav-collapse .dropdown-menu a:hover { + background-color: #111111; + } + .nav-collapse.in .btn-group { + padding: 0; + margin-top: 5px; + } + .nav-collapse .dropdown-menu { + position: static; + top: auto; + left: auto; + display: block; + float: none; + max-width: none; + padding: 0; + margin: 0 15px; + background-color: transparent; + border: none; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + } + .nav-collapse .dropdown-menu:before, + .nav-collapse .dropdown-menu:after { + display: none; + } + .nav-collapse .dropdown-menu .divider { + display: none; + } + .nav-collapse .nav > li > .dropdown-menu:before, + .nav-collapse .nav > li > .dropdown-menu:after { + display: none; + } + .nav-collapse .navbar-form, + .nav-collapse .navbar-search { + float: none; + padding: 10px 15px; + margin: 10px 0; + border-top: 1px solid #f2f2f2; + border-bottom: 1px solid #f2f2f2; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + } + .navbar-inverse .nav-collapse .navbar-form, + .navbar-inverse .nav-collapse .navbar-search { + border-top-color: #111111; + border-bottom-color: #111111; + } + .navbar .nav-collapse .nav.pull-right { + float: none; + margin-left: 0; + } + .nav-collapse, + .nav-collapse.collapse { + height: 0; + overflow: hidden; + } + .navbar .btn-navbar { + display: block; + } + .navbar-static .navbar-inner { + padding-right: 10px; + padding-left: 10px; + } +} + +@media (min-width: 980px) { + .nav-collapse.collapse { + height: auto !important; + overflow: visible !important; + } +} diff --git a/css/bootstrap-responsive.min.css b/css/bootstrap-responsive.min.css new file mode 100644 index 0000000..7b0158d --- /dev/null +++ b/css/bootstrap-responsive.min.css @@ -0,0 +1,9 @@ +/*! + * Bootstrap Responsive v2.1.1 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade.in{top:auto}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:hover{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:block;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} diff --git a/css/bootstrap.min.css b/css/bootstrap.min.css new file mode 100644 index 0000000..be1c0ca --- /dev/null +++ b/css/bootstrap.min.css @@ -0,0 +1,854 @@ +@import url(https://fonts.googleapis.com/css?family=Telex); +article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;} +audio,canvas,video{display:inline-block;*display:inline;*zoom:1;} +audio:not([controls]){display:none;} +html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;} +a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;} +a:hover,a:active{outline:0;} +sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline;} +sup{top:-0.5em;} +sub{bottom:-0.25em;} +img{max-width:100%;width:auto\9;height:auto;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic;} +#map_canvas img{max-width:none;} +button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle;} +button,input{*overflow:visible;line-height:normal;} +button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0;} +button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;} +input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield;} +input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none;} +textarea{overflow:auto;vertical-align:top;} +.clearfix{*zoom:1;}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0;} +.clearfix:after{clear:both;} +.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0;} +.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;} +body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#555555;background-color:#ffffff;} +a{color:#2fa4e7;text-decoration:none;} +a:hover{color:#157ab5;text-decoration:underline;} +.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} +.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.1);} +.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px;} +.row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:"";line-height:0;} +.row:after{clear:both;} +[class*="span"]{float:left;min-height:1px;margin-left:20px;} +.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;} +.span12{width:940px;} +.span11{width:860px;} +.span10{width:780px;} +.span9{width:700px;} +.span8{width:620px;} +.span7{width:540px;} +.span6{width:460px;} +.span5{width:380px;} +.span4{width:300px;} +.span3{width:220px;} +.span2{width:140px;} +.span1{width:60px;} +.offset12{margin-left:980px;} +.offset11{margin-left:900px;} +.offset10{margin-left:820px;} +.offset9{margin-left:740px;} +.offset8{margin-left:660px;} +.offset7{margin-left:580px;} +.offset6{margin-left:500px;} +.offset5{margin-left:420px;} +.offset4{margin-left:340px;} +.offset3{margin-left:260px;} +.offset2{margin-left:180px;} +.offset1{margin-left:100px;} +.row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0;} +.row-fluid:after{clear:both;} +.row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;} +.row-fluid [class*="span"]:first-child{margin-left:0;} +.row-fluid .span12{width:100%;*width:99.94680851063829%;} +.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%;} +.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%;} +.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%;} +.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%;} +.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%;} +.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%;} +.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%;} +.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%;} +.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%;} +.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%;} +.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%;} +.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%;} +.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%;} +.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%;} +.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%;} +.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%;} +.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%;} +.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%;} +.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%;} +.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%;} +.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%;} +.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%;} +.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%;} +.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%;} +.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%;} +.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%;} +.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%;} +.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%;} +.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%;} +.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%;} +.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%;} +.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%;} +.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%;} +.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%;} +.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%;} +[class*="span"].hide,.row-fluid [class*="span"].hide{display:none;} +[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right;} +.container{margin-right:auto;margin-left:auto;*zoom:1;}.container:before,.container:after{display:table;content:"";line-height:0;} +.container:after{clear:both;} +.container-fluid{padding-right:20px;padding-left:20px;*zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";line-height:0;} +.container-fluid:after{clear:both;} +p{margin:0 0 10px;} +.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px;} +small{font-size:85%;} +strong{font-weight:bold;} +em{font-style:italic;} +cite{font-style:normal;} +.muted{color:#999999;} +.text-warning{color:#126b9e;} +.text-error{color:#bd4247;} +.text-info{color:#817b58;} +.text-success{color:#669533;} +h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:'Telex',sans-serif;font-weight:bold;line-height:1;color:#317eac;text-rendering:optimizelegibility;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999999;} +h1{font-size:36px;line-height:40px;} +h2{font-size:30px;line-height:40px;} +h3{font-size:24px;line-height:40px;} +h4{font-size:18px;line-height:20px;} +h5{font-size:14px;line-height:20px;} +h6{font-size:12px;line-height:20px;} +h1 small{font-size:24px;} +h2 small{font-size:18px;} +h3 small{font-size:14px;} +h4 small{font-size:14px;} +.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #f5f5f5;} +ul,ol{padding:0;margin:0 0 10px 25px;} +ul ul,ul ol,ol ol,ol ul{margin-bottom:0;} +li{line-height:20px;} +ul.unstyled,ol.unstyled{margin-left:0;list-style:none;} +dl{margin-bottom:20px;} +dt,dd{line-height:20px;} +dt{font-weight:bold;} +dd{margin-left:10px;} +.dl-horizontal{*zoom:1;}.dl-horizontal:before,.dl-horizontal:after{display:table;content:"";line-height:0;} +.dl-horizontal:after{clear:both;} +.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;} +.dl-horizontal dd{margin-left:180px;} +hr{margin:20px 0;border:0;border-top:1px solid #f5f5f5;border-bottom:1px solid #ffffff;} +abbr[title]{cursor:help;border-bottom:1px dotted #999999;} +abbr.initialism{font-size:90%;text-transform:uppercase;} +blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #f5f5f5;}blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:25px;} +blockquote small{display:block;line-height:20px;color:#999999;}blockquote small:before{content:'\2014 \00A0';} +blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #f5f5f5;border-left:0;}blockquote.pull-right p,blockquote.pull-right small{text-align:right;} +blockquote.pull-right small:before{content:'';} +blockquote.pull-right small:after{content:'\00A0 \2014';} +q:before,q:after,blockquote:before,blockquote:after{content:"";} +address{display:block;margin-bottom:20px;font-style:normal;line-height:20px;} +code,pre{padding:0 3px 2px;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;font-size:12px;color:#333333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8;} +pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}pre.prettyprint{margin-bottom:20px;} +pre code{padding:0;color:inherit;background-color:transparent;border:0;} +.pre-scrollable{max-height:340px;overflow-y:scroll;} +form{margin:0 0 20px;} +fieldset{padding:0;margin:0;border:0;} +legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333333;border:0;border-bottom:1px solid #e5e5e5;}legend small{font-size:15px;color:#999999;} +label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px;} +input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;} +label{display:block;margin-bottom:5px;} +select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:9px;font-size:14px;line-height:20px;color:#555555;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +input,textarea,.uneditable-input{width:206px;} +textarea{height:auto;} +textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#ffffff;border:1px solid #cccccc;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-webkit-transition:border linear .2s, box-shadow linear .2s;-moz-transition:border linear .2s, box-shadow linear .2s;-o-transition:border linear .2s, box-shadow linear .2s;transition:border linear .2s, box-shadow linear .2s;}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82, 168, 236, 0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);} +input[type="radio"],input[type="checkbox"]{margin:4px 0 0;*margin-top:0;margin-top:1px \9;line-height:normal;cursor:pointer;} +input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto;} +select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px;} +select{width:220px;border:1px solid #cccccc;background-color:#ffffff;} +select[multiple],select[size]{height:auto;} +select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;} +.uneditable-input,.uneditable-textarea{color:#999999;background-color:#fcfcfc;border-color:#cccccc;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);cursor:not-allowed;} +.uneditable-input{overflow:hidden;white-space:nowrap;} +.uneditable-textarea{width:auto;height:auto;} +input:-moz-placeholder,textarea:-moz-placeholder{color:#999999;} +input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999999;} +input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999999;} +.radio,.checkbox{min-height:18px;padding-left:18px;} +.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-18px;} +.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px;} +.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle;} +.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px;} +.input-mini{width:60px;} +.input-small{width:90px;} +.input-medium{width:150px;} +.input-large{width:210px;} +.input-xlarge{width:270px;} +.input-xxlarge{width:530px;} +input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0;} +.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block;} +input,textarea,.uneditable-input{margin-left:0;} +.controls-row [class*="span"]+[class*="span"]{margin-left:20px;} +input.span12, textarea.span12, .uneditable-input.span12{width:926px;} +input.span11, textarea.span11, .uneditable-input.span11{width:846px;} +input.span10, textarea.span10, .uneditable-input.span10{width:766px;} +input.span9, textarea.span9, .uneditable-input.span9{width:686px;} +input.span8, textarea.span8, .uneditable-input.span8{width:606px;} +input.span7, textarea.span7, .uneditable-input.span7{width:526px;} +input.span6, textarea.span6, .uneditable-input.span6{width:446px;} +input.span5, textarea.span5, .uneditable-input.span5{width:366px;} +input.span4, textarea.span4, .uneditable-input.span4{width:286px;} +input.span3, textarea.span3, .uneditable-input.span3{width:206px;} +input.span2, textarea.span2, .uneditable-input.span2{width:126px;} +input.span1, textarea.span1, .uneditable-input.span1{width:46px;} +.controls-row{*zoom:1;}.controls-row:before,.controls-row:after{display:table;content:"";line-height:0;} +.controls-row:after{clear:both;} +.controls-row [class*="span"]{float:left;} +input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#f5f5f5;} +input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent;} +.control-group.warning>label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#126b9e;} +.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#126b9e;} +.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#126b9e;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#0d4c70;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #2fa4e7;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #2fa4e7;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #2fa4e7;} +.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#126b9e;background-color:#8accf2;border-color:#126b9e;} +.control-group.error>label,.control-group.error .help-block,.control-group.error .help-inline{color:#bd4247;} +.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#bd4247;} +.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#bd4247;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#983538;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d88e90;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d88e90;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d88e90;} +.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#bd4247;background-color:#eddbe3;border-color:#bd4247;} +.control-group.success>label,.control-group.success .help-block,.control-group.success .help-inline{color:#669533;} +.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#669533;} +.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#669533;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#4c6f26;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #99ca63;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #99ca63;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #99ca63;} +.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#669533;background-color:#ccddbb;border-color:#669533;} +.control-group.info>label,.control-group.info .help-block,.control-group.info .help-inline{color:#817b58;} +.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#817b58;} +.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#817b58;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#625e43;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #b1ad8d;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #b1ad8d;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #b1ad8d;} +.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#817b58;background-color:#a7dff1;border-color:#817b58;} +input:focus:required:invalid,textarea:focus:required:invalid,select:focus:required:invalid{color:#b94a48;border-color:#ee5f5b;}input:focus:required:invalid:focus,textarea:focus:required:invalid:focus,select:focus:required:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7;} +.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1;}.form-actions:before,.form-actions:after{display:table;content:"";line-height:0;} +.form-actions:after{clear:both;} +.help-block,.help-inline{color:#7b7b7b;} +.help-block{display:block;margin-bottom:10px;} +.help-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle;padding-left:5px;} +.input-append,.input-prepend{margin-bottom:5px;font-size:0;white-space:nowrap;}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;font-size:14px;vertical-align:top;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2;} +.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #ffffff;background-color:#f5f5f5;border:1px solid #ccc;} +.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.input-append .active,.input-prepend .active{background-color:#bede9c;border-color:#73a839;} +.input-prepend .add-on,.input-prepend .btn{margin-right:-1px;} +.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.input-append .add-on,.input-append .btn{margin-left:-1px;} +.input-append .add-on:last-child,.input-append .btn:last-child{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} +.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} +input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;} +.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px;} +.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0;} +.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0;} +.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px;} +.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;*zoom:1;margin-bottom:0;vertical-align:middle;} +.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none;} +.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block;} +.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0;} +.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle;} +.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0;} +.control-group{margin-bottom:10px;} +legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate;} +.form-horizontal .control-group{margin-bottom:20px;*zoom:1;}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;content:"";line-height:0;} +.form-horizontal .control-group:after{clear:both;} +.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right;} +.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0;}.form-horizontal .controls:first-child{*padding-left:180px;} +.form-horizontal .help-block{margin-bottom:0;} +.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block{margin-top:10px;} +.form-horizontal .form-actions{padding-left:180px;} +table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0;} +.table{width:100%;margin-bottom:20px;}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #dddddd;} +.table th{font-weight:bold;} +.table thead th{vertical-align:bottom;} +.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0;} +.table tbody+tbody{border-top:2px solid #dddddd;} +.table-condensed th,.table-condensed td{padding:4px 5px;} +.table-bordered{border:1px solid #dddddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.table-bordered th,.table-bordered td{border-left:1px solid #dddddd;} +.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0;} +.table-bordered thead:first-child tr:first-child th:first-child,.table-bordered tbody:first-child tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px;} +.table-bordered thead:first-child tr:first-child th:last-child,.table-bordered tbody:first-child tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px;} +.table-bordered thead:last-child tr:last-child th:first-child,.table-bordered tbody:last-child tr:last-child td:first-child,.table-bordered tfoot:last-child tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;} +.table-bordered thead:last-child tr:last-child th:last-child,.table-bordered tbody:last-child tr:last-child td:last-child,.table-bordered tfoot:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;} +.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px;} +.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topleft:4px;} +.table-striped tbody tr:nth-child(odd) td,.table-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9;} +.table-hover tbody tr:hover td,.table-hover tbody tr:hover th{background-color:#f5f5f5;} +table [class*=span],.row-fluid table [class*=span]{display:table-cell;float:none;margin-left:0;} +.table .span1{float:none;width:44px;margin-left:0;} +.table .span2{float:none;width:124px;margin-left:0;} +.table .span3{float:none;width:204px;margin-left:0;} +.table .span4{float:none;width:284px;margin-left:0;} +.table .span5{float:none;width:364px;margin-left:0;} +.table .span6{float:none;width:444px;margin-left:0;} +.table .span7{float:none;width:524px;margin-left:0;} +.table .span8{float:none;width:604px;margin-left:0;} +.table .span9{float:none;width:684px;margin-left:0;} +.table .span10{float:none;width:764px;margin-left:0;} +.table .span11{float:none;width:844px;margin-left:0;} +.table .span12{float:none;width:924px;margin-left:0;} +.table .span13{float:none;width:1004px;margin-left:0;} +.table .span14{float:none;width:1084px;margin-left:0;} +.table .span15{float:none;width:1164px;margin-left:0;} +.table .span16{float:none;width:1244px;margin-left:0;} +.table .span17{float:none;width:1324px;margin-left:0;} +.table .span18{float:none;width:1404px;margin-left:0;} +.table .span19{float:none;width:1484px;margin-left:0;} +.table .span20{float:none;width:1564px;margin-left:0;} +.table .span21{float:none;width:1644px;margin-left:0;} +.table .span22{float:none;width:1724px;margin-left:0;} +.table .span23{float:none;width:1804px;margin-left:0;} +.table .span24{float:none;width:1884px;margin-left:0;} +.table tbody tr.success td{background-color:#ccddbb;} +.table tbody tr.error td{background-color:#eddbe3;} +.table tbody tr.warning td{background-color:#8accf2;} +.table tbody tr.info td{background-color:#a7dff1;} +.table-hover tbody tr.success:hover td{background-color:#bfd4aa;} +.table-hover tbody tr.error:hover td{background-color:#e4cad6;} +.table-hover tbody tr.warning:hover td{background-color:#74c2ef;} +.table-hover tbody tr.info:hover td{background-color:#91d7ee;} +[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat;margin-top:1px;} +.icon-white,.nav-tabs>.active>a>[class^="icon-"],.nav-tabs>.active>a>[class*=" icon-"],.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png");} +.icon-glass{background-position:0 0;} +.icon-music{background-position:-24px 0;} +.icon-search{background-position:-48px 0;} +.icon-envelope{background-position:-72px 0;} +.icon-heart{background-position:-96px 0;} +.icon-star{background-position:-120px 0;} +.icon-star-empty{background-position:-144px 0;} +.icon-user{background-position:-168px 0;} +.icon-film{background-position:-192px 0;} +.icon-th-large{background-position:-216px 0;} +.icon-th{background-position:-240px 0;} +.icon-th-list{background-position:-264px 0;} +.icon-ok{background-position:-288px 0;} +.icon-remove{background-position:-312px 0;} +.icon-zoom-in{background-position:-336px 0;} +.icon-zoom-out{background-position:-360px 0;} +.icon-off{background-position:-384px 0;} +.icon-signal{background-position:-408px 0;} +.icon-cog{background-position:-432px 0;} +.icon-trash{background-position:-456px 0;} +.icon-home{background-position:0 -24px;} +.icon-file{background-position:-24px -24px;} +.icon-time{background-position:-48px -24px;} +.icon-road{background-position:-72px -24px;} +.icon-download-alt{background-position:-96px -24px;} +.icon-download{background-position:-120px -24px;} +.icon-upload{background-position:-144px -24px;} +.icon-inbox{background-position:-168px -24px;} +.icon-play-circle{background-position:-192px -24px;} +.icon-repeat{background-position:-216px -24px;} +.icon-refresh{background-position:-240px -24px;} +.icon-list-alt{background-position:-264px -24px;} +.icon-lock{background-position:-287px -24px;} +.icon-flag{background-position:-312px -24px;} +.icon-headphones{background-position:-336px -24px;} +.icon-volume-off{background-position:-360px -24px;} +.icon-volume-down{background-position:-384px -24px;} +.icon-volume-up{background-position:-408px -24px;} +.icon-qrcode{background-position:-432px -24px;} +.icon-barcode{background-position:-456px -24px;} +.icon-tag{background-position:0 -48px;} +.icon-tags{background-position:-25px -48px;} +.icon-book{background-position:-48px -48px;} +.icon-bookmark{background-position:-72px -48px;} +.icon-print{background-position:-96px -48px;} +.icon-camera{background-position:-120px -48px;} +.icon-font{background-position:-144px -48px;} +.icon-bold{background-position:-167px -48px;} +.icon-italic{background-position:-192px -48px;} +.icon-text-height{background-position:-216px -48px;} +.icon-text-width{background-position:-240px -48px;} +.icon-align-left{background-position:-264px -48px;} +.icon-align-center{background-position:-288px -48px;} +.icon-align-right{background-position:-312px -48px;} +.icon-align-justify{background-position:-336px -48px;} +.icon-list{background-position:-360px -48px;} +.icon-indent-left{background-position:-384px -48px;} +.icon-indent-right{background-position:-408px -48px;} +.icon-facetime-video{background-position:-432px -48px;} +.icon-picture{background-position:-456px -48px;} +.icon-pencil{background-position:0 -72px;} +.icon-map-marker{background-position:-24px -72px;} +.icon-adjust{background-position:-48px -72px;} +.icon-tint{background-position:-72px -72px;} +.icon-edit{background-position:-96px -72px;} +.icon-share{background-position:-120px -72px;} +.icon-check{background-position:-144px -72px;} +.icon-move{background-position:-168px -72px;} +.icon-step-backward{background-position:-192px -72px;} +.icon-fast-backward{background-position:-216px -72px;} +.icon-backward{background-position:-240px -72px;} +.icon-play{background-position:-264px -72px;} +.icon-pause{background-position:-288px -72px;} +.icon-stop{background-position:-312px -72px;} +.icon-forward{background-position:-336px -72px;} +.icon-fast-forward{background-position:-360px -72px;} +.icon-step-forward{background-position:-384px -72px;} +.icon-eject{background-position:-408px -72px;} +.icon-chevron-left{background-position:-432px -72px;} +.icon-chevron-right{background-position:-456px -72px;} +.icon-plus-sign{background-position:0 -96px;} +.icon-minus-sign{background-position:-24px -96px;} +.icon-remove-sign{background-position:-48px -96px;} +.icon-ok-sign{background-position:-72px -96px;} +.icon-question-sign{background-position:-96px -96px;} +.icon-info-sign{background-position:-120px -96px;} +.icon-screenshot{background-position:-144px -96px;} +.icon-remove-circle{background-position:-168px -96px;} +.icon-ok-circle{background-position:-192px -96px;} +.icon-ban-circle{background-position:-216px -96px;} +.icon-arrow-left{background-position:-240px -96px;} +.icon-arrow-right{background-position:-264px -96px;} +.icon-arrow-up{background-position:-289px -96px;} +.icon-arrow-down{background-position:-312px -96px;} +.icon-share-alt{background-position:-336px -96px;} +.icon-resize-full{background-position:-360px -96px;} +.icon-resize-small{background-position:-384px -96px;} +.icon-plus{background-position:-408px -96px;} +.icon-minus{background-position:-433px -96px;} +.icon-asterisk{background-position:-456px -96px;} +.icon-exclamation-sign{background-position:0 -120px;} +.icon-gift{background-position:-24px -120px;} +.icon-leaf{background-position:-48px -120px;} +.icon-fire{background-position:-72px -120px;} +.icon-eye-open{background-position:-96px -120px;} +.icon-eye-close{background-position:-120px -120px;} +.icon-warning-sign{background-position:-144px -120px;} +.icon-plane{background-position:-168px -120px;} +.icon-calendar{background-position:-192px -120px;} +.icon-random{background-position:-216px -120px;width:16px;} +.icon-comment{background-position:-240px -120px;} +.icon-magnet{background-position:-264px -120px;} +.icon-chevron-up{background-position:-288px -120px;} +.icon-chevron-down{background-position:-313px -119px;} +.icon-retweet{background-position:-336px -120px;} +.icon-shopping-cart{background-position:-360px -120px;} +.icon-folder-close{background-position:-384px -120px;} +.icon-folder-open{background-position:-408px -120px;width:16px;} +.icon-resize-vertical{background-position:-432px -119px;} +.icon-resize-horizontal{background-position:-456px -118px;} +.icon-hdd{background-position:0 -144px;} +.icon-bullhorn{background-position:-24px -144px;} +.icon-bell{background-position:-48px -144px;} +.icon-certificate{background-position:-72px -144px;} +.icon-thumbs-up{background-position:-96px -144px;} +.icon-thumbs-down{background-position:-120px -144px;} +.icon-hand-right{background-position:-144px -144px;} +.icon-hand-left{background-position:-168px -144px;} +.icon-hand-up{background-position:-192px -144px;} +.icon-hand-down{background-position:-216px -144px;} +.icon-circle-arrow-right{background-position:-240px -144px;} +.icon-circle-arrow-left{background-position:-264px -144px;} +.icon-circle-arrow-up{background-position:-288px -144px;} +.icon-circle-arrow-down{background-position:-312px -144px;} +.icon-globe{background-position:-336px -144px;} +.icon-wrench{background-position:-360px -144px;} +.icon-tasks{background-position:-384px -144px;} +.icon-filter{background-position:-408px -144px;} +.icon-briefcase{background-position:-432px -144px;} +.icon-fullscreen{background-position:-456px -144px;} +.dropup,.dropdown{position:relative;} +.dropdown-toggle{*margin-bottom:-3px;} +.dropdown-toggle:active,.open .dropdown-toggle{outline:0;} +.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000000;border-right:4px solid transparent;border-left:4px solid transparent;content:"";} +.dropdown .caret{margin-top:8px;margin-left:2px;} +.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#ffffff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;}.dropdown-menu.pull-right{right:0;left:auto;} +.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;} +.dropdown-menu a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333333;white-space:nowrap;} +.dropdown-menu li>a:hover,.dropdown-menu li>a:focus,.dropdown-submenu:hover>a{text-decoration:none;color:#ffffff;background-color:#2fa4e7;background-color:#27a0e5;background-image:-moz-linear-gradient(top, #2fa4e7, #1a99e2);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#2fa4e7), to(#1a99e2));background-image:-webkit-linear-gradient(top, #2fa4e7, #1a99e2);background-image:-o-linear-gradient(top, #2fa4e7, #1a99e2);background-image:linear-gradient(to bottom, #2fa4e7, #1a99e2);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff2fa4e7', endColorstr='#ff1a99e2', GradientType=0);} +.dropdown-menu .active>a,.dropdown-menu .active>a:hover{color:#ffffff;text-decoration:none;outline:0;background-color:#2fa4e7;background-color:#27a0e5;background-image:-moz-linear-gradient(top, #2fa4e7, #1a99e2);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#2fa4e7), to(#1a99e2));background-image:-webkit-linear-gradient(top, #2fa4e7, #1a99e2);background-image:-o-linear-gradient(top, #2fa4e7, #1a99e2);background-image:linear-gradient(to bottom, #2fa4e7, #1a99e2);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff2fa4e7', endColorstr='#ff1a99e2', GradientType=0);} +.dropdown-menu .disabled>a,.dropdown-menu .disabled>a:hover{color:#999999;} +.dropdown-menu .disabled>a:hover{text-decoration:none;background-color:transparent;cursor:default;} +.open{*z-index:1000;}.open >.dropdown-menu{display:block;} +.pull-right>.dropdown-menu{right:0;left:auto;} +.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000000;content:"";} +.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px;} +.dropdown-submenu{position:relative;} +.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px;} +.dropdown-submenu:hover>.dropdown-menu{display:block;} +.dropdown-submenu>a:after{display:block;content:" ";float:right;width:0;height:0;border-color:transparent;border-style:solid;border-width:5px 0 5px 5px;border-left-color:#cccccc;margin-top:5px;margin-right:-10px;} +.dropdown-submenu:hover>a:after{border-left-color:#ffffff;} +.dropdown .dropdown-menu .nav-header{padding-left:20px;padding-right:20px;} +.typeahead{margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);} +.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} +.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +.fade{opacity:0;-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;}.fade.in{opacity:1;} +.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height 0.35s ease;-moz-transition:height 0.35s ease;-o-transition:height 0.35s ease;transition:height 0.35s ease;}.collapse.in{height:auto;} +.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000000;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20);}.close:hover{color:#000000;text-decoration:none;cursor:pointer;opacity:0.4;filter:alpha(opacity=40);} +button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none;} +.btn{display:inline-block;*display:inline;*zoom:1;padding:4px 14px;margin-bottom:0;font-size:14px;line-height:20px;*line-height:20px;text-align:center;vertical-align:middle;cursor:pointer;color:#333333;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);background-color:#f5f5f5;background-image:-moz-linear-gradient(top, #ffffff, #e6e6e6);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(top, #ffffff, #e6e6e6);background-image:-o-linear-gradient(top, #ffffff, #e6e6e6);background-image:linear-gradient(to bottom, #ffffff, #e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#e6e6e6;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border:1px solid #cccccc;*border:0;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*margin-left:.3em;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);}.btn:hover,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333333;background-color:#e6e6e6;*background-color:#d9d9d9;} +.btn:active,.btn.active{background-color:#cccccc \9;} +.btn:first-child{*margin-left:0;} +.btn:hover{color:#333333;text-decoration:none;background-color:#e6e6e6;*background-color:#d9d9d9;background-position:0 -15px;-webkit-transition:background-position 0.1s linear;-moz-transition:background-position 0.1s linear;-o-transition:background-position 0.1s linear;transition:background-position 0.1s linear;} +.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;} +.btn.active,.btn:active{background-color:#e6e6e6;background-color:#d9d9d9 \9;background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);} +.btn.disabled,.btn[disabled]{cursor:default;background-color:#e6e6e6;background-image:none;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +.btn-large{padding:9px 14px;font-size:16px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;} +.btn-large [class^="icon-"]{margin-top:2px;} +.btn-small{padding:3px 9px;font-size:12px;line-height:18px;} +.btn-small [class^="icon-"]{margin-top:0;} +.btn-mini{padding:2px 6px;font-size:11px;line-height:17px;} +.btn-block{display:block;width:100%;padding-left:0;padding-right:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;} +.btn-block+.btn-block{margin-top:5px;} +input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%;} +.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255, 255, 255, 0.75);} +.btn{border-color:#c5c5c5;border-color:rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.25);} +.btn-primary{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#2f92e7;background-image:-moz-linear-gradient(top, #2fa4e7, #2f76e7);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#2fa4e7), to(#2f76e7));background-image:-webkit-linear-gradient(top, #2fa4e7, #2f76e7);background-image:-o-linear-gradient(top, #2fa4e7, #2f76e7);background-image:linear-gradient(to bottom, #2fa4e7, #2f76e7);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff2fa4e7', endColorstr='#ff2f76e7', GradientType=0);border-color:#2f76e7 #2f76e7 #1553b5;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#2f76e7;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#ffffff;background-color:#2f76e7;*background-color:#1a67e2;} +.btn-primary:active,.btn-primary.active{background-color:#175dcc \9;} +.btn-warning{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#dd5600;background-image:-moz-linear-gradient(top, #dd5600, #dd5600);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#dd5600), to(#dd5600));background-image:-webkit-linear-gradient(top, #dd5600, #dd5600);background-image:-o-linear-gradient(top, #dd5600, #dd5600);background-image:linear-gradient(to bottom, #dd5600, #dd5600);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdd5600', endColorstr='#ffdd5600', GradientType=0);border-color:#dd5600 #dd5600 #913800;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#dd5600;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#ffffff;background-color:#dd5600;*background-color:#c44c00;} +.btn-warning:active,.btn-warning.active{background-color:#aa4200 \9;} +.btn-danger{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#c32627;background-image:-moz-linear-gradient(top, #c71c22, #bd362f);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#c71c22), to(#bd362f));background-image:-webkit-linear-gradient(top, #c71c22, #bd362f);background-image:-o-linear-gradient(top, #c71c22, #bd362f);background-image:linear-gradient(to bottom, #c71c22, #bd362f);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffc71c22', endColorstr='#ffbd362f', GradientType=0);border-color:#bd362f #bd362f #802420;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#bd362f;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#ffffff;background-color:#bd362f;*background-color:#a9302a;} +.btn-danger:active,.btn-danger.active{background-color:#942a25 \9;} +.btn-success{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#65a643;background-image:-moz-linear-gradient(top, #73a839, #51a351);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#73a839), to(#51a351));background-image:-webkit-linear-gradient(top, #73a839, #51a351);background-image:-o-linear-gradient(top, #73a839, #51a351);background-image:linear-gradient(to bottom, #73a839, #51a351);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff73a839', endColorstr='#ff51a351', GradientType=0);border-color:#51a351 #51a351 #387038;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#51a351;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#ffffff;background-color:#51a351;*background-color:#499249;} +.btn-success:active,.btn-success.active{background-color:#408140 \9;} +.btn-info{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#6d76b3;background-image:-moz-linear-gradient(top, #9760b3, #2f96b4);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#9760b3), to(#2f96b4));background-image:-webkit-linear-gradient(top, #9760b3, #2f96b4);background-image:-o-linear-gradient(top, #9760b3, #2f96b4);background-image:linear-gradient(to bottom, #9760b3, #2f96b4);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff9760b3', endColorstr='#ff2f96b4', GradientType=0);border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#2f96b4;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#ffffff;background-color:#2f96b4;*background-color:#2a85a0;} +.btn-info:active,.btn-info.active{background-color:#24748c \9;} +.btn-inverse{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0f3253;background-image:-moz-linear-gradient(top, #033c73, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#033c73), to(#222222));background-image:-webkit-linear-gradient(top, #033c73, #222222);background-image:-o-linear-gradient(top, #033c73, #222222);background-image:linear-gradient(to bottom, #033c73, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff033c73', endColorstr='#ff222222', GradientType=0);border-color:#222222 #222222 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#222222;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#ffffff;background-color:#222222;*background-color:#151515;} +.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9;} +button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px;}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0;} +button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px;} +button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px;} +button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px;} +.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +.btn-link{border-color:transparent;cursor:pointer;color:#2fa4e7;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.btn-link:hover{color:#157ab5;text-decoration:underline;background-color:transparent;} +.btn-link[disabled]:hover{color:#333333;text-decoration:none;} +.btn-group{position:relative;font-size:0;vertical-align:middle;white-space:nowrap;*margin-left:.3em;}.btn-group:first-child{*margin-left:0;} +.btn-group+.btn-group{margin-left:5px;} +.btn-toolbar{font-size:0;margin-top:10px;margin-bottom:10px;}.btn-toolbar .btn-group{display:inline-block;*display:inline;*zoom:1;} +.btn-toolbar .btn+.btn,.btn-toolbar .btn-group+.btn,.btn-toolbar .btn+.btn-group{margin-left:5px;} +.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.btn-group>.btn+.btn{margin-left:-1px;} +.btn-group>.btn,.btn-group>.dropdown-menu{font-size:14px;} +.btn-group>.btn-mini{font-size:11px;} +.btn-group>.btn-small{font-size:12px;} +.btn-group>.btn-large{font-size:16px;} +.btn-group>.btn:first-child{margin-left:0;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;} +.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;} +.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px;} +.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px;} +.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2;} +.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0;} +.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);*padding-top:5px;*padding-bottom:5px;} +.btn-group>.btn-mini+.dropdown-toggle{padding-left:5px;padding-right:5px;*padding-top:2px;*padding-bottom:2px;} +.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px;} +.btn-group>.btn-large+.dropdown-toggle{padding-left:12px;padding-right:12px;*padding-top:7px;*padding-bottom:7px;} +.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);} +.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6;} +.btn-group.open .btn-primary.dropdown-toggle{background-color:#2f76e7;} +.btn-group.open .btn-warning.dropdown-toggle{background-color:#dd5600;} +.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f;} +.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351;} +.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4;} +.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222222;} +.btn .caret{margin-top:8px;margin-left:0;} +.btn-mini .caret,.btn-small .caret,.btn-large .caret{margin-top:6px;} +.btn-large .caret{border-left-width:5px;border-right-width:5px;border-top-width:5px;} +.dropup .btn-large .caret{border-bottom:5px solid #000000;border-top:0;} +.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;} +.btn-group-vertical{display:inline-block;*display:inline;*zoom:1;} +.btn-group-vertical .btn{display:block;float:none;width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.btn-group-vertical .btn+.btn{margin-left:0;margin-top:-1px;} +.btn-group-vertical .btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;} +.btn-group-vertical .btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;} +.btn-group-vertical .btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0;} +.btn-group-vertical .btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;} +.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);background-color:#8accf2;border:1px solid #7dd9f0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;color:#126b9e;} +.alert h4{margin:0;} +.alert .close{position:relative;top:-2px;right:-21px;line-height:20px;} +.alert-success{background-color:#ccddbb;border-color:#c6d4aa;color:#669533;} +.alert-danger,.alert-error{background-color:#eddbe3;border-color:#e8d1df;color:#bd4247;} +.alert-info{background-color:#a7dff1;border-color:#88e4ec;color:#817b58;} +.alert-block{padding-top:14px;padding-bottom:14px;} +.alert-block>p,.alert-block>ul{margin-bottom:0;} +.alert-block p+p{margin-top:5px;} +.nav{margin-left:0;margin-bottom:20px;list-style:none;} +.nav>li>a{display:block;} +.nav>li>a:hover{text-decoration:none;background-color:#f5f5f5;} +.nav>.pull-right{float:right;} +.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999999;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);text-transform:uppercase;} +.nav li+.nav-header{margin-top:9px;} +.nav-list{padding-left:15px;padding-right:15px;margin-bottom:0;} +.nav-list>li>a,.nav-list .nav-header{margin-left:-15px;margin-right:-15px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);} +.nav-list>li>a{padding:3px 15px;} +.nav-list>.active>a,.nav-list>.active>a:hover{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.2);background-color:#2fa4e7;} +.nav-list [class^="icon-"]{margin-right:2px;} +.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;} +.nav-tabs,.nav-pills{*zoom:1;}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;content:"";line-height:0;} +.nav-tabs:after,.nav-pills:after{clear:both;} +.nav-tabs>li,.nav-pills>li{float:left;} +.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px;} +.nav-tabs{border-bottom:1px solid #ddd;} +.nav-tabs>li{margin-bottom:-1px;} +.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.nav-tabs>li>a:hover{border-color:#f5f5f5 #f5f5f5 #dddddd;} +.nav-tabs>.active>a,.nav-tabs>.active>a:hover{color:#555555;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default;} +.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;} +.nav-pills>.active>a,.nav-pills>.active>a:hover{color:#ffffff;background-color:#2fa4e7;} +.nav-stacked>li{float:none;} +.nav-stacked>li>a{margin-right:0;} +.nav-tabs.nav-stacked{border-bottom:0;} +.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;} +.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;} +.nav-tabs.nav-stacked>li>a:hover{border-color:#ddd;z-index:2;} +.nav-pills.nav-stacked>li>a{margin-bottom:3px;} +.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px;} +.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;} +.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} +.nav .dropdown-toggle .caret{border-top-color:#2fa4e7;border-bottom-color:#2fa4e7;margin-top:6px;} +.nav .dropdown-toggle:hover .caret{border-top-color:#157ab5;border-bottom-color:#157ab5;} +.nav-tabs .dropdown-toggle .caret{margin-top:8px;} +.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff;} +.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555555;border-bottom-color:#555555;} +.nav>.dropdown.active>a:hover{cursor:pointer;} +.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover{color:#ffffff;background-color:#999999;border-color:#999999;} +.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:1;filter:alpha(opacity=100);} +.tabs-stacked .open>a:hover{border-color:#999999;} +.tabbable{*zoom:1;}.tabbable:before,.tabbable:after{display:table;content:"";line-height:0;} +.tabbable:after{clear:both;} +.tab-content{overflow:auto;} +.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0;} +.tab-content>.tab-pane,.pill-content>.pill-pane{display:none;} +.tab-content>.active,.pill-content>.active{display:block;} +.tabs-below>.nav-tabs{border-top:1px solid #ddd;} +.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0;} +.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}.tabs-below>.nav-tabs>li>a:hover{border-bottom-color:transparent;border-top-color:#ddd;} +.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover{border-color:transparent #ddd #ddd #ddd;} +.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none;} +.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px;} +.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd;} +.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;} +.tabs-left>.nav-tabs>li>a:hover{border-color:#f5f5f5 #dddddd #f5f5f5 #f5f5f5;} +.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover{border-color:#ddd transparent #ddd #ddd;*border-right-color:#ffffff;} +.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd;} +.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;} +.tabs-right>.nav-tabs>li>a:hover{border-color:#f5f5f5 #f5f5f5 #f5f5f5 #dddddd;} +.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;*border-left-color:#ffffff;} +.nav>.disabled>a{color:#999999;} +.nav>.disabled>a:hover{text-decoration:none;background-color:transparent;cursor:default;} +.navbar{overflow:visible;margin-bottom:20px;color:#f5f5f5;*position:relative;*z-index:2;} +.navbar-inner{min-height:50px;padding-left:20px;padding-right:20px;background-color:#45aeea;background-image:-moz-linear-gradient(top, #54b4eb, #2fa4e7);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#54b4eb), to(#2fa4e7));background-image:-webkit-linear-gradient(top, #54b4eb, #2fa4e7);background-image:-o-linear-gradient(top, #54b4eb, #2fa4e7);background-image:linear-gradient(to bottom, #54b4eb, #2fa4e7);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff54b4eb', endColorstr='#ff2fa4e7', GradientType=0);border:1px solid #1684c2;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 4px rgba(0, 0, 0, 0.065);-moz-box-shadow:0 1px 4px rgba(0, 0, 0, 0.065);box-shadow:0 1px 4px rgba(0, 0, 0, 0.065);*zoom:1;}.navbar-inner:before,.navbar-inner:after{display:table;content:"";line-height:0;} +.navbar-inner:after{clear:both;} +.navbar .container{width:auto;} +.nav-collapse.collapse{height:auto;} +.navbar .brand{float:left;display:block;padding:15px 20px 15px;margin-left:-20px;font-size:20px;font-weight:200;color:#ffffff;text-shadow:0 1px 0 #54b4eb;}.navbar .brand:hover{text-decoration:none;} +.navbar-text{margin-bottom:0;line-height:50px;} +.navbar-link{color:#ffffff;}.navbar-link:hover{color:#ffffff;} +.navbar .divider-vertical{height:50px;margin:0 9px;border-left:1px solid #2fa4e7;border-right:1px solid #54b4eb;} +.navbar .btn,.navbar .btn-group{margin-top:10px;} +.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn{margin-top:0;} +.navbar-form{margin-bottom:0;*zoom:1;}.navbar-form:before,.navbar-form:after{display:table;content:"";line-height:0;} +.navbar-form:after{clear:both;} +.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:10px;} +.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0;} +.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px;} +.navbar-form .input-append,.navbar-form .input-prepend{margin-top:6px;white-space:nowrap;}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0;} +.navbar-search{position:relative;float:left;margin-top:10px;margin-bottom:0;}.navbar-search .search-query{margin-bottom:0;padding:4px 14px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;} +.navbar-static-top{position:static;width:100%;margin-bottom:0;}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0;} +.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px;} +.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0;} +.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-left:0;padding-right:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;} +.navbar-fixed-top{top:0;} +.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.1), 0 1px 10px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.1), 0 1px 10px rgba(0, 0, 0, 0.1);box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.1), 0 1px 10px rgba(0, 0, 0, 0.1);} +.navbar-fixed-bottom{bottom:0;}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.1), 0 -1px 10px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.1), 0 -1px 10px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.1), 0 -1px 10px rgba(0, 0, 0, 0.1);} +.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0;} +.navbar .nav.pull-right{float:right;margin-right:0;} +.navbar .nav>li{float:left;} +.navbar .nav>li>a{float:none;padding:15px 15px 15px;color:#ffffff;text-decoration:none;text-shadow:0 1px 0 #54b4eb;} +.navbar .nav .dropdown-toggle .caret{margin-top:8px;} +.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{background-color:rgba(0, 0, 0, 0.2);color:#ffffff;text-decoration:none;} +.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#ffffff;text-decoration:none;background-color:rgba(0, 0, 0, 0.2);-webkit-box-shadow:inset 0 3px 8px rgba(0, 0, 0, 0.125);-moz-box-shadow:inset 0 3px 8px rgba(0, 0, 0, 0.125);box-shadow:inset 0 3px 8px rgba(0, 0, 0, 0.125);} +.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-left:5px;margin-right:5px;color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#2fa3e6;background-image:-moz-linear-gradient(top, #3daae9, #1a99e2);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#3daae9), to(#1a99e2));background-image:-webkit-linear-gradient(top, #3daae9, #1a99e2);background-image:-o-linear-gradient(top, #3daae9, #1a99e2);background-image:linear-gradient(to bottom, #3daae9, #1a99e2);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3daae9', endColorstr='#ff1a99e2', GradientType=0);border-color:#1a99e2 #1a99e2 #126b9e;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#1a99e2;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);}.navbar .btn-navbar:hover,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#ffffff;background-color:#1a99e2;*background-color:#178acc;} +.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#157ab5 \9;} +.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);-moz-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);} +.btn-navbar .icon-bar+.icon-bar{margin-top:3px;} +.navbar .nav>li>.dropdown-menu:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0, 0, 0, 0.2);position:absolute;top:-7px;left:9px;} +.navbar .nav>li>.dropdown-menu:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #ffffff;position:absolute;top:-6px;left:10px;} +.navbar-fixed-bottom .nav>li>.dropdown-menu:before{border-top:7px solid #ccc;border-top-color:rgba(0, 0, 0, 0.2);border-bottom:0;bottom:-7px;top:auto;} +.navbar-fixed-bottom .nav>li>.dropdown-menu:after{border-top:6px solid #ffffff;border-bottom:0;bottom:-6px;top:auto;} +.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{background-color:rgba(0, 0, 0, 0.2);color:#ffffff;} +.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;} +.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;} +.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{left:auto;right:0;}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{left:auto;right:12px;} +.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{left:auto;right:13px;} +.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{left:auto;right:100%;margin-left:0;margin-right:-1px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px;} +.navbar-inverse{color:#ffffff;}.navbar-inverse .navbar-inner{background-color:#034482;background-image:-moz-linear-gradient(top, #04498c, #033c73);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#04498c), to(#033c73));background-image:-webkit-linear-gradient(top, #04498c, #033c73);background-image:-o-linear-gradient(top, #04498c, #033c73);background-image:linear-gradient(to bottom, #04498c, #033c73);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff04498c', endColorstr='#ff033c73', GradientType=0);border-color:rgba(0, 0, 0, 0.1);} +.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover{color:#ffffff;} +.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{background-color:rgba(0, 0, 0, 0.05);color:#ffffff;} +.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#ffffff;background-color:rgba(0, 0, 0, 0.05);} +.navbar-inverse .navbar-link{color:#ffffff;}.navbar-inverse .navbar-link:hover{color:#ffffff;} +.navbar-inverse .divider-vertical{border-left-color:#033c73;border-right-color:#04498c;} +.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{background-color:rgba(0, 0, 0, 0.05);color:#ffffff;} +.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;} +.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;} +.navbar-inverse .navbar-search .search-query{color:#ffffff;background-color:#ffffff;border-color:#033c73;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none;}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#999999;} +.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#999999;} +.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#999999;} +.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333333;text-shadow:0 1px 0 #ffffff;background-color:#ffffff;border:0;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);outline:0;} +.navbar-inverse .btn-navbar{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#033769;background-image:-moz-linear-gradient(top, #033c73, #022f5a);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#033c73), to(#022f5a));background-image:-webkit-linear-gradient(top, #033c73, #022f5a);background-image:-o-linear-gradient(top, #033c73, #022f5a);background-image:linear-gradient(to bottom, #033c73, #022f5a);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff033c73', endColorstr='#ff022f5a', GradientType=0);border-color:#022f5a #022f5a #000810;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#022f5a;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#ffffff;background-color:#022f5a;*background-color:#022241;} +.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#011528 \9;} +.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.breadcrumb li{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 0 #ffffff;} +.breadcrumb .divider{padding:0 5px;color:#ccc;} +.breadcrumb .active{color:#999999;} +.pagination{height:40px;margin:20px 0;} +.pagination ul{display:inline-block;*display:inline;*zoom:1;margin-left:0;margin-bottom:0;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);} +.pagination ul>li{display:inline;} +.pagination ul>li>a,.pagination ul>li>span{float:left;padding:0 14px;line-height:38px;text-decoration:none;background-color:#ffffff;border:1px solid #dddddd;border-left-width:0;} +.pagination ul>li>a:hover,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5;} +.pagination ul>.active>a,.pagination ul>.active>span{color:#999999;cursor:default;} +.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover{color:#999999;background-color:transparent;cursor:default;} +.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} +.pagination-centered{text-align:center;} +.pagination-right{text-align:right;} +.pager{margin:20px 0;list-style:none;text-align:center;*zoom:1;}.pager:before,.pager:after{display:table;content:"";line-height:0;} +.pager:after{clear:both;} +.pager li{display:inline;} +.pager a,.pager span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;} +.pager a:hover{text-decoration:none;background-color:#f5f5f5;} +.pager .next a,.pager .next span{float:right;} +.pager .previous a{float:left;} +.pager .disabled a,.pager .disabled a:hover,.pager .disabled span{color:#999999;background-color:#fff;cursor:default;} +.modal-open .modal .dropdown-menu{z-index:2050;} +.modal-open .modal .dropdown.open{*z-index:2050;} +.modal-open .modal .popover{z-index:2060;} +.modal-open .modal .tooltip{z-index:2070;} +.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000;}.modal-backdrop.fade{opacity:0;} +.modal-backdrop,.modal-backdrop.fade.in{opacity:0.8;filter:alpha(opacity=80);} +.modal{position:fixed;top:50%;left:50%;z-index:1050;overflow:auto;width:560px;margin:-250px 0 0 -280px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;} +.modal.fade.in{top:50%;} +.modal-header{padding:9px 15px;border-bottom:1px solid #eee;}.modal-header .close{margin-top:2px;} +.modal-header h3{margin:0;line-height:30px;} +.modal-body{overflow-y:auto;max-height:400px;padding:15px;} +.modal-form{margin-bottom:0;} +.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;*zoom:1;}.modal-footer:before,.modal-footer:after{display:table;content:"";line-height:0;} +.modal-footer:after{clear:both;} +.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0;} +.modal-footer .btn-group .btn+.btn{margin-left:-1px;} +.tooltip{position:absolute;z-index:1020;display:block;visibility:visible;padding:5px;font-size:11px;opacity:0;filter:alpha(opacity=0);}.tooltip.in{opacity:0.8;filter:alpha(opacity=80);} +.tooltip.top{margin-top:-3px;} +.tooltip.right{margin-left:3px;} +.tooltip.bottom{margin-top:3px;} +.tooltip.left{margin-left:-3px;} +.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;text-decoration:none;background-color:#000000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid;} +.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000000;} +.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000000;} +.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000000;} +.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000000;} +.popover{position:absolute;top:0;left:0;z-index:1010;display:none;width:236px;padding:1px;background-color:#ffffff;-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);}.popover.top{margin-bottom:10px;} +.popover.right{margin-left:10px;} +.popover.bottom{margin-top:10px;} +.popover.left{margin-right:10px;} +.popover-title{margin:0;padding:8px 14px;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0;} +.popover-content{padding:9px 14px;}.popover-content p,.popover-content ul,.popover-content ol{margin-bottom:0;} +.popover .arrow,.popover .arrow:after{position:absolute;display:inline-block;width:0;height:0;border-color:transparent;border-style:solid;} +.popover .arrow:after{content:"";z-index:-1;} +.popover.top .arrow{bottom:-10px;left:50%;margin-left:-10px;border-width:10px 10px 0;border-top-color:#ffffff;}.popover.top .arrow:after{border-width:11px 11px 0;border-top-color:rgba(0, 0, 0, 0.25);bottom:-1px;left:-11px;} +.popover.right .arrow{top:50%;left:-10px;margin-top:-10px;border-width:10px 10px 10px 0;border-right-color:#ffffff;}.popover.right .arrow:after{border-width:11px 11px 11px 0;border-right-color:rgba(0, 0, 0, 0.25);bottom:-11px;left:-1px;} +.popover.bottom .arrow{top:-10px;left:50%;margin-left:-10px;border-width:0 10px 10px;border-bottom-color:#ffffff;}.popover.bottom .arrow:after{border-width:0 11px 11px;border-bottom-color:rgba(0, 0, 0, 0.25);top:-1px;left:-11px;} +.popover.left .arrow{top:50%;right:-10px;margin-top:-10px;border-width:10px 0 10px 10px;border-left-color:#ffffff;}.popover.left .arrow:after{border-width:11px 0 11px 11px;border-left-color:rgba(0, 0, 0, 0.25);bottom:-11px;right:-1px;} +.thumbnails{margin-left:-20px;list-style:none;*zoom:1;}.thumbnails:before,.thumbnails:after{display:table;content:"";line-height:0;} +.thumbnails:after{clear:both;} +.row-fluid .thumbnails{margin-left:0;} +.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px;} +.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.055);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.055);box-shadow:0 1px 3px rgba(0, 0, 0, 0.055);-webkit-transition:all 0.2s ease-in-out;-moz-transition:all 0.2s ease-in-out;-o-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out;} +a.thumbnail:hover{border-color:#2fa4e7;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);} +.thumbnail>img{display:block;max-width:100%;margin-left:auto;margin-right:auto;} +.thumbnail .caption{padding:9px;color:#555555;} +.label,.badge{font-size:11.844px;font-weight:bold;line-height:14px;color:#ffffff;vertical-align:baseline;white-space:nowrap;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#999999;} +.label{padding:1px 4px 2px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +.badge{padding:1px 9px 2px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px;} +a.label:hover,a.badge:hover{color:#ffffff;text-decoration:none;cursor:pointer;} +.label-important,.badge-important{background-color:#bd4247;} +.label-important[href],.badge-important[href]{background-color:#983538;} +.label-warning,.badge-warning{background-color:#dd5600;} +.label-warning[href],.badge-warning[href]{background-color:#aa4200;} +.label-success,.badge-success{background-color:#669533;} +.label-success[href],.badge-success[href]{background-color:#4c6f26;} +.label-info,.badge-info{background-color:#817b58;} +.label-info[href],.badge-info[href]{background-color:#625e43;} +.label-inverse,.badge-inverse{background-color:#333333;} +.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a;} +.btn .label,.btn .badge{position:relative;top:-1px;} +.btn-mini .label,.btn-mini .badge{top:0;} +@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-o-keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}@keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f7f7f7;background-image:-moz-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));background-image:-webkit-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-o-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:linear-gradient(to bottom, #f5f5f5, #f9f9f9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.progress .bar{width:0%;height:100%;color:#ffffff;float:left;font-size:12px;text-align:center;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top, #149bdf, #0480be);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));background-image:-webkit-linear-gradient(top, #149bdf, #0480be);background-image:-o-linear-gradient(top, #149bdf, #0480be);background-image:linear-gradient(to bottom, #149bdf, #0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width 0.6s ease;-moz-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease;} +.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);box-shadow:inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);} +.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px;} +.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite;} +.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(to bottom, #ee5f5b, #c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffc43c35', GradientType=0);} +.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} +.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(to bottom, #62c462, #57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff57a957', GradientType=0);} +.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} +.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(to bottom, #5bc0de, #339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff339bb9', GradientType=0);} +.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} +.progress-warning .bar,.progress .bar-warning{background-color:#f16e1a;background-image:-moz-linear-gradient(top, #ff7d2b, #dd5600);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ff7d2b), to(#dd5600));background-image:-webkit-linear-gradient(top, #ff7d2b, #dd5600);background-image:-o-linear-gradient(top, #ff7d2b, #dd5600);background-image:linear-gradient(to bottom, #ff7d2b, #dd5600);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff7d2b', endColorstr='#ffdd5600', GradientType=0);} +.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#ff7d2b;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} +.accordion{margin-bottom:20px;} +.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.accordion-heading{border-bottom:0;} +.accordion-heading .accordion-toggle{display:block;padding:8px 15px;} +.accordion-toggle{cursor:pointer;} +.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5;} +.carousel{position:relative;margin-bottom:20px;line-height:1;} +.carousel-inner{overflow:hidden;width:100%;position:relative;} +.carousel .item{display:none;position:relative;-webkit-transition:0.6s ease-in-out left;-moz-transition:0.6s ease-in-out left;-o-transition:0.6s ease-in-out left;transition:0.6s ease-in-out left;} +.carousel .item>img{display:block;line-height:1;} +.carousel .active,.carousel .next,.carousel .prev{display:block;} +.carousel .active{left:0;} +.carousel .next,.carousel .prev{position:absolute;top:0;width:100%;} +.carousel .next{left:100%;} +.carousel .prev{left:-100%;} +.carousel .next.left,.carousel .prev.right{left:0;} +.carousel .active.left{left:-100%;} +.carousel .active.right{left:100%;} +.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#ffffff;text-align:center;background:#222222;border:3px solid #ffffff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:0.5;filter:alpha(opacity=50);}.carousel-control.right{left:auto;right:15px;} +.carousel-control:hover{color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90);} +.carousel-caption{position:absolute;left:0;right:0;bottom:0;padding:15px;background:#333333;background:rgba(0, 0, 0, 0.75);} +.carousel-caption h4,.carousel-caption p{color:#ffffff;line-height:20px;} +.carousel-caption h4{margin:0 0 5px;} +.carousel-caption p{margin-bottom:0;} +.hero-unit{padding:60px;margin-bottom:30px;background-color:#f5f5f5;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;color:inherit;letter-spacing:-1px;} +.hero-unit p{font-size:18px;font-weight:200;line-height:30px;color:inherit;} +.pull-right{float:right;} +.pull-left{float:left;} +.hide{display:none;} +.show{display:block;} +.invisible{visibility:hidden;} +.affix{position:fixed;} +.navbar .brand{padding:14px 20px 16px;font-family:'Telex',sans-serif;text-shadow:1px 1px 0 rgba(0, 0, 0, 0.2);} +.navbar li{line-height:20px;} +.navbar .nav>li>a{padding:16px 10px 14px;font-family:'Telex',sans-serif;text-shadow:1px 1px 0 rgba(0, 0, 0, 0.2);} +.navbar .search-query{border:1px solid #178acc;line-height:normal;} +.navbar .navbar-text{padding:19px 10px 18px;line-height:13px;color:rgba(0, 0, 0, 0.5);text-shadow:1px 1px 0 rgba(255, 255, 255, 0.3);} +.navbar-inverse .navbar-search .search-query{color:#555555;} +@media (max-width:979px){.navbar .nav-collapse .nav li>a{font-family:'Telex',sans-serif;font-weight:normal;color:#ffffff;text-shadow:1px 1px 0 rgba(0, 0, 0, 0.2);}.navbar .nav-collapse .nav li>a:hover{background-color:#2B7CAC;} .navbar .nav-collapse .nav .active>a{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;background-color:#2B7CAC;} .navbar .nav-collapse .dropdown-menu li>a:hover,.navbar .nav-collapse .dropdown-menu li>a:focus,.navbar .nav-collapse .dropdown-submenu:hover>a{background-image:none;} .navbar .nav-collapse .navbar-form,.navbar .nav-collapse .navbar-search{border:none;} .navbar .nav-collapse .nav-header{color:#2B7CAC;} .navbar-inverse .nav-collapse .nav li>a{color:#ffffff;}.navbar-inverse .nav-collapse .nav li>a:hover{background-color:rgba(0, 0, 0, 0.1);} .navbar-inverse .nav-collapse .nav .active>a,.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:hover{background-color:rgba(0, 0, 0, 0.1) !important;}}div.subnav{font-family:'Telex',sans-serif;text-shadow:1px 1px 0 rgba(255, 255, 255, 0.2);}div.subnav-fixed{top:50px;} +.btn{background-color:#ffffff;background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(5%, #ffffff), to(#ffffff));background-image:-webkit-linear-gradient(#ffffff, #ffffff 5%, #ffffff);background-image:-moz-linear-gradient(top, #ffffff, #ffffff 5%, #ffffff);background-image:-o-linear-gradient(#ffffff, #ffffff 5%, #ffffff);background-image:linear-gradient(#ffffff, #ffffff 5%, #ffffff);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffffffff', GradientType=0);-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);}.btn:hover{background-position:0 0;} +.btn-primary{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#3daae9;background-image:-moz-linear-gradient(top, #46aeea, #2fa4e7);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#46aeea), to(#2fa4e7));background-image:-webkit-linear-gradient(top, #46aeea, #2fa4e7);background-image:-o-linear-gradient(top, #46aeea, #2fa4e7);background-image:linear-gradient(to bottom, #46aeea, #2fa4e7);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff46aeea', endColorstr='#ff2fa4e7', GradientType=0);border-color:#2fa4e7 #2fa4e7 #157ab5;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#2fa4e7;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#ffffff;background-color:#2fa4e7;*background-color:#1a99e2;} +.btn-primary:active,.btn-primary.active{background-color:#178acc \9;} +.btn-info{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#9e6ab8;background-image:-moz-linear-gradient(top, #a271bb, #9760b3);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#a271bb), to(#9760b3));background-image:-webkit-linear-gradient(top, #a271bb, #9760b3);background-image:-o-linear-gradient(top, #a271bb, #9760b3);background-image:linear-gradient(to bottom, #a271bb, #9760b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffa271bb', endColorstr='#ff9760b3', GradientType=0);border-color:#9760b3 #9760b3 #6f4086;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#9760b3;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#ffffff;background-color:#9760b3;*background-color:#8b51a9;} +.btn-info:active,.btn-info.active{background-color:#7d4898 \9;} +.btn-success{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#7bb33d;background-image:-moz-linear-gradient(top, #80bb3f, #73a839);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#80bb3f), to(#73a839));background-image:-webkit-linear-gradient(top, #80bb3f, #73a839);background-image:-o-linear-gradient(top, #80bb3f, #73a839);background-image:linear-gradient(to bottom, #80bb3f, #73a839);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff80bb3f', endColorstr='#ff73a839', GradientType=0);border-color:#73a839 #73a839 #4c6f26;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#73a839;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#ffffff;background-color:#73a839;*background-color:#669533;} +.btn-success:active,.btn-success.active{background-color:#59822c \9;} +.btn-warning{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#ec5c00;background-image:-moz-linear-gradient(top, #f76000, #dd5600);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f76000), to(#dd5600));background-image:-webkit-linear-gradient(top, #f76000, #dd5600);background-image:-o-linear-gradient(top, #f76000, #dd5600);background-image:linear-gradient(to bottom, #f76000, #dd5600);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff76000', endColorstr='#ffdd5600', GradientType=0);border-color:#dd5600 #dd5600 #913800;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#dd5600;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#ffffff;background-color:#dd5600;*background-color:#c44c00;} +.btn-warning:active,.btn-warning.active{background-color:#aa4200 \9;} +.btn-danger{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#d41e24;background-image:-moz-linear-gradient(top, #dd1f26, #c71c22);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#dd1f26), to(#c71c22));background-image:-webkit-linear-gradient(top, #dd1f26, #c71c22);background-image:-o-linear-gradient(top, #dd1f26, #c71c22);background-image:linear-gradient(to bottom, #dd1f26, #c71c22);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdd1f26', endColorstr='#ffc71c22', GradientType=0);border-color:#c71c22 #c71c22 #841317;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#c71c22;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#ffffff;background-color:#c71c22;*background-color:#b1191e;} +.btn-danger:active,.btn-danger.active{background-color:#9a161a \9;} +.btn-inverse{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#034482;background-image:-moz-linear-gradient(top, #04498c, #033c73);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#04498c), to(#033c73));background-image:-webkit-linear-gradient(top, #04498c, #033c73);background-image:-o-linear-gradient(top, #04498c, #033c73);background-image:linear-gradient(to bottom, #04498c, #033c73);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff04498c', endColorstr='#ff033c73', GradientType=0);border-color:#033c73 #033c73 #011528;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#033c73;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#ffffff;background-color:#033c73;*background-color:#022f5a;} +.btn-inverse:active,.btn-inverse.active{background-color:#022241 \9;} +i[class^="icon-"]{opacity:0.8;} +.pull-right{float:right;} +.pull-left{float:left;} +.hide{display:none;} +.show{display:block;} +.invisible{visibility:hidden;} +.affix{position:fixed;} diff --git a/css/jquery.miniColors.css b/css/jquery.miniColors.css new file mode 100644 index 0000000..f0777a2 --- /dev/null +++ b/css/jquery.miniColors.css @@ -0,0 +1,125 @@ +INPUT.miniColors { + margin-right: 4px; +} + +.miniColors-selector { + position: absolute; + width: 175px; + height: 150px; + background: white; + border: solid 1px #bababa; + -moz-box-shadow: 0 0 6px rgba(0, 0, 0, .25); + -webkit-box-shadow: 0 0 6px rgba(0, 0, 0, .25); + box-shadow: 0 0 6px rgba(0, 0, 0, .25); + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; + padding: 5px; + z-index: 999999; +} + +.miniColors.opacity.miniColors-selector { + width: 200px; +} + +.miniColors-selector.black { + background: black; + border-color: black; +} + +.miniColors-colors { + position: absolute; + top: 5px; + left: 5px; + width: 150px; + height: 150px; + background: url(../img/colors.png) -40px 0 no-repeat; + cursor: crosshair; +} + +.miniColors.opacity .miniColors-colors { + left: 30px; +} + +.miniColors-hues { + position: absolute; + top: 5px; + left: 160px; + width: 20px; + height: 150px; + background: url(../img/colors.png) 0 0 no-repeat; + cursor: crosshair; +} + +.miniColors.opacity .miniColors-hues { + left: 185px; +} + +.miniColors-opacity { + position: absolute; + top: 5px; + left: 5px; + width: 20px; + height: 150px; + background: url(../img/colors.png) -20px 0 no-repeat; + cursor: crosshair; +} + +.miniColors-colorPicker { + position: absolute; + width: 11px; + height: 11px; + border: 1px solid black; + -moz-border-radius: 11px; + -webkit-border-radius: 11px; + border-radius: 11px; +} +.miniColors-colorPicker-inner { + position: absolute; + top: 0; + left: 0; + width: 7px; + height: 7px; + border: 2px solid white; + -moz-border-radius: 9px; + -webkit-border-radius: 9px; + border-radius: 9px; +} + +.miniColors-huePicker, +.miniColors-opacityPicker { + position: absolute; + left: -2px; + width: 22px; + height: 2px; + border: 1px solid black; + background: white; + margin-top: -1px; + border-radius: 2px; +} + +.miniColors-trigger, +.miniColors-triggerWrap { + width: 22px; + height: 22px; + display: inline-block; +} + +.miniColors-triggerWrap { + background: url(../img/trigger.png) -22px 0 no-repeat; +} + +.miniColors-triggerWrap.disabled { + filter: alpha(opacity=50); + opacity: .5; +} + +.miniColors-trigger { + vertical-align: middle; + outline: none; + background: url(../img/trigger.png) 0 0 no-repeat; +} + +.miniColors-triggerWrap.disabled .miniColors-trigger { + cursor: default; +}
\ No newline at end of file diff --git a/css/jquery.simplecolorpicker.css b/css/jquery.simplecolorpicker.css new file mode 100644 index 0000000..9a6f33a --- /dev/null +++ b/css/jquery.simplecolorpicker.css @@ -0,0 +1,95 @@ +/** + * Very simple jQuery Color Picker CSS. + * + * Copyright (C) 2012 Tanguy Krotoff + * + * Licensed under the MIT license. + * + * Inspired by Bootstrap Twitter. + * See https://github.com/twitter/bootstrap/blob/master/less/dropdowns.less + * See http://twitter.github.com/bootstrap/assets/css/bootstrap.css + */ + +.simplecolorpicker:before { + position: absolute; + top: -7px; + left: 9px; + display: inline-block; + border-right: 7px solid transparent; + border-bottom: 7px solid #ccc; + border-left: 7px solid transparent; + border-bottom-color: rgba(0, 0, 0, 0.2); + content: ''; +} + +.simplecolorpicker:after { + position: absolute; + top: -6px; + left: 10px; + display: inline-block; + border-right: 6px solid transparent; + border-bottom: 6px solid #ffffff; + border-left: 6px solid transparent; + content: ''; +} + +.simplecolorpicker.picker { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + + min-width: 160px; + max-width: 264px; + + padding: 4px 0 0 4px; + margin: 1px 0 0; + list-style: none; + background-color: #ffffff; + + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, 0.2); + + *border-right-width: 2px; + *border-bottom-width: 2px; + + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + background-clip: padding-box; +} + + .simplecolorpicker.inline { + display: inline-block; + height: 18px; + padding: 4px 0; + } + + .simplecolorpicker.icon, + .simplecolorpicker div { + cursor: pointer; + display: inline-block; + + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + + border: 1px solid transparent; + } + .simplecolorpicker div { + margin: 0 4px 4px 0; + } + + .simplecolorpicker div:hover, + .simplecolorpicker div.selected { + border: 1px solid black; + } diff --git a/img/Actions-format-text-bold-icon.png b/img/Actions-format-text-bold-icon.png Binary files differnew file mode 100644 index 0000000..2d20694 --- /dev/null +++ b/img/Actions-format-text-bold-icon.png diff --git a/img/Actions-format-text-italic-icon.png b/img/Actions-format-text-italic-icon.png Binary files differnew file mode 100644 index 0000000..1b472ef --- /dev/null +++ b/img/Actions-format-text-italic-icon.png diff --git a/img/Actions-format-text-strikethrough-icon.png b/img/Actions-format-text-strikethrough-icon.png Binary files differnew file mode 100644 index 0000000..f3078bc --- /dev/null +++ b/img/Actions-format-text-strikethrough-icon.png diff --git a/img/Actions-format-text-underline-icon.png b/img/Actions-format-text-underline-icon.png Binary files differnew file mode 100644 index 0000000..ada09d5 --- /dev/null +++ b/img/Actions-format-text-underline-icon.png diff --git a/img/colors.png b/img/colors.png Binary files differnew file mode 100644 index 0000000..deb50a9 --- /dev/null +++ b/img/colors.png diff --git a/img/crew_back.png b/img/crew_back.png Binary files differnew file mode 100644 index 0000000..f755ddc --- /dev/null +++ b/img/crew_back.png diff --git a/img/crew_front.png b/img/crew_front.png Binary files differnew file mode 100644 index 0000000..c9e85bd --- /dev/null +++ b/img/crew_front.png diff --git a/img/delete.png b/img/delete.png Binary files differnew file mode 100644 index 0000000..4713c13 --- /dev/null +++ b/img/delete.png diff --git a/img/drag.png b/img/drag.png Binary files differnew file mode 100644 index 0000000..5aa700d --- /dev/null +++ b/img/drag.png diff --git a/img/font_bold.png b/img/font_bold.png Binary files differnew file mode 100644 index 0000000..52d41e0 --- /dev/null +++ b/img/font_bold.png diff --git a/img/font_italic.png b/img/font_italic.png Binary files differnew file mode 100644 index 0000000..1017f96 --- /dev/null +++ b/img/font_italic.png diff --git a/img/font_strikethrough.png b/img/font_strikethrough.png Binary files differnew file mode 100644 index 0000000..42abc79 --- /dev/null +++ b/img/font_strikethrough.png diff --git a/img/font_underline.png b/img/font_underline.png Binary files differnew file mode 100644 index 0000000..a90947a --- /dev/null +++ b/img/font_underline.png diff --git a/img/glyphicons-halflings-white.png b/img/glyphicons-halflings-white.png Binary files differnew file mode 100644 index 0000000..3bf6484 --- /dev/null +++ b/img/glyphicons-halflings-white.png diff --git a/img/glyphicons-halflings.png b/img/glyphicons-halflings.png Binary files differnew file mode 100644 index 0000000..a996999 --- /dev/null +++ b/img/glyphicons-halflings.png diff --git a/img/invisibleman.jpg b/img/invisibleman.jpg Binary files differnew file mode 100644 index 0000000..dd057c8 --- /dev/null +++ b/img/invisibleman.jpg diff --git a/img/mens_hoodie_back.png b/img/mens_hoodie_back.png Binary files differnew file mode 100644 index 0000000..ae7c1d3 --- /dev/null +++ b/img/mens_hoodie_back.png diff --git a/img/mens_hoodie_front.png b/img/mens_hoodie_front.png Binary files differnew file mode 100644 index 0000000..178d227 --- /dev/null +++ b/img/mens_hoodie_front.png diff --git a/img/mens_longsleeve_back.png b/img/mens_longsleeve_back.png Binary files differnew file mode 100644 index 0000000..43e29b2 --- /dev/null +++ b/img/mens_longsleeve_back.png diff --git a/img/mens_longsleeve_front.png b/img/mens_longsleeve_front.png Binary files differnew file mode 100644 index 0000000..7cd405b --- /dev/null +++ b/img/mens_longsleeve_front.png diff --git a/img/mens_tank_back.png b/img/mens_tank_back.png Binary files differnew file mode 100644 index 0000000..160498c --- /dev/null +++ b/img/mens_tank_back.png diff --git a/img/mens_tank_front.png b/img/mens_tank_front.png Binary files differnew file mode 100644 index 0000000..c672d3f --- /dev/null +++ b/img/mens_tank_front.png diff --git a/img/phones/GalaxyS3.png b/img/phones/GalaxyS3.png Binary files differnew file mode 100644 index 0000000..32d558e --- /dev/null +++ b/img/phones/GalaxyS3.png diff --git a/img/phones/GalaxyS3A.png b/img/phones/GalaxyS3A.png Binary files differnew file mode 100644 index 0000000..0ddeb4f --- /dev/null +++ b/img/phones/GalaxyS3A.png diff --git a/img/phones/GalaxyS3Base.png b/img/phones/GalaxyS3Base.png Binary files differnew file mode 100644 index 0000000..0ab5838 --- /dev/null +++ b/img/phones/GalaxyS3Base.png diff --git a/img/phones/GalaxyS3Mask.png b/img/phones/GalaxyS3Mask.png Binary files differnew file mode 100644 index 0000000..0f52d08 --- /dev/null +++ b/img/phones/GalaxyS3Mask.png diff --git a/img/phones/designs/1.jpg b/img/phones/designs/1.jpg Binary files differnew file mode 100644 index 0000000..5118cd7 --- /dev/null +++ b/img/phones/designs/1.jpg diff --git a/img/phones/designs/2.jpg b/img/phones/designs/2.jpg Binary files differnew file mode 100644 index 0000000..8efabb8 --- /dev/null +++ b/img/phones/designs/2.jpg diff --git a/img/phones/designs/3.jpg b/img/phones/designs/3.jpg Binary files differnew file mode 100644 index 0000000..080a8a2 --- /dev/null +++ b/img/phones/designs/3.jpg diff --git a/img/phones/designs/4.jpg b/img/phones/designs/4.jpg Binary files differnew file mode 100644 index 0000000..0072bb0 --- /dev/null +++ b/img/phones/designs/4.jpg diff --git a/img/phones/designs/5.jpg b/img/phones/designs/5.jpg Binary files differnew file mode 100644 index 0000000..a8f2558 --- /dev/null +++ b/img/phones/designs/5.jpg diff --git a/img/phones/designs/6.jpg b/img/phones/designs/6.jpg Binary files differnew file mode 100644 index 0000000..5ac30f3 --- /dev/null +++ b/img/phones/designs/6.jpg diff --git a/img/phones/designs/7.jpg b/img/phones/designs/7.jpg Binary files differnew file mode 100644 index 0000000..b3f21d5 --- /dev/null +++ b/img/phones/designs/7.jpg diff --git a/img/phones/designs/8.jpg b/img/phones/designs/8.jpg Binary files differnew file mode 100644 index 0000000..0f597bd --- /dev/null +++ b/img/phones/designs/8.jpg diff --git a/img/phones/iPhone3A.png b/img/phones/iPhone3A.png Binary files differnew file mode 100644 index 0000000..135d8bd --- /dev/null +++ b/img/phones/iPhone3A.png diff --git a/img/phones/iPhone3GHCOverlay.png b/img/phones/iPhone3GHCOverlay.png Binary files differnew file mode 100644 index 0000000..984791d --- /dev/null +++ b/img/phones/iPhone3GHCOverlay.png diff --git a/img/phones/iPhone4.png b/img/phones/iPhone4.png Binary files differnew file mode 100644 index 0000000..19b4ca0 --- /dev/null +++ b/img/phones/iPhone4.png diff --git a/img/phones/iPhone4A.png b/img/phones/iPhone4A.png Binary files differnew file mode 100644 index 0000000..71fb864 --- /dev/null +++ b/img/phones/iPhone4A.png diff --git a/img/phones/iPhone4HCOverlay.png b/img/phones/iPhone4HCOverlay.png Binary files differnew file mode 100644 index 0000000..a470366 --- /dev/null +++ b/img/phones/iPhone4HCOverlay.png diff --git a/img/phones/iPhone5A.png b/img/phones/iPhone5A.png Binary files differnew file mode 100644 index 0000000..33aa90c --- /dev/null +++ b/img/phones/iPhone5A.png diff --git a/img/phones/iphone4Base.png b/img/phones/iphone4Base.png Binary files differnew file mode 100644 index 0000000..c523a94 --- /dev/null +++ b/img/phones/iphone4Base.png diff --git a/img/phones/iphone4Mask.png b/img/phones/iphone4Mask.png Binary files differnew file mode 100644 index 0000000..d398023 --- /dev/null +++ b/img/phones/iphone4Mask.png diff --git a/img/phones/iphone5.png b/img/phones/iphone5.png Binary files differnew file mode 100644 index 0000000..1a56333 --- /dev/null +++ b/img/phones/iphone5.png diff --git a/img/phones/iphone5Base.png b/img/phones/iphone5Base.png Binary files differnew file mode 100644 index 0000000..e39ba70 --- /dev/null +++ b/img/phones/iphone5Base.png diff --git a/img/phones/iphone5Mask.png b/img/phones/iphone5Mask.png Binary files differnew file mode 100644 index 0000000..fb9d5b6 --- /dev/null +++ b/img/phones/iphone5Mask.png diff --git a/img/rotate.png b/img/rotate.png Binary files differnew file mode 100644 index 0000000..9883184 --- /dev/null +++ b/img/rotate.png diff --git a/img/sample.jpg b/img/sample.jpg Binary files differnew file mode 100644 index 0000000..433d6bb --- /dev/null +++ b/img/sample.jpg diff --git a/img/scale.png b/img/scale.png Binary files differnew file mode 100644 index 0000000..b783f8b --- /dev/null +++ b/img/scale.png diff --git a/img/trigger.png b/img/trigger.png Binary files differnew file mode 100644 index 0000000..96c9129 --- /dev/null +++ b/img/trigger.png diff --git a/img/womens_crew_back.png b/img/womens_crew_back.png Binary files differnew file mode 100644 index 0000000..7f5b8e2 --- /dev/null +++ b/img/womens_crew_back.png diff --git a/img/womens_crew_front.png b/img/womens_crew_front.png Binary files differnew file mode 100644 index 0000000..f1e1006 --- /dev/null +++ b/img/womens_crew_front.png diff --git a/index.html b/index.html new file mode 100644 index 0000000..9061219 --- /dev/null +++ b/index.html @@ -0,0 +1,279 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <title>Title</title> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content=""> + <meta name="author" content=""> + + <!-- Le HTML5 shim, for IE6-8 support of HTML elements --> + <!--[if lt IE 9]> + <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script> + <![endif]--> + <!--[if IE]><script type="text/javascript" src="js/excanvas.js"></script><![endif]--> + <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> + <script type="text/javascript" src="js/fabric.js"></script> + <script type="text/javascript" src="js/tshirtEditor.js"></script> + <script type="text/javascript" src="js/jquery.miniColors.min.js"></script> + + <!-- Le styles --> + <link type="text/css" rel="stylesheet" href="css/jquery.miniColors.css" /> + <link href="css/bootstrap.min.css" rel="stylesheet"> + <link href="css/bootstrap-responsive.min.css" rel="stylesheet"> + <script type="text/javascript"> + </script> + <style type="text/css"> + .footer { + padding: 70px 0; + margin-top: 70px; + border-top: 1px solid #E5E5E5; + background-color: whiteSmoke; + } + body { + padding-top: 60px; + } + .color-preview { + border: 1px solid #CCC; + margin: 2px; + zoom: 1; + vertical-align: top; + display: inline-block; + cursor: pointer; + overflow: hidden; + width: 20px; + height: 20px; + } + .rotate { + -webkit-transform:rotate(90deg); + -moz-transform:rotate(90deg); + -o-transform:rotate(90deg); + /* filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1.5); */ + -ms-transform:rotate(90deg); + } + .Arial{font-family:"Arial";} + .Helvetica{font-family:"Helvetica";} + .MyriadPro{font-family:"Myriad Pro";} + .Delicious{font-family:"Delicious";} + .Verdana{font-family:"Verdana";} + .Georgia{font-family:"Georgia";} + .Courier{font-family:"Courier";} + .ComicSansMS{font-family:"Comic Sans MS";} + .Impact{font-family:"Impact";} + .Monaco{font-family:"Monaco";} + .Optima{font-family:"Optima";} + .HoeflerText{font-family:"Hoefler Text";} + .Plaster{font-family:"Plaster";} + .Engagement{font-family:"Engagement";} + + </style> + </head> + + <body class="preview" data-spy="scroll" data-target=".subnav" data-offset="80"> + + <!-- Navbar + ================================================== --> + <div class="navbar navbar-fixed-top"> + <div class="navbar-inner"> + <div class="container"> + <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + </a> + <a class="brand" href="#">Custom T-Shirt</a> + <div class="nav-collapse" id="main-menu"> + <ul class="nav" id="main-menu-left"> + <li><a href="case.html">Phone Case</a></li> + </ul> + </div> + </div> + </div> + </div> + + <div class="container"> + <section id="typography"> + <div class="page-header"> + <h1>Customize T-Shirt</h1> + </div> + + <!-- Headings & Paragraph Copy --> + <div class="row"> + <div class="span3"> + + <div class="tabbable"> <!-- Only required for left/right tabs --> + <ul class="nav nav-tabs"> + <li class="active"><a href="#tab1" data-toggle="tab">T-Shirt Options</a></li> + <li><a href="#tab2" data-toggle="tab">Gravatar</a></li> + </ul> + <div class="tab-content"> + <div class="tab-pane active" id="tab1"> + <div class="well"> +<!-- <h3>Tee Styles</h3>--> +<!-- <p>--> + <select id=""> + <option value="1" selected="selected">Short Sleeve Shirts</option> + <option value="2">Long Sleeve Shirts</option> + <option value="3">Hoodies</option> + <option value="4">Tank tops</option> + </select> +<!-- </p>--> + </div> + <div class="well"> + <ul class="nav"> + <li class="color-preview" title="White" style="background-color:#ffffff;"></li> + <li class="color-preview" title="Dark Heather" style="background-color:#616161;"></li> + <li class="color-preview" title="Gray" style="background-color:#f0f0f0;"></li> + <li class="color-preview" title="Charcoal" style="background-color:#5b5b5b;"></li> + <li class="color-preview" title="Black" style="background-color:#222222;"></li> + <li class="color-preview" title="Heather Orange" style="background-color:#fc8d74;"></li> + <li class="color-preview" title="Heather Dark Chocolate" style="background-color:#432d26;"></li> + <li class="color-preview" title="Salmon" style="background-color:#eead91;"></li> + <li class="color-preview" title="Chesnut" style="background-color:#806355;"></li> + <li class="color-preview" title="Dark Chocolate" style="background-color:#382d21;"></li> + <li class="color-preview" title="Citrus Yellow" style="background-color:#faef93;"></li> + <li class="color-preview" title="Avocado" style="background-color:#aeba5e;"></li> + <li class="color-preview" title="Kiwi" style="background-color:#8aa140;"></li> + <li class="color-preview" title="Irish Green" style="background-color:#1f6522;"></li> + <li class="color-preview" title="Scrub Green" style="background-color:#13afa2;"></li> + <li class="color-preview" title="Teal Ice" style="background-color:#b8d5d7;"></li> + <li class="color-preview" title="Heather Sapphire" style="background-color:#15aeda;"></li> + <li class="color-preview" title="Sky" style="background-color:#a5def8;"></li> + <li class="color-preview" title="Antique Sapphire" style="background-color:#0f77c0;"></li> + <li class="color-preview" title="Heather Navy" style="background-color:#3469b7;"></li> + <li class="color-preview" title="Cherry Red" style="background-color:#c50404;"></li> + </ul> + </div> + </div> + <div class="tab-pane" id="tab2"> + <div class="well"> + <div class="input-append"> + <input class="span2" id="text-string" type="text" placeholder="add text here..."><button id="add-text" class="btn" title="Add text"><i class="icon-share-alt"></i></button> + <hr> + </div> + <div id="avatarlist"> + <img style="cursor:pointer;" class="img-polaroid" src="img/invisibleman.jpg"> + <img style="cursor:pointer;" class="img-polaroid" src="data:image/jpeg;base64,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"> + <img style="cursor:pointer;" class="img-polaroid" src="data:image/png;base64,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"> + <img style="cursor:pointer;" class="img-polaroid" src="data:image/png;base64,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%3D%3D"> + <img style="cursor:pointer;" class="img-polaroid" src="data:image/png;base64,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"> + <img style="cursor:pointer;" class="img-polaroid" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAABL0lEQVR4nO3cMQ6CQBRAQVFvaWOsbT2FrbVn9QaEJ26QZKZHNi+/WMjidLpeDixz3HoBeyJWIFYgViBWIFYgViBWIFYgViBWIFYgViBWIFYgViBWIFYgViBWIFYgViBWIFYgViBWIFYgViBWcN7qxu/74+trb6/nD1eynMkKxArECsQKxArECsQKxArECgbu4Of36Gt24eN+eZ7JCsQKxArECsQKxArECsQKxAqm+S9Z9/imfNyaTVYgViBWIFYgViBWIFYgViBWIFYgViBWIFYgViBWIFYgViBWIFaw2SmaPTJZgViBWIFYgViBWIFYgViBWMGqHfz8mZP/PAe/5rnCZAViBWIFYgViBWIFYgViBWIFA9/BjzsH779odkCsQKxArECsQKxArECsQKzgA29sHs723zLdAAAAAElFTkSuQmCC"> + </div> + </div> + </div> + </div> + </div> + </div> + <div class="span6"> + <div align="center" style="min-height: 32px;"> + <div class="clearfix"> + <div class="btn-group inline pull-left" id="texteditor" style="display:none"> + <button id="font-family" class="btn dropdown-toggle" data-toggle="dropdown" title="Font Style"><i class="icon-font" style="width:19px;height:19px;"></i></button> + <ul class="dropdown-menu" role="menu" aria-labelledby="font-family-X"> + <li><a tabindex="-1" href="#" onclick="setFont('Arial');" class="Arial">Arial</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Helvetica');" class="Helvetica">Helvetica</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Myriad Pro');" class="MyriadPro">Myriad Pro</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Delicious');" class="Delicious">Delicious</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Verdana');" class="Verdana">Verdana</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Georgia');" class="Georgia">Georgia</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Courier');" class="Courier">Courier</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Comic Sans MS');" class="ComicSansMS">Comic Sans MS</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Impact');" class="Impact">Impact</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Monaco');" class="Monaco">Monaco</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Optima');" class="Optima">Optima</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Hoefler Text');" class="Hoefler Text">Hoefler Text</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Plaster');" class="Plaster">Plaster</a></li> + <li><a tabindex="-1" href="#" onclick="setFont('Engagement');" class="Engagement">Engagement</a></li> + </ul> + <button id="text-bold" class="btn" data-original-title="Bold"><img src="img/font_bold.png" height="" width=""></button> + <button id="text-italic" class="btn" data-original-title="Italic"><img src="img/font_italic.png" height="" width=""></button> + <button id="text-strike" class="btn" title="Strike" style=""><img src="img/font_strikethrough.png" height="" width=""></button> + <button id="text-underline" class="btn" title="Underline" style=""><img src="img/font_underline.png"></button> + <a class="btn" href="#" rel="tooltip" data-placement="top" data-original-title="Font Color"><input type="hidden" id="text-fontcolor" class="color-picker" size="7" value="#000000"></a> + <a class="btn" href="#" rel="tooltip" data-placement="top" data-original-title="Font Border Color"><input type="hidden" id="text-strokecolor" class="color-picker" size="7" value="#000000"></a> + <!--- Background <input type="hidden" id="text-bgcolor" class="color-picker" size="7" value="#ffffff"> ---> + </div> + <div class="pull-right" align="" id="imageeditor" style="display:none"> + <div class="btn-group"> + <button class="btn" id="bring-to-front" title="Bring to Front"><i class="icon-fast-backward rotate" style="height:19px;"></i></button> + <button class="btn" id="send-to-back" title="Send to Back"><i class="icon-fast-forward rotate" style="height:19px;"></i></button> + <button id="flip" type="button" class="btn" title="Show Back View"><i class="icon-retweet" style="height:19px;"></i></button> + <button id="remove-selected" class="btn" title="Delete selected item"><i class="icon-trash" style="height:19px;"></i></button> + </div> + </div> + </div> + </div> + <!-- EDITOR --> + <div id="shirtDiv" class="page" style="width: 530px; height: 630px; position: relative; background-color: rgb(255, 255, 255);"> + <img id="tshirtFacing" src="img/crew_front.png"></img> + <div id="drawingArea" style="position: absolute;top: 100px;left: 160px;z-index: 10;width: 200px;height: 400px;"> + <canvas id="tcanvas" width=200 height="400" class="hover" style="-webkit-user-select: none;"></canvas> + </div> + </div> +<!-- <div id="shirtBack" class="page" style="width: 530px; height: 630px; position: relative; background-color: rgb(255, 255, 255); display:none;">--> +<!-- <img src="img/crew_back.png"></img>--> +<!-- <div id="drawingArea" style="position: absolute;top: 100px;left: 160px;z-index: 10;width: 200px;height: 400px;"> --> +<!-- <canvas id="backCanvas" width=200 height="400" class="hover" style="-webkit-user-select: none;"></canvas>--> +<!-- </div>--> +<!-- </div> --> + + <!-- /EDITOR --> + </div> + + <div class="span3"> + <div class="well"> + <h3>Total Prices</h3> + <p> + <table class="table"> + <tr> + <td>Short Sleeve</td> + <td align="right">$12.49</td> + </tr> + <tr> + <td>Front Design</td> + <td align="right">$4.99</td> + </tr> + <tr> + <td>Back Design</td> + <td align="right">$4.99</td> + </tr> + <tr> + <td><strong>Total</strong></td> + <td align="right"><strong>$22.47</strong></td> + </tr> + </table> + </p> + <button type="button" class="btn btn-large btn-block btn-success" name="addToTheBag" id="addToTheBag">Add to bag <i class="icon-briefcase icon-white"></i></button> + </div> + </div> + + </div> + + </section> + </div><!-- /container --> + +<!-- Footer ================================================== --> + <footer class="footer"> + <div class="container"> + <p class="pull-right"><a href="#">Back to top</a></p> + </div> + </footer> + <!-- Le javascript + ================================================== --> + <!-- Placed at the end of the document so the pages load faster --> + <script src="js/bootstrap.min.js"></script> + <script type="text/javascript"> + + var _gaq = _gaq || []; + _gaq.push(['_setAccount', 'UA-35639689-1']); + _gaq.push(['_trackPageview']); + + (function() { + var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; + ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; + var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); + })(); + +</script> + </body> +</html>
\ No newline at end of file diff --git a/js/bootstrap.js b/js/bootstrap.js new file mode 100644 index 0000000..f73fcb8 --- /dev/null +++ b/js/bootstrap.js @@ -0,0 +1,2027 @@ +/* =================================================== + * bootstrap-transition.js v2.1.1 + * http://twitter.github.com/bootstrap/javascript.html#transitions + * =================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + + +!function ($) { + + $(function () { + + "use strict"; // jshint ;_; + + + /* CSS TRANSITION SUPPORT (http://www.modernizr.com/) + * ======================================================= */ + + $.support.transition = (function () { + + var transitionEnd = (function () { + + var el = document.createElement('bootstrap') + , transEndEventNames = { + 'WebkitTransition' : 'webkitTransitionEnd' + , 'MozTransition' : 'transitionend' + , 'OTransition' : 'oTransitionEnd otransitionend' + , 'transition' : 'transitionend' + } + , name + + for (name in transEndEventNames){ + if (el.style[name] !== undefined) { + return transEndEventNames[name] + } + } + + }()) + + return transitionEnd && { + end: transitionEnd + } + + })() + + }) + +}(window.jQuery);/* ========================================================== + * bootstrap-alert.js v2.1.1 + * http://twitter.github.com/bootstrap/javascript.html#alerts + * ========================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* ALERT CLASS DEFINITION + * ====================== */ + + var dismiss = '[data-dismiss="alert"]' + , Alert = function (el) { + $(el).on('click', dismiss, this.close) + } + + Alert.prototype.close = function (e) { + var $this = $(this) + , selector = $this.attr('data-target') + , $parent + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 + } + + $parent = $(selector) + + e && e.preventDefault() + + $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent()) + + $parent.trigger(e = $.Event('close')) + + if (e.isDefaultPrevented()) return + + $parent.removeClass('in') + + function removeElement() { + $parent + .trigger('closed') + .remove() + } + + $.support.transition && $parent.hasClass('fade') ? + $parent.on($.support.transition.end, removeElement) : + removeElement() + } + + + /* ALERT PLUGIN DEFINITION + * ======================= */ + + $.fn.alert = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('alert') + if (!data) $this.data('alert', (data = new Alert(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + $.fn.alert.Constructor = Alert + + + /* ALERT DATA-API + * ============== */ + + $(function () { + $('body').on('click.alert.data-api', dismiss, Alert.prototype.close) + }) + +}(window.jQuery);/* ============================================================ + * bootstrap-button.js v2.1.1 + * http://twitter.github.com/bootstrap/javascript.html#buttons + * ============================================================ + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* BUTTON PUBLIC CLASS DEFINITION + * ============================== */ + + var Button = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, $.fn.button.defaults, options) + } + + Button.prototype.setState = function (state) { + var d = 'disabled' + , $el = this.$element + , data = $el.data() + , val = $el.is('input') ? 'val' : 'html' + + state = state + 'Text' + data.resetText || $el.data('resetText', $el[val]()) + + $el[val](data[state] || this.options[state]) + + // push to event loop to allow forms to submit + setTimeout(function () { + state == 'loadingText' ? + $el.addClass(d).attr(d, d) : + $el.removeClass(d).removeAttr(d) + }, 0) + } + + Button.prototype.toggle = function () { + var $parent = this.$element.closest('[data-toggle="buttons-radio"]') + + $parent && $parent + .find('.active') + .removeClass('active') + + this.$element.toggleClass('active') + } + + + /* BUTTON PLUGIN DEFINITION + * ======================== */ + + $.fn.button = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('button') + , options = typeof option == 'object' && option + if (!data) $this.data('button', (data = new Button(this, options))) + if (option == 'toggle') data.toggle() + else if (option) data.setState(option) + }) + } + + $.fn.button.defaults = { + loadingText: 'loading...' + } + + $.fn.button.Constructor = Button + + + /* BUTTON DATA-API + * =============== */ + + $(function () { + $('body').on('click.button.data-api', '[data-toggle^=button]', function ( e ) { + var $btn = $(e.target) + if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') + $btn.button('toggle') + }) + }) + +}(window.jQuery);/* ========================================================== + * bootstrap-carousel.js v2.1.1 + * http://twitter.github.com/bootstrap/javascript.html#carousel + * ========================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* CAROUSEL CLASS DEFINITION + * ========================= */ + + var Carousel = function (element, options) { + this.$element = $(element) + this.options = options + this.options.slide && this.slide(this.options.slide) + this.options.pause == 'hover' && this.$element + .on('mouseenter', $.proxy(this.pause, this)) + .on('mouseleave', $.proxy(this.cycle, this)) + } + + Carousel.prototype = { + + cycle: function (e) { + if (!e) this.paused = false + this.options.interval + && !this.paused + && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) + return this + } + + , to: function (pos) { + var $active = this.$element.find('.item.active') + , children = $active.parent().children() + , activePos = children.index($active) + , that = this + + if (pos > (children.length - 1) || pos < 0) return + + if (this.sliding) { + return this.$element.one('slid', function () { + that.to(pos) + }) + } + + if (activePos == pos) { + return this.pause().cycle() + } + + return this.slide(pos > activePos ? 'next' : 'prev', $(children[pos])) + } + + , pause: function (e) { + if (!e) this.paused = true + if (this.$element.find('.next, .prev').length && $.support.transition.end) { + this.$element.trigger($.support.transition.end) + this.cycle() + } + clearInterval(this.interval) + this.interval = null + return this + } + + , next: function () { + if (this.sliding) return + return this.slide('next') + } + + , prev: function () { + if (this.sliding) return + return this.slide('prev') + } + + , slide: function (type, next) { + var $active = this.$element.find('.item.active') + , $next = next || $active[type]() + , isCycling = this.interval + , direction = type == 'next' ? 'left' : 'right' + , fallback = type == 'next' ? 'first' : 'last' + , that = this + , e = $.Event('slide', { + relatedTarget: $next[0] + }) + + this.sliding = true + + isCycling && this.pause() + + $next = $next.length ? $next : this.$element.find('.item')[fallback]() + + if ($next.hasClass('active')) return + + if ($.support.transition && this.$element.hasClass('slide')) { + this.$element.trigger(e) + if (e.isDefaultPrevented()) return + $next.addClass(type) + $next[0].offsetWidth // force reflow + $active.addClass(direction) + $next.addClass(direction) + this.$element.one($.support.transition.end, function () { + $next.removeClass([type, direction].join(' ')).addClass('active') + $active.removeClass(['active', direction].join(' ')) + that.sliding = false + setTimeout(function () { that.$element.trigger('slid') }, 0) + }) + } else { + this.$element.trigger(e) + if (e.isDefaultPrevented()) return + $active.removeClass('active') + $next.addClass('active') + this.sliding = false + this.$element.trigger('slid') + } + + isCycling && this.cycle() + + return this + } + + } + + + /* CAROUSEL PLUGIN DEFINITION + * ========================== */ + + $.fn.carousel = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('carousel') + , options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option) + , action = typeof option == 'string' ? option : options.slide + if (!data) $this.data('carousel', (data = new Carousel(this, options))) + if (typeof option == 'number') data.to(option) + else if (action) data[action]() + else if (options.interval) data.cycle() + }) + } + + $.fn.carousel.defaults = { + interval: 5000 + , pause: 'hover' + } + + $.fn.carousel.Constructor = Carousel + + + /* CAROUSEL DATA-API + * ================= */ + + $(function () { + $('body').on('click.carousel.data-api', '[data-slide]', function ( e ) { + var $this = $(this), href + , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 + , options = !$target.data('modal') && $.extend({}, $target.data(), $this.data()) + $target.carousel(options) + e.preventDefault() + }) + }) + +}(window.jQuery);/* ============================================================= + * bootstrap-collapse.js v2.1.1 + * http://twitter.github.com/bootstrap/javascript.html#collapse + * ============================================================= + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* COLLAPSE PUBLIC CLASS DEFINITION + * ================================ */ + + var Collapse = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, $.fn.collapse.defaults, options) + + if (this.options.parent) { + this.$parent = $(this.options.parent) + } + + this.options.toggle && this.toggle() + } + + Collapse.prototype = { + + constructor: Collapse + + , dimension: function () { + var hasWidth = this.$element.hasClass('width') + return hasWidth ? 'width' : 'height' + } + + , show: function () { + var dimension + , scroll + , actives + , hasData + + if (this.transitioning) return + + dimension = this.dimension() + scroll = $.camelCase(['scroll', dimension].join('-')) + actives = this.$parent && this.$parent.find('> .accordion-group > .in') + + if (actives && actives.length) { + hasData = actives.data('collapse') + if (hasData && hasData.transitioning) return + actives.collapse('hide') + hasData || actives.data('collapse', null) + } + + this.$element[dimension](0) + this.transition('addClass', $.Event('show'), 'shown') + $.support.transition && this.$element[dimension](this.$element[0][scroll]) + } + + , hide: function () { + var dimension + if (this.transitioning) return + dimension = this.dimension() + this.reset(this.$element[dimension]()) + this.transition('removeClass', $.Event('hide'), 'hidden') + this.$element[dimension](0) + } + + , reset: function (size) { + var dimension = this.dimension() + + this.$element + .removeClass('collapse') + [dimension](size || 'auto') + [0].offsetWidth + + this.$element[size !== null ? 'addClass' : 'removeClass']('collapse') + + return this + } + + , transition: function (method, startEvent, completeEvent) { + var that = this + , complete = function () { + if (startEvent.type == 'show') that.reset() + that.transitioning = 0 + that.$element.trigger(completeEvent) + } + + this.$element.trigger(startEvent) + + if (startEvent.isDefaultPrevented()) return + + this.transitioning = 1 + + this.$element[method]('in') + + $.support.transition && this.$element.hasClass('collapse') ? + this.$element.one($.support.transition.end, complete) : + complete() + } + + , toggle: function () { + this[this.$element.hasClass('in') ? 'hide' : 'show']() + } + + } + + + /* COLLAPSIBLE PLUGIN DEFINITION + * ============================== */ + + $.fn.collapse = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('collapse') + , options = typeof option == 'object' && option + if (!data) $this.data('collapse', (data = new Collapse(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.collapse.defaults = { + toggle: true + } + + $.fn.collapse.Constructor = Collapse + + + /* COLLAPSIBLE DATA-API + * ==================== */ + + $(function () { + $('body').on('click.collapse.data-api', '[data-toggle=collapse]', function (e) { + var $this = $(this), href + , target = $this.attr('data-target') + || e.preventDefault() + || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 + , option = $(target).data('collapse') ? 'toggle' : $this.data() + $this[$(target).hasClass('in') ? 'addClass' : 'removeClass']('collapsed') + $(target).collapse(option) + }) + }) + +}(window.jQuery);/* ============================================================ + * bootstrap-dropdown.js v2.1.1 + * http://twitter.github.com/bootstrap/javascript.html#dropdowns + * ============================================================ + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* DROPDOWN CLASS DEFINITION + * ========================= */ + + var toggle = '[data-toggle=dropdown]' + , Dropdown = function (element) { + var $el = $(element).on('click.dropdown.data-api', this.toggle) + $('html').on('click.dropdown.data-api', function () { + $el.parent().removeClass('open') + }) + } + + Dropdown.prototype = { + + constructor: Dropdown + + , toggle: function (e) { + var $this = $(this) + , $parent + , isActive + + if ($this.is('.disabled, :disabled')) return + + $parent = getParent($this) + + isActive = $parent.hasClass('open') + + clearMenus() + + if (!isActive) { + $parent.toggleClass('open') + $this.focus() + } + + return false + } + + , keydown: function (e) { + var $this + , $items + , $active + , $parent + , isActive + , index + + if (!/(38|40|27)/.test(e.keyCode)) return + + $this = $(this) + + e.preventDefault() + e.stopPropagation() + + if ($this.is('.disabled, :disabled')) return + + $parent = getParent($this) + + isActive = $parent.hasClass('open') + + if (!isActive || (isActive && e.keyCode == 27)) return $this.click() + + $items = $('[role=menu] li:not(.divider) a', $parent) + + if (!$items.length) return + + index = $items.index($items.filter(':focus')) + + if (e.keyCode == 38 && index > 0) index-- // up + if (e.keyCode == 40 && index < $items.length - 1) index++ // down + if (!~index) index = 0 + + $items + .eq(index) + .focus() + } + + } + + function clearMenus() { + getParent($(toggle)) + .removeClass('open') + } + + function getParent($this) { + var selector = $this.attr('data-target') + , $parent + + if (!selector) { + selector = $this.attr('href') + selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 + } + + $parent = $(selector) + $parent.length || ($parent = $this.parent()) + + return $parent + } + + + /* DROPDOWN PLUGIN DEFINITION + * ========================== */ + + $.fn.dropdown = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('dropdown') + if (!data) $this.data('dropdown', (data = new Dropdown(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + $.fn.dropdown.Constructor = Dropdown + + + /* APPLY TO STANDARD DROPDOWN ELEMENTS + * =================================== */ + + $(function () { + $('html') + .on('click.dropdown.data-api touchstart.dropdown.data-api', clearMenus) + $('body') + .on('click.dropdown touchstart.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) + .on('click.dropdown.data-api touchstart.dropdown.data-api' , toggle, Dropdown.prototype.toggle) + .on('keydown.dropdown.data-api touchstart.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown) + }) + +}(window.jQuery);/* ========================================================= + * bootstrap-modal.js v2.1.1 + * http://twitter.github.com/bootstrap/javascript.html#modals + * ========================================================= + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================= */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* MODAL CLASS DEFINITION + * ====================== */ + + var Modal = function (element, options) { + this.options = options + this.$element = $(element) + .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this)) + this.options.remote && this.$element.find('.modal-body').load(this.options.remote) + } + + Modal.prototype = { + + constructor: Modal + + , toggle: function () { + return this[!this.isShown ? 'show' : 'hide']() + } + + , show: function () { + var that = this + , e = $.Event('show') + + this.$element.trigger(e) + + if (this.isShown || e.isDefaultPrevented()) return + + $('body').addClass('modal-open') + + this.isShown = true + + this.escape() + + this.backdrop(function () { + var transition = $.support.transition && that.$element.hasClass('fade') + + if (!that.$element.parent().length) { + that.$element.appendTo(document.body) //don't move modals dom position + } + + that.$element + .show() + + if (transition) { + that.$element[0].offsetWidth // force reflow + } + + that.$element + .addClass('in') + .attr('aria-hidden', false) + .focus() + + that.enforceFocus() + + transition ? + that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) : + that.$element.trigger('shown') + + }) + } + + , hide: function (e) { + e && e.preventDefault() + + var that = this + + e = $.Event('hide') + + this.$element.trigger(e) + + if (!this.isShown || e.isDefaultPrevented()) return + + this.isShown = false + + $('body').removeClass('modal-open') + + this.escape() + + $(document).off('focusin.modal') + + this.$element + .removeClass('in') + .attr('aria-hidden', true) + + $.support.transition && this.$element.hasClass('fade') ? + this.hideWithTransition() : + this.hideModal() + } + + , enforceFocus: function () { + var that = this + $(document).on('focusin.modal', function (e) { + if (that.$element[0] !== e.target && !that.$element.has(e.target).length) { + that.$element.focus() + } + }) + } + + , escape: function () { + var that = this + if (this.isShown && this.options.keyboard) { + this.$element.on('keyup.dismiss.modal', function ( e ) { + e.which == 27 && that.hide() + }) + } else if (!this.isShown) { + this.$element.off('keyup.dismiss.modal') + } + } + + , hideWithTransition: function () { + var that = this + , timeout = setTimeout(function () { + that.$element.off($.support.transition.end) + that.hideModal() + }, 500) + + this.$element.one($.support.transition.end, function () { + clearTimeout(timeout) + that.hideModal() + }) + } + + , hideModal: function (that) { + this.$element + .hide() + .trigger('hidden') + + this.backdrop() + } + + , removeBackdrop: function () { + this.$backdrop.remove() + this.$backdrop = null + } + + , backdrop: function (callback) { + var that = this + , animate = this.$element.hasClass('fade') ? 'fade' : '' + + if (this.isShown && this.options.backdrop) { + var doAnimate = $.support.transition && animate + + this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />') + .appendTo(document.body) + + if (this.options.backdrop != 'static') { + this.$backdrop.click($.proxy(this.hide, this)) + } + + if (doAnimate) this.$backdrop[0].offsetWidth // force reflow + + this.$backdrop.addClass('in') + + doAnimate ? + this.$backdrop.one($.support.transition.end, callback) : + callback() + + } else if (!this.isShown && this.$backdrop) { + this.$backdrop.removeClass('in') + + $.support.transition && this.$element.hasClass('fade')? + this.$backdrop.one($.support.transition.end, $.proxy(this.removeBackdrop, this)) : + this.removeBackdrop() + + } else if (callback) { + callback() + } + } + } + + + /* MODAL PLUGIN DEFINITION + * ======================= */ + + $.fn.modal = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('modal') + , options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option) + if (!data) $this.data('modal', (data = new Modal(this, options))) + if (typeof option == 'string') data[option]() + else if (options.show) data.show() + }) + } + + $.fn.modal.defaults = { + backdrop: true + , keyboard: true + , show: true + } + + $.fn.modal.Constructor = Modal + + + /* MODAL DATA-API + * ============== */ + + $(function () { + $('body').on('click.modal.data-api', '[data-toggle="modal"]', function ( e ) { + var $this = $(this) + , href = $this.attr('href') + , $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) //strip for ie7 + , option = $target.data('modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) + + e.preventDefault() + + $target + .modal(option) + .one('hide', function () { + $this.focus() + }) + }) + }) + +}(window.jQuery);/* =========================================================== + * bootstrap-tooltip.js v2.1.1 + * http://twitter.github.com/bootstrap/javascript.html#tooltips + * Inspired by the original jQuery.tipsy by Jason Frame + * =========================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* TOOLTIP PUBLIC CLASS DEFINITION + * =============================== */ + + var Tooltip = function (element, options) { + this.init('tooltip', element, options) + } + + Tooltip.prototype = { + + constructor: Tooltip + + , init: function (type, element, options) { + var eventIn + , eventOut + + this.type = type + this.$element = $(element) + this.options = this.getOptions(options) + this.enabled = true + + if (this.options.trigger == 'click') { + this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) + } else if (this.options.trigger != 'manual') { + eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus' + eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur' + this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)) + this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)) + } + + this.options.selector ? + (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : + this.fixTitle() + } + + , getOptions: function (options) { + options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data()) + + if (options.delay && typeof options.delay == 'number') { + options.delay = { + show: options.delay + , hide: options.delay + } + } + + return options + } + + , enter: function (e) { + var self = $(e.currentTarget)[this.type](this._options).data(this.type) + + if (!self.options.delay || !self.options.delay.show) return self.show() + + clearTimeout(this.timeout) + self.hoverState = 'in' + this.timeout = setTimeout(function() { + if (self.hoverState == 'in') self.show() + }, self.options.delay.show) + } + + , leave: function (e) { + var self = $(e.currentTarget)[this.type](this._options).data(this.type) + + if (this.timeout) clearTimeout(this.timeout) + if (!self.options.delay || !self.options.delay.hide) return self.hide() + + self.hoverState = 'out' + this.timeout = setTimeout(function() { + if (self.hoverState == 'out') self.hide() + }, self.options.delay.hide) + } + + , show: function () { + var $tip + , inside + , pos + , actualWidth + , actualHeight + , placement + , tp + + if (this.hasContent() && this.enabled) { + $tip = this.tip() + this.setContent() + + if (this.options.animation) { + $tip.addClass('fade') + } + + placement = typeof this.options.placement == 'function' ? + this.options.placement.call(this, $tip[0], this.$element[0]) : + this.options.placement + + inside = /in/.test(placement) + + $tip + .remove() + .css({ top: 0, left: 0, display: 'block' }) + .appendTo(inside ? this.$element : document.body) + + pos = this.getPosition(inside) + + actualWidth = $tip[0].offsetWidth + actualHeight = $tip[0].offsetHeight + + switch (inside ? placement.split(' ')[1] : placement) { + case 'bottom': + tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2} + break + case 'top': + tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2} + break + case 'left': + tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth} + break + case 'right': + tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width} + break + } + + $tip + .css(tp) + .addClass(placement) + .addClass('in') + } + } + + , setContent: function () { + var $tip = this.tip() + , title = this.getTitle() + + $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title) + $tip.removeClass('fade in top bottom left right') + } + + , hide: function () { + var that = this + , $tip = this.tip() + + $tip.removeClass('in') + + function removeWithAnimation() { + var timeout = setTimeout(function () { + $tip.off($.support.transition.end).remove() + }, 500) + + $tip.one($.support.transition.end, function () { + clearTimeout(timeout) + $tip.remove() + }) + } + + $.support.transition && this.$tip.hasClass('fade') ? + removeWithAnimation() : + $tip.remove() + + return this + } + + , fixTitle: function () { + var $e = this.$element + if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') { + $e.attr('data-original-title', $e.attr('title') || '').removeAttr('title') + } + } + + , hasContent: function () { + return this.getTitle() + } + + , getPosition: function (inside) { + return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), { + width: this.$element[0].offsetWidth + , height: this.$element[0].offsetHeight + }) + } + + , getTitle: function () { + var title + , $e = this.$element + , o = this.options + + title = $e.attr('data-original-title') + || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) + + return title + } + + , tip: function () { + return this.$tip = this.$tip || $(this.options.template) + } + + , validate: function () { + if (!this.$element[0].parentNode) { + this.hide() + this.$element = null + this.options = null + } + } + + , enable: function () { + this.enabled = true + } + + , disable: function () { + this.enabled = false + } + + , toggleEnabled: function () { + this.enabled = !this.enabled + } + + , toggle: function () { + this[this.tip().hasClass('in') ? 'hide' : 'show']() + } + + , destroy: function () { + this.hide().$element.off('.' + this.type).removeData(this.type) + } + + } + + + /* TOOLTIP PLUGIN DEFINITION + * ========================= */ + + $.fn.tooltip = function ( option ) { + return this.each(function () { + var $this = $(this) + , data = $this.data('tooltip') + , options = typeof option == 'object' && option + if (!data) $this.data('tooltip', (data = new Tooltip(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.tooltip.Constructor = Tooltip + + $.fn.tooltip.defaults = { + animation: true + , placement: 'top' + , selector: false + , template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>' + , trigger: 'hover' + , title: '' + , delay: 0 + , html: true + } + +}(window.jQuery); +/* =========================================================== + * bootstrap-popover.js v2.1.1 + * http://twitter.github.com/bootstrap/javascript.html#popovers + * =========================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * =========================================================== */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* POPOVER PUBLIC CLASS DEFINITION + * =============================== */ + + var Popover = function (element, options) { + this.init('popover', element, options) + } + + + /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js + ========================================== */ + + Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, { + + constructor: Popover + + , setContent: function () { + var $tip = this.tip() + , title = this.getTitle() + , content = this.getContent() + + $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title) + $tip.find('.popover-content > *')[this.options.html ? 'html' : 'text'](content) + + $tip.removeClass('fade top bottom left right in') + } + + , hasContent: function () { + return this.getTitle() || this.getContent() + } + + , getContent: function () { + var content + , $e = this.$element + , o = this.options + + content = $e.attr('data-content') + || (typeof o.content == 'function' ? o.content.call($e[0]) : o.content) + + return content + } + + , tip: function () { + if (!this.$tip) { + this.$tip = $(this.options.template) + } + return this.$tip + } + + , destroy: function () { + this.hide().$element.off('.' + this.type).removeData(this.type) + } + + }) + + + /* POPOVER PLUGIN DEFINITION + * ======================= */ + + $.fn.popover = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('popover') + , options = typeof option == 'object' && option + if (!data) $this.data('popover', (data = new Popover(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.popover.Constructor = Popover + + $.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, { + placement: 'right' + , trigger: 'click' + , content: '' + , template: '<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>' + }) + +}(window.jQuery);/* ============================================================= + * bootstrap-scrollspy.js v2.1.1 + * http://twitter.github.com/bootstrap/javascript.html#scrollspy + * ============================================================= + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================== */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* SCROLLSPY CLASS DEFINITION + * ========================== */ + + function ScrollSpy(element, options) { + var process = $.proxy(this.process, this) + , $element = $(element).is('body') ? $(window) : $(element) + , href + this.options = $.extend({}, $.fn.scrollspy.defaults, options) + this.$scrollElement = $element.on('scroll.scroll-spy.data-api', process) + this.selector = (this.options.target + || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 + || '') + ' .nav li > a' + this.$body = $('body') + this.refresh() + this.process() + } + + ScrollSpy.prototype = { + + constructor: ScrollSpy + + , refresh: function () { + var self = this + , $targets + + this.offsets = $([]) + this.targets = $([]) + + $targets = this.$body + .find(this.selector) + .map(function () { + var $el = $(this) + , href = $el.data('target') || $el.attr('href') + , $href = /^#\w/.test(href) && $(href) + return ( $href + && $href.length + && [[ $href.position().top, href ]] ) || null + }) + .sort(function (a, b) { return a[0] - b[0] }) + .each(function () { + self.offsets.push(this[0]) + self.targets.push(this[1]) + }) + } + + , process: function () { + var scrollTop = this.$scrollElement.scrollTop() + this.options.offset + , scrollHeight = this.$scrollElement[0].scrollHeight || this.$body[0].scrollHeight + , maxScroll = scrollHeight - this.$scrollElement.height() + , offsets = this.offsets + , targets = this.targets + , activeTarget = this.activeTarget + , i + + if (scrollTop >= maxScroll) { + return activeTarget != (i = targets.last()[0]) + && this.activate ( i ) + } + + for (i = offsets.length; i--;) { + activeTarget != targets[i] + && scrollTop >= offsets[i] + && (!offsets[i + 1] || scrollTop <= offsets[i + 1]) + && this.activate( targets[i] ) + } + } + + , activate: function (target) { + var active + , selector + + this.activeTarget = target + + $(this.selector) + .parent('.active') + .removeClass('active') + + selector = this.selector + + '[data-target="' + target + '"],' + + this.selector + '[href="' + target + '"]' + + active = $(selector) + .parent('li') + .addClass('active') + + if (active.parent('.dropdown-menu').length) { + active = active.closest('li.dropdown').addClass('active') + } + + active.trigger('activate') + } + + } + + + /* SCROLLSPY PLUGIN DEFINITION + * =========================== */ + + $.fn.scrollspy = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('scrollspy') + , options = typeof option == 'object' && option + if (!data) $this.data('scrollspy', (data = new ScrollSpy(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.scrollspy.Constructor = ScrollSpy + + $.fn.scrollspy.defaults = { + offset: 10 + } + + + /* SCROLLSPY DATA-API + * ================== */ + + $(window).on('load', function () { + $('[data-spy="scroll"]').each(function () { + var $spy = $(this) + $spy.scrollspy($spy.data()) + }) + }) + +}(window.jQuery);/* ======================================================== + * bootstrap-tab.js v2.1.1 + * http://twitter.github.com/bootstrap/javascript.html#tabs + * ======================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ======================================================== */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* TAB CLASS DEFINITION + * ==================== */ + + var Tab = function (element) { + this.element = $(element) + } + + Tab.prototype = { + + constructor: Tab + + , show: function () { + var $this = this.element + , $ul = $this.closest('ul:not(.dropdown-menu)') + , selector = $this.attr('data-target') + , previous + , $target + , e + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 + } + + if ( $this.parent('li').hasClass('active') ) return + + previous = $ul.find('.active a').last()[0] + + e = $.Event('show', { + relatedTarget: previous + }) + + $this.trigger(e) + + if (e.isDefaultPrevented()) return + + $target = $(selector) + + this.activate($this.parent('li'), $ul) + this.activate($target, $target.parent(), function () { + $this.trigger({ + type: 'shown' + , relatedTarget: previous + }) + }) + } + + , activate: function ( element, container, callback) { + var $active = container.find('> .active') + , transition = callback + && $.support.transition + && $active.hasClass('fade') + + function next() { + $active + .removeClass('active') + .find('> .dropdown-menu > .active') + .removeClass('active') + + element.addClass('active') + + if (transition) { + element[0].offsetWidth // reflow for transition + element.addClass('in') + } else { + element.removeClass('fade') + } + + if ( element.parent('.dropdown-menu') ) { + element.closest('li.dropdown').addClass('active') + } + + callback && callback() + } + + transition ? + $active.one($.support.transition.end, next) : + next() + + $active.removeClass('in') + } + } + + + /* TAB PLUGIN DEFINITION + * ===================== */ + + $.fn.tab = function ( option ) { + return this.each(function () { + var $this = $(this) + , data = $this.data('tab') + if (!data) $this.data('tab', (data = new Tab(this))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.tab.Constructor = Tab + + + /* TAB DATA-API + * ============ */ + + $(function () { + $('body').on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) { + e.preventDefault() + $(this).tab('show') + }) + }) + +}(window.jQuery);/* ============================================================= + * bootstrap-typeahead.js v2.1.1 + * http://twitter.github.com/bootstrap/javascript.html#typeahead + * ============================================================= + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + + +!function($){ + + "use strict"; // jshint ;_; + + + /* TYPEAHEAD PUBLIC CLASS DEFINITION + * ================================= */ + + var Typeahead = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, $.fn.typeahead.defaults, options) + this.matcher = this.options.matcher || this.matcher + this.sorter = this.options.sorter || this.sorter + this.highlighter = this.options.highlighter || this.highlighter + this.updater = this.options.updater || this.updater + this.$menu = $(this.options.menu).appendTo('body') + this.source = this.options.source + this.shown = false + this.listen() + } + + Typeahead.prototype = { + + constructor: Typeahead + + , select: function () { + var val = this.$menu.find('.active').attr('data-value') + this.$element + .val(this.updater(val)) + .change() + return this.hide() + } + + , updater: function (item) { + return item + } + + , show: function () { + var pos = $.extend({}, this.$element.offset(), { + height: this.$element[0].offsetHeight + }) + + this.$menu.css({ + top: pos.top + pos.height + , left: pos.left + }) + + this.$menu.show() + this.shown = true + return this + } + + , hide: function () { + this.$menu.hide() + this.shown = false + return this + } + + , lookup: function (event) { + var items + + this.query = this.$element.val() + + if (!this.query || this.query.length < this.options.minLength) { + return this.shown ? this.hide() : this + } + + items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source + + return items ? this.process(items) : this + } + + , process: function (items) { + var that = this + + items = $.grep(items, function (item) { + return that.matcher(item) + }) + + items = this.sorter(items) + + if (!items.length) { + return this.shown ? this.hide() : this + } + + return this.render(items.slice(0, this.options.items)).show() + } + + , matcher: function (item) { + return ~item.toLowerCase().indexOf(this.query.toLowerCase()) + } + + , sorter: function (items) { + var beginswith = [] + , caseSensitive = [] + , caseInsensitive = [] + , item + + while (item = items.shift()) { + if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item) + else if (~item.indexOf(this.query)) caseSensitive.push(item) + else caseInsensitive.push(item) + } + + return beginswith.concat(caseSensitive, caseInsensitive) + } + + , highlighter: function (item) { + var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&') + return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) { + return '<strong>' + match + '</strong>' + }) + } + + , render: function (items) { + var that = this + + items = $(items).map(function (i, item) { + i = $(that.options.item).attr('data-value', item) + i.find('a').html(that.highlighter(item)) + return i[0] + }) + + items.first().addClass('active') + this.$menu.html(items) + return this + } + + , next: function (event) { + var active = this.$menu.find('.active').removeClass('active') + , next = active.next() + + if (!next.length) { + next = $(this.$menu.find('li')[0]) + } + + next.addClass('active') + } + + , prev: function (event) { + var active = this.$menu.find('.active').removeClass('active') + , prev = active.prev() + + if (!prev.length) { + prev = this.$menu.find('li').last() + } + + prev.addClass('active') + } + + , listen: function () { + this.$element + .on('blur', $.proxy(this.blur, this)) + .on('keypress', $.proxy(this.keypress, this)) + .on('keyup', $.proxy(this.keyup, this)) + + if ($.browser.chrome || $.browser.webkit || $.browser.msie) { + this.$element.on('keydown', $.proxy(this.keydown, this)) + } + + this.$menu + .on('click', $.proxy(this.click, this)) + .on('mouseenter', 'li', $.proxy(this.mouseenter, this)) + } + + , move: function (e) { + if (!this.shown) return + + switch(e.keyCode) { + case 9: // tab + case 13: // enter + case 27: // escape + e.preventDefault() + break + + case 38: // up arrow + e.preventDefault() + this.prev() + break + + case 40: // down arrow + e.preventDefault() + this.next() + break + } + + e.stopPropagation() + } + + , keydown: function (e) { + this.suppressKeyPressRepeat = !~$.inArray(e.keyCode, [40,38,9,13,27]) + this.move(e) + } + + , keypress: function (e) { + if (this.suppressKeyPressRepeat) return + this.move(e) + } + + , keyup: function (e) { + switch(e.keyCode) { + case 40: // down arrow + case 38: // up arrow + break + + case 9: // tab + case 13: // enter + if (!this.shown) return + this.select() + break + + case 27: // escape + if (!this.shown) return + this.hide() + break + + default: + this.lookup() + } + + e.stopPropagation() + e.preventDefault() + } + + , blur: function (e) { + var that = this + setTimeout(function () { that.hide() }, 150) + } + + , click: function (e) { + e.stopPropagation() + e.preventDefault() + this.select() + } + + , mouseenter: function (e) { + this.$menu.find('.active').removeClass('active') + $(e.currentTarget).addClass('active') + } + + } + + + /* TYPEAHEAD PLUGIN DEFINITION + * =========================== */ + + $.fn.typeahead = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('typeahead') + , options = typeof option == 'object' && option + if (!data) $this.data('typeahead', (data = new Typeahead(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.typeahead.defaults = { + source: [] + , items: 8 + , menu: '<ul class="typeahead dropdown-menu"></ul>' + , item: '<li><a href="#"></a></li>' + , minLength: 1 + } + + $.fn.typeahead.Constructor = Typeahead + + + /* TYPEAHEAD DATA-API + * ================== */ + + $(function () { + $('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) { + var $this = $(this) + if ($this.data('typeahead')) return + e.preventDefault() + $this.typeahead($this.data()) + }) + }) + +}(window.jQuery); +/* ========================================================== + * bootstrap-affix.js v2.1.1 + * http://twitter.github.com/bootstrap/javascript.html#affix + * ========================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* AFFIX CLASS DEFINITION + * ====================== */ + + var Affix = function (element, options) { + this.options = $.extend({}, $.fn.affix.defaults, options) + this.$window = $(window).on('scroll.affix.data-api', $.proxy(this.checkPosition, this)) + this.$element = $(element) + this.checkPosition() + } + + Affix.prototype.checkPosition = function () { + if (!this.$element.is(':visible')) return + + var scrollHeight = $(document).height() + , scrollTop = this.$window.scrollTop() + , position = this.$element.offset() + , offset = this.options.offset + , offsetBottom = offset.bottom + , offsetTop = offset.top + , reset = 'affix affix-top affix-bottom' + , affix + + if (typeof offset != 'object') offsetBottom = offsetTop = offset + if (typeof offsetTop == 'function') offsetTop = offset.top() + if (typeof offsetBottom == 'function') offsetBottom = offset.bottom() + + affix = this.unpin != null && (scrollTop + this.unpin <= position.top) ? + false : offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ? + 'bottom' : offsetTop != null && scrollTop <= offsetTop ? + 'top' : false + + if (this.affixed === affix) return + + this.affixed = affix + this.unpin = affix == 'bottom' ? position.top - scrollTop : null + + this.$element.removeClass(reset).addClass('affix' + (affix ? '-' + affix : '')) + } + + + /* AFFIX PLUGIN DEFINITION + * ======================= */ + + $.fn.affix = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('affix') + , options = typeof option == 'object' && option + if (!data) $this.data('affix', (data = new Affix(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.affix.Constructor = Affix + + $.fn.affix.defaults = { + offset: 0 + } + + + /* AFFIX DATA-API + * ============== */ + + $(window).on('load', function () { + $('[data-spy="affix"]').each(function () { + var $spy = $(this) + , data = $spy.data() + + data.offset = data.offset || {} + + data.offsetBottom && (data.offset.bottom = data.offsetBottom) + data.offsetTop && (data.offset.top = data.offsetTop) + + $spy.affix(data) + }) + }) + + +}(window.jQuery);
\ No newline at end of file diff --git a/js/bootstrap.min.js b/js/bootstrap.min.js new file mode 100644 index 0000000..0e33fb1 --- /dev/null +++ b/js/bootstrap.min.js @@ -0,0 +1,6 @@ +/*! +* Bootstrap.js by @fat & @mdo +* Copyright 2012 Twitter, Inc. +* http://www.apache.org/licenses/LICENSE-2.0.txt +*/ +!function(e){e(function(){"use strict";e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()},e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e(function(){e("body").on("click.alert.data-api",t,n.prototype.close)})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")},e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e(function(){e("body").on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=n,this.options.slide&&this.slide(this.options.slide),this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},to:function(t){var n=this.$element.find(".item.active"),r=n.parent().children(),i=r.index(n),s=this;if(t>r.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){s.to(t)}):i==t?this.pause().cycle():this.slide(t>i?"next":"prev",e(r[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f=e.Event("slide",{relatedTarget:i[0]});this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u]();if(i.hasClass("active"))return;if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}},e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e(function(){e("body").on("click.carousel.data-api","[data-slide]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=!i.data("modal")&&e.extend({},i.data(),n.data());i.carousel(s),t.preventDefault()})})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning)return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning)return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}},e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=typeof n=="object"&&n;i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e(function(){e("body").on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})})}(window.jQuery),!function(e){"use strict";function r(){i(e(t)).removeClass("open")}function i(t){var n=t.attr("data-target"),r;return n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=e(n),r.length||(r=t.parent()),r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||(s.toggleClass("open"),n.focus()),!1},keydown:function(t){var n,r,s,o,u,a;if(!/(38|40|27)/.test(t.keyCode))return;n=e(this),t.preventDefault(),t.stopPropagation();if(n.is(".disabled, :disabled"))return;o=i(n),u=o.hasClass("open");if(!u||u&&t.keyCode==27)return n.click();r=e("[role=menu] li:not(.divider) a",o);if(!r.length)return;a=r.index(r.filter(":focus")),t.keyCode==38&&a>0&&a--,t.keyCode==40&&a<r.length-1&&a++,~a||(a=0),r.eq(a).focus()}},e.fn.dropdown=function(t){return this.each(function(){var r=e(this),i=r.data("dropdown");i||r.data("dropdown",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.dropdown.Constructor=n,e(function(){e("html").on("click.dropdown.data-api touchstart.dropdown.data-api",r),e("body").on("click.dropdown touchstart.dropdown.data-api",".dropdown form",function(e){e.stopPropagation()}).on("click.dropdown.data-api touchstart.dropdown.data-api",t,n.prototype.toggle).on("keydown.dropdown.data-api touchstart.dropdown.data-api",t+", [role=menu]",n.prototype.keydown)})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=n,this.$element=e(t).delegate('[data-dismiss="modal"]',"click.dismiss.modal",e.proxy(this.hide,this)),this.options.remote&&this.$element.find(".modal-body").load(this.options.remote)};t.prototype={constructor:t,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var t=this,n=e.Event("show");this.$element.trigger(n);if(this.isShown||n.isDefaultPrevented())return;e("body").addClass("modal-open"),this.isShown=!0,this.escape(),this.backdrop(function(){var n=e.support.transition&&t.$element.hasClass("fade");t.$element.parent().length||t.$element.appendTo(document.body),t.$element.show(),n&&t.$element[0].offsetWidth,t.$element.addClass("in").attr("aria-hidden",!1).focus(),t.enforceFocus(),n?t.$element.one(e.support.transition.end,function(){t.$element.trigger("shown")}):t.$element.trigger("shown")})},hide:function(t){t&&t.preventDefault();var n=this;t=e.Event("hide"),this.$element.trigger(t);if(!this.isShown||t.isDefaultPrevented())return;this.isShown=!1,e("body").removeClass("modal-open"),this.escape(),e(document).off("focusin.modal"),this.$element.removeClass("in").attr("aria-hidden",!0),e.support.transition&&this.$element.hasClass("fade")?this.hideWithTransition():this.hideModal()},enforceFocus:function(){var t=this;e(document).on("focusin.modal",function(e){t.$element[0]!==e.target&&!t.$element.has(e.target).length&&t.$element.focus()})},escape:function(){var e=this;this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.modal",function(t){t.which==27&&e.hide()}):this.isShown||this.$element.off("keyup.dismiss.modal")},hideWithTransition:function(){var t=this,n=setTimeout(function(){t.$element.off(e.support.transition.end),t.hideModal()},500);this.$element.one(e.support.transition.end,function(){clearTimeout(n),t.hideModal()})},hideModal:function(e){this.$element.hide().trigger("hidden"),this.backdrop()},removeBackdrop:function(){this.$backdrop.remove(),this.$backdrop=null},backdrop:function(t){var n=this,r=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var i=e.support.transition&&r;this.$backdrop=e('<div class="modal-backdrop '+r+'" />').appendTo(document.body),this.options.backdrop!="static"&&this.$backdrop.click(e.proxy(this.hide,this)),i&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),i?this.$backdrop.one(e.support.transition.end,t):t()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),e.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(e.support.transition.end,e.proxy(this.removeBackdrop,this)):this.removeBackdrop()):t&&t()}},e.fn.modal=function(n){return this.each(function(){var r=e(this),i=r.data("modal"),s=e.extend({},e.fn.modal.defaults,r.data(),typeof n=="object"&&n);i||r.data("modal",i=new t(this,s)),typeof n=="string"?i[n]():s.show&&i.show()})},e.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},e.fn.modal.Constructor=t,e(function(){e("body").on("click.modal.data-api",'[data-toggle="modal"]',function(t){var n=e(this),r=n.attr("href"),i=e(n.attr("data-target")||r&&r.replace(/.*(?=#[^\s]+$)/,"")),s=i.data("modal")?"toggle":e.extend({remote:!/#/.test(r)&&r},i.data(),n.data());t.preventDefault(),i.modal(s).one("hide",function(){n.focus()})})})}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("tooltip",e,t)};t.prototype={constructor:t,init:function(t,n,r){var i,s;this.type=t,this.$element=e(n),this.options=this.getOptions(r),this.enabled=!0,this.options.trigger=="click"?this.$element.on("click."+this.type,this.options.selector,e.proxy(this.toggle,this)):this.options.trigger!="manual"&&(i=this.options.trigger=="hover"?"mouseenter":"focus",s=this.options.trigger=="hover"?"mouseleave":"blur",this.$element.on(i+"."+this.type,this.options.selector,e.proxy(this.enter,this)),this.$element.on(s+"."+this.type,this.options.selector,e.proxy(this.leave,this))),this.options.selector?this._options=e.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(t){return t=e.extend({},e.fn[this.type].defaults,t,this.$element.data()),t.delay&&typeof t.delay=="number"&&(t.delay={show:t.delay,hide:t.delay}),t},enter:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);if(!n.options.delay||!n.options.delay.show)return n.show();clearTimeout(this.timeout),n.hoverState="in",this.timeout=setTimeout(function(){n.hoverState=="in"&&n.show()},n.options.delay.show)},leave:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!n.options.delay||!n.options.delay.hide)return n.hide();n.hoverState="out",this.timeout=setTimeout(function(){n.hoverState=="out"&&n.hide()},n.options.delay.hide)},show:function(){var e,t,n,r,i,s,o;if(this.hasContent()&&this.enabled){e=this.tip(),this.setContent(),this.options.animation&&e.addClass("fade"),s=typeof this.options.placement=="function"?this.options.placement.call(this,e[0],this.$element[0]):this.options.placement,t=/in/.test(s),e.remove().css({top:0,left:0,display:"block"}).appendTo(t?this.$element:document.body),n=this.getPosition(t),r=e[0].offsetWidth,i=e[0].offsetHeight;switch(t?s.split(" ")[1]:s){case"bottom":o={top:n.top+n.height,left:n.left+n.width/2-r/2};break;case"top":o={top:n.top-i,left:n.left+n.width/2-r/2};break;case"left":o={top:n.top+n.height/2-i/2,left:n.left-r};break;case"right":o={top:n.top+n.height/2-i/2,left:n.left+n.width}}e.css(o).addClass(s).addClass("in")}},setContent:function(){var e=this.tip(),t=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](t),e.removeClass("fade in top bottom left right")},hide:function(){function r(){var t=setTimeout(function(){n.off(e.support.transition.end).remove()},500);n.one(e.support.transition.end,function(){clearTimeout(t),n.remove()})}var t=this,n=this.tip();return n.removeClass("in"),e.support.transition&&this.$tip.hasClass("fade")?r():n.remove(),this},fixTitle:function(){var e=this.$element;(e.attr("title")||typeof e.attr("data-original-title")!="string")&&e.attr("data-original-title",e.attr("title")||"").removeAttr("title")},hasContent:function(){return this.getTitle()},getPosition:function(t){return e.extend({},t?{top:0,left:0}:this.$element.offset(),{width:this.$element[0].offsetWidth,height:this.$element[0].offsetHeight})},getTitle:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-original-title")||(typeof n.title=="function"?n.title.call(t[0]):n.title),e},tip:function(){return this.$tip=this.$tip||e(this.options.template)},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(){this[this.tip().hasClass("in")?"hide":"show"]()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}},e.fn.tooltip=function(n){return this.each(function(){var r=e(this),i=r.data("tooltip"),s=typeof n=="object"&&n;i||r.data("tooltip",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.tooltip.Constructor=t,e.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover",title:"",delay:0,html:!0}}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("popover",e,t)};t.prototype=e.extend({},e.fn.tooltip.Constructor.prototype,{constructor:t,setContent:function(){var e=this.tip(),t=this.getTitle(),n=this.getContent();e.find(".popover-title")[this.options.html?"html":"text"](t),e.find(".popover-content > *")[this.options.html?"html":"text"](n),e.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-content")||(typeof n.content=="function"?n.content.call(t[0]):n.content),e},tip:function(){return this.$tip||(this.$tip=e(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}}),e.fn.popover=function(n){return this.each(function(){var r=e(this),i=r.data("popover"),s=typeof n=="object"&&n;i||r.data("popover",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.popover.Constructor=t,e.fn.popover.defaults=e.extend({},e.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:'<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>'})}(window.jQuery),!function(e){"use strict";function t(t,n){var r=e.proxy(this.process,this),i=e(t).is("body")?e(window):e(t),s;this.options=e.extend({},e.fn.scrollspy.defaults,n),this.$scrollElement=i.on("scroll.scroll-spy.data-api",r),this.selector=(this.options.target||(s=e(t).attr("href"))&&s.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=e("body"),this.refresh(),this.process()}t.prototype={constructor:t,refresh:function(){var t=this,n;this.offsets=e([]),this.targets=e([]),n=this.$body.find(this.selector).map(function(){var t=e(this),n=t.data("target")||t.attr("href"),r=/^#\w/.test(n)&&e(n);return r&&r.length&&[[r.position().top,n]]||null}).sort(function(e,t){return e[0]-t[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},process:function(){var e=this.$scrollElement.scrollTop()+this.options.offset,t=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,n=t-this.$scrollElement.height(),r=this.offsets,i=this.targets,s=this.activeTarget,o;if(e>=n)return s!=(o=i.last()[0])&&this.activate(o);for(o=r.length;o--;)s!=i[o]&&e>=r[o]&&(!r[o+1]||e<=r[o+1])&&this.activate(i[o])},activate:function(t){var n,r;this.activeTarget=t,e(this.selector).parent(".active").removeClass("active"),r=this.selector+'[data-target="'+t+'"],'+this.selector+'[href="'+t+'"]',n=e(r).parent("li").addClass("active"),n.parent(".dropdown-menu").length&&(n=n.closest("li.dropdown").addClass("active")),n.trigger("activate")}},e.fn.scrollspy=function(n){return this.each(function(){var r=e(this),i=r.data("scrollspy"),s=typeof n=="object"&&n;i||r.data("scrollspy",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.scrollspy.Constructor=t,e.fn.scrollspy.defaults={offset:10},e(window).on("load",function(){e('[data-spy="scroll"]').each(function(){var t=e(this);t.scrollspy(t.data())})})}(window.jQuery),!function(e){"use strict";var t=function(t){this.element=e(t)};t.prototype={constructor:t,show:function(){var t=this.element,n=t.closest("ul:not(.dropdown-menu)"),r=t.attr("data-target"),i,s,o;r||(r=t.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,""));if(t.parent("li").hasClass("active"))return;i=n.find(".active a").last()[0],o=e.Event("show",{relatedTarget:i}),t.trigger(o);if(o.isDefaultPrevented())return;s=e(r),this.activate(t.parent("li"),n),this.activate(s,s.parent(),function(){t.trigger({type:"shown",relatedTarget:i})})},activate:function(t,n,r){function o(){i.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),t.addClass("active"),s?(t[0].offsetWidth,t.addClass("in")):t.removeClass("fade"),t.parent(".dropdown-menu")&&t.closest("li.dropdown").addClass("active"),r&&r()}var i=n.find("> .active"),s=r&&e.support.transition&&i.hasClass("fade");s?i.one(e.support.transition.end,o):o(),i.removeClass("in")}},e.fn.tab=function(n){return this.each(function(){var r=e(this),i=r.data("tab");i||r.data("tab",i=new t(this)),typeof n=="string"&&i[n]()})},e.fn.tab.Constructor=t,e(function(){e("body").on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(t){t.preventDefault(),e(this).tab("show")})})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.typeahead.defaults,n),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.$menu=e(this.options.menu).appendTo("body"),this.source=this.options.source,this.shown=!1,this.listen()};t.prototype={constructor:t,select:function(){var e=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(e)).change(),this.hide()},updater:function(e){return e},show:function(){var t=e.extend({},this.$element.offset(),{height:this.$element[0].offsetHeight});return this.$menu.css({top:t.top+t.height,left:t.left}),this.$menu.show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(t){var n;return this.query=this.$element.val(),!this.query||this.query.length<this.options.minLength?this.shown?this.hide():this:(n=e.isFunction(this.source)?this.source(this.query,e.proxy(this.process,this)):this.source,n?this.process(n):this)},process:function(t){var n=this;return t=e.grep(t,function(e){return n.matcher(e)}),t=this.sorter(t),t.length?this.render(t.slice(0,this.options.items)).show():this.shown?this.hide():this},matcher:function(e){return~e.toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(e){var t=[],n=[],r=[],i;while(i=e.shift())i.toLowerCase().indexOf(this.query.toLowerCase())?~i.indexOf(this.query)?n.push(i):r.push(i):t.push(i);return t.concat(n,r)},highlighter:function(e){var t=this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&");return e.replace(new RegExp("("+t+")","ig"),function(e,t){return"<strong>"+t+"</strong>"})},render:function(t){var n=this;return t=e(t).map(function(t,r){return t=e(n.options.item).attr("data-value",r),t.find("a").html(n.highlighter(r)),t[0]}),t.first().addClass("active"),this.$menu.html(t),this},next:function(t){var n=this.$menu.find(".active").removeClass("active"),r=n.next();r.length||(r=e(this.$menu.find("li")[0])),r.addClass("active")},prev:function(e){var t=this.$menu.find(".active").removeClass("active"),n=t.prev();n.length||(n=this.$menu.find("li").last()),n.addClass("active")},listen:function(){this.$element.on("blur",e.proxy(this.blur,this)).on("keypress",e.proxy(this.keypress,this)).on("keyup",e.proxy(this.keyup,this)),(e.browser.chrome||e.browser.webkit||e.browser.msie)&&this.$element.on("keydown",e.proxy(this.keydown,this)),this.$menu.on("click",e.proxy(this.click,this)).on("mouseenter","li",e.proxy(this.mouseenter,this))},move:function(e){if(!this.shown)return;switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault(),this.prev();break;case 40:e.preventDefault(),this.next()}e.stopPropagation()},keydown:function(t){this.suppressKeyPressRepeat=!~e.inArray(t.keyCode,[40,38,9,13,27]),this.move(t)},keypress:function(e){if(this.suppressKeyPressRepeat)return;this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation(),e.preventDefault()},blur:function(e){var t=this;setTimeout(function(){t.hide()},150)},click:function(e){e.stopPropagation(),e.preventDefault(),this.select()},mouseenter:function(t){this.$menu.find(".active").removeClass("active"),e(t.currentTarget).addClass("active")}},e.fn.typeahead=function(n){return this.each(function(){var r=e(this),i=r.data("typeahead"),s=typeof n=="object"&&n;i||r.data("typeahead",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.typeahead.defaults={source:[],items:8,menu:'<ul class="typeahead dropdown-menu"></ul>',item:'<li><a href="#"></a></li>',minLength:1},e.fn.typeahead.Constructor=t,e(function(){e("body").on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(t){var n=e(this);if(n.data("typeahead"))return;t.preventDefault(),n.typeahead(n.data())})})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=e.extend({},e.fn.affix.defaults,n),this.$window=e(window).on("scroll.affix.data-api",e.proxy(this.checkPosition,this)),this.$element=e(t),this.checkPosition()};t.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var t=e(document).height(),n=this.$window.scrollTop(),r=this.$element.offset(),i=this.options.offset,s=i.bottom,o=i.top,u="affix affix-top affix-bottom",a;typeof i!="object"&&(s=o=i),typeof o=="function"&&(o=i.top()),typeof s=="function"&&(s=i.bottom()),a=this.unpin!=null&&n+this.unpin<=r.top?!1:s!=null&&r.top+this.$element.height()>=t-s?"bottom":o!=null&&n<=o?"top":!1;if(this.affixed===a)return;this.affixed=a,this.unpin=a=="bottom"?r.top-n:null,this.$element.removeClass(u).addClass("affix"+(a?"-"+a:""))},e.fn.affix=function(n){return this.each(function(){var r=e(this),i=r.data("affix"),s=typeof n=="object"&&n;i||r.data("affix",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.affix.Constructor=t,e.fn.affix.defaults={offset:0},e(window).on("load",function(){e('[data-spy="affix"]').each(function(){var t=e(this),n=t.data();n.offset=n.offset||{},n.offsetBottom&&(n.offset.bottom=n.offsetBottom),n.offsetTop&&(n.offset.top=n.offsetTop),t.affix(n)})})}(window.jQuery);
\ No newline at end of file diff --git a/js/caseEditor.js b/js/caseEditor.js new file mode 100644 index 0000000..5e4c46a --- /dev/null +++ b/js/caseEditor.js @@ -0,0 +1,344 @@ +var canvas; +var tshirts = new Array(); //prototype: [{style:'x',color:'white',front:'a',back:'b',price:{tshirt:'12.95',frontPrint:'4.99',backPrint:'4.99',total:'22.47'}}] +var a; +var b; +var line1; +var line2; +var line3; +var line4; + $(document).ready(function() { + //setup front side canvas + canvas = new fabric.Canvas('tcanvas', { + hoverCursor: 'pointer', + selection: true, + selectionBorderColor:'blue' + }); + canvas.on({ + 'object:moving': function(e) { + e.target.opacity = 0.5; + }, + 'object:modified': function(e) { + e.target.opacity = 1; + }, + 'object:selected':onObjectSelected, + 'selection:cleared':onSelectedCleared + }); + // piggyback on `canvas.findTarget`, to fire "object:over" and "object:out" events + canvas.findTarget = (function(originalFn) { + return function() { + var target = originalFn.apply(this, arguments); + if (target) { + if (this._hoveredTarget !== target) { + canvas.fire('object:over', { target: target }); + if (this._hoveredTarget) { + canvas.fire('object:out', { target: this._hoveredTarget }); + } + this._hoveredTarget = target; + } + } + else if (this._hoveredTarget) { + canvas.fire('object:out', { target: this._hoveredTarget }); + this._hoveredTarget = null; + } + return target; + }; + })(canvas.findTarget); + + canvas.on('object:over', function(e) { + //e.target.setFill('red'); + //canvas.renderAll(); + }); + + canvas.on('object:out', function(e) { + //e.target.setFill('green'); + //canvas.renderAll(); + }); + + document.getElementById('add-text').onclick = function() { + var text = $("#text-string").val(); + var textSample = new fabric.Text(text, { + left: fabric.util.getRandomInt(0, 200), + top: fabric.util.getRandomInt(0, 400), + fontFamily: 'helvetica', + angle: 0, + fill: '#000000', + scaleX: 0.5, + scaleY: 0.5, + fontWeight: '', + hasRotatingPoint:true + }); + canvas.add(textSample); + canvas.item(canvas.item.length-1).hasRotatingPoint = true; + $("#texteditor").css('display', 'block'); + $("#imageeditor").css('display', 'block'); + }; + $("#text-string").keyup(function(){ + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.text = this.value; + canvas.renderAll(); + } + }); + + $("#phoneTypes").change(function(e){ + debugger; + if($(this).val() == "1"){ + $("#phoneDiv").css('height','590'); + $("#phone")[0].src = "img/phones/iphone5A.png"; + //$("#borderMask")[0].src = "img/phones/iphone5Mask.png"; + line1 = new fabric.Line([0,0,225,0], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false}); + line2 = new fabric.Line([224,0,225,450], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false}); + line3 = new fabric.Line([0,0,0,450], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false}); + line4 = new fabric.Line([0,450,225,449], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false}); + } + else if ($(this).val() == "2"){ + $("#phoneDiv").css('height','540'); + $("#phone")[0].src = "img/phones/iPhone4A.png"; + //$("#borderMask")[0].src = "img/phones/iphone4Mask.png"; + line1 = new fabric.Line([0,20,220,20], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false}); + line2 = new fabric.Line([220,20,220,420], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false}); + line3 = new fabric.Line([0,20,0,420], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false}); + line4 = new fabric.Line([0,420,220,420], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false}); + } + else if ($(this).val() == "3"){ + $("#phoneDiv").css('height','535'); + $("#phone")[0].src = "img/phones/GalaxyS3A.png"; + //$("#borderMask")[0].src = "img/phones/GalaxyS3Mask.png"; + line1 = new fabric.Line([0,30,225,30], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false}); + line2 = new fabric.Line([224,30,225,400], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false}); + line3 = new fabric.Line([0,30,0,400], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false}); + line4 = new fabric.Line([0,400,225,400], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false}); + } + }); + + line1 = new fabric.Line([0,0,225,0], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false}); + line2 = new fabric.Line([224,0,225,450], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false}); + line3 = new fabric.Line([0,0,0,450], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false}); + line4 = new fabric.Line([0,450,225,449], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false}); + + $(".img-polaroid").click(function(e){ + var el = e.target; + var design = $(this).attr("src"); + $('#phoneDiv').css({ + 'backgroundImage': 'url(' + design +')', + 'backgroundRepeat': 'no-repeat', + 'backgroundPosition': 'top center', + 'background-size': '100% 100%' + + }); + // document.getElementById("phoneDiv").style.backgroundImage="url("+ design +")"; + }); + document.getElementById('remove-selected').onclick = function() { + var activeObject = canvas.getActiveObject(), + activeGroup = canvas.getActiveGroup(); + if (activeObject) { + canvas.remove(activeObject); + $("#text-string").val(""); + } + else if (activeGroup) { + var objectsInGroup = activeGroup.getObjects(); + canvas.discardActiveGroup(); + objectsInGroup.forEach(function(object) { + canvas.remove(object); + }); + } + }; + document.getElementById('bring-to-front').onclick = function() { + var activeObject = canvas.getActiveObject(), + activeGroup = canvas.getActiveGroup(); + if (activeObject) { + activeObject.bringToFront(); + } + else if (activeGroup) { + var objectsInGroup = activeGroup.getObjects(); + canvas.discardActiveGroup(); + objectsInGroup.forEach(function(object) { + object.bringToFront(); + }); + } + }; + document.getElementById('send-to-back').onclick = function() { + var activeObject = canvas.getActiveObject(), + activeGroup = canvas.getActiveGroup(); + if (activeObject) { + activeObject.sendToBack(); + } + else if (activeGroup) { + var objectsInGroup = activeGroup.getObjects(); + canvas.discardActiveGroup(); + objectsInGroup.forEach(function(object) { + object.sendToBack(); + }); + } + }; + $("#text-bold").click(function() { + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.fontWeight = (activeObject.fontWeight == 'bold' ? '' : 'bold'); + canvas.renderAll(); + } + }); + $("#text-italic").click(function() { + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.fontStyle = (activeObject.fontStyle == 'italic' ? '' : 'italic'); + canvas.renderAll(); + } + }); + $("#text-strike").click(function() { + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.textDecoration = (activeObject.textDecoration == 'line-through' ? '' : 'line-through'); + canvas.renderAll(); + } + }); + $("#text-underline").click(function() { + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.textDecoration = (activeObject.textDecoration == 'underline' ? '' : 'underline'); + canvas.renderAll(); + } + }); + $("#text-left").click(function() { + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.textAlign = 'left'; + canvas.renderAll(); + } + }); + $("#text-center").click(function() { + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.textAlign = 'center'; + canvas.renderAll(); + } + }); + $("#text-right").click(function() { + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.textAlign = 'right'; + canvas.renderAll(); + } + }); + $("#font-family").change(function() { + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.fontFamily = this.value; + canvas.renderAll(); + } + }); + $('#text-bgcolor').miniColors({ + change: function(hex, rgb) { + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.backgroundColor = this.value; + canvas.renderAll(); + } + }, + open: function(hex, rgb) { + // + }, + close: function(hex, rgb) { + // + } + }); + $('#text-fontcolor').miniColors({ + change: function(hex, rgb) { + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.fill = this.value; + canvas.renderAll(); + } + }, + open: function(hex, rgb) { + // + }, + close: function(hex, rgb) { + // + } + }); + + $('#text-strokecolor').miniColors({ + change: function(hex, rgb) { + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.strokeStyle = this.value; + canvas.renderAll(); + } + }, + open: function(hex, rgb) { + // + }, + close: function(hex, rgb) { + // + } + }); + + //canvas.add(new fabric.fabric.Object({hasBorders:true,hasControls:false,hasRotatingPoint:false,selectable:false,type:'rect'})); + $("#drawingArea").hover( + function() { + canvas.add(line1); + canvas.add(line2); + canvas.add(line3); + canvas.add(line4); + canvas.renderAll(); + }, + function() { + canvas.remove(line1); + canvas.remove(line2); + canvas.remove(line3); + canvas.remove(line4); + canvas.renderAll(); + } + ); + + $('.color-preview').click(function(){ + var color = $(this).css("background-color"); + document.getElementById("phoneDiv").style.backgroundColor = color; + }); + + $(".clearfix button,a").tooltip(); + });//doc ready + + + function getRandomNum(min, max) { + return Math.random() * (max - min) + min; + } + + function onObjectSelected(e) { + var selectedObject = e.target; + $("#text-string").val(""); + selectedObject.hasRotatingPoint = true + if (selectedObject && selectedObject.type === 'text') { + //display text editor + $("#texteditor").css('display', 'block'); + $("#text-string").val(selectedObject.getText()); + $('#text-fontcolor').miniColors('value',selectedObject.fill); + $('#text-strokecolor').miniColors('value',selectedObject.strokeStyle); + $("#imageeditor").css('display', 'block'); + } + else if (selectedObject && selectedObject.type === 'image'){ + //display image editor + $("#texteditor").css('display', 'none'); + $("#imageeditor").css('display', 'block'); + } + } + function onSelectedCleared(e){ + $("#texteditor").css('display', 'none'); + $("#text-string").val(""); + $("#imageeditor").css('display', 'none'); + } + function setFont(font){ + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.fontFamily = font; + canvas.renderAll(); + } + } + function removeWhite(){ + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'image') { + activeObject.filters[2] = new fabric.Image.filters.RemoveWhite({hreshold: 100, distance: 10});//0-255, 0-255 + activeObject.applyFilters(canvas.renderAll.bind(canvas)); + } + }
\ No newline at end of file diff --git a/js/excanvas.js b/js/excanvas.js new file mode 100644 index 0000000..367764b --- /dev/null +++ b/js/excanvas.js @@ -0,0 +1,924 @@ +// Copyright 2006 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +// Known Issues: +// +// * Patterns are not implemented. +// * Radial gradient are not implemented. The VML version of these look very +// different from the canvas one. +// * Clipping paths are not implemented. +// * Coordsize. The width and height attribute have higher priority than the +// width and height style values which isn't correct. +// * Painting mode isn't implemented. +// * Canvas width/height should is using content-box by default. IE in +// Quirks mode will draw the canvas using border-box. Either change your +// doctype to HTML5 +// (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype) +// or use Box Sizing Behavior from WebFX +// (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html) +// * Non uniform scaling does not correctly scale strokes. +// * Optimize. There is always room for speed improvements. + +// Only add this code if we do not already have a canvas implementation +if (!document.createElement('canvas').getContext) { + +(function() { + + // alias some functions to make (compiled) code shorter + var m = Math; + var mr = m.round; + var ms = m.sin; + var mc = m.cos; + var abs = m.abs; + var sqrt = m.sqrt; + + // this is used for sub pixel precision + var Z = 10; + var Z2 = Z / 2; + + /** + * This funtion is assigned to the <canvas> elements as element.getContext(). + * @this {HTMLElement} + * @return {CanvasRenderingContext2D_} + */ + function getContext() { + return this.context_ || + (this.context_ = new CanvasRenderingContext2D_(this)); + } + + var slice = Array.prototype.slice; + + /** + * Binds a function to an object. The returned function will always use the + * passed in {@code obj} as {@code this}. + * + * Example: + * + * g = bind(f, obj, a, b) + * g(c, d) // will do f.call(obj, a, b, c, d) + * + * @param {Function} f The function to bind the object to + * @param {Object} obj The object that should act as this when the function + * is called + * @param {*} var_args Rest arguments that will be used as the initial + * arguments when the function is called + * @return {Function} A new function that has bound this + */ + function bind(f, obj, var_args) { + var a = slice.call(arguments, 2); + return function() { + return f.apply(obj, a.concat(slice.call(arguments))); + }; + } + + var G_vmlCanvasManager_ = { + init: function(opt_doc) { + if (/MSIE/.test(navigator.userAgent) && !window.opera) { + var doc = opt_doc || document; + // Create a dummy element so that IE will allow canvas elements to be + // recognized. + doc.createElement('canvas'); + doc.attachEvent('onreadystatechange', bind(this.init_, this, doc)); + } + }, + + init_: function(doc) { + // create xmlns + if (!doc.namespaces['g_vml_']) { + doc.namespaces.add('g_vml_', 'urn:schemas-microsoft-com:vml', + '#default#VML'); + + } + if (!doc.namespaces['g_o_']) { + doc.namespaces.add('g_o_', 'urn:schemas-microsoft-com:office:office', + '#default#VML'); + } + + // Setup default CSS. Only add one style sheet per document + if (!doc.styleSheets['ex_canvas_']) { + var ss = doc.createStyleSheet(); + ss.owningElement.id = 'ex_canvas_'; + ss.cssText = 'canvas{display:inline-block;overflow:hidden;' + + // default size is 300x150 in Gecko and Opera + 'text-align:left;width:300px;height:150px}' + + 'g_vml_\\:*{behavior:url(#default#VML)}' + + 'g_o_\\:*{behavior:url(#default#VML)}'; + + } + + // find all canvas elements + var els = doc.getElementsByTagName('canvas'); + for (var i = 0; i < els.length; i++) { + this.initElement(els[i]); + } + }, + + /** + * Public initializes a canvas element so that it can be used as canvas + * element from now on. This is called automatically before the page is + * loaded but if you are creating elements using createElement you need to + * make sure this is called on the element. + * @param {HTMLElement} el The canvas element to initialize. + * @return {HTMLElement} the element that was created. + */ + initElement: function(el) { + if (!el.getContext) { + + el.getContext = getContext; + + // Remove fallback content. There is no way to hide text nodes so we + // just remove all childNodes. We could hide all elements and remove + // text nodes but who really cares about the fallback content. + el.innerHTML = ''; + + // do not use inline function because that will leak memory + el.attachEvent('onpropertychange', onPropertyChange); + el.attachEvent('onresize', onResize); + + var attrs = el.attributes; + if (attrs.width && attrs.width.specified) { + // TODO: use runtimeStyle and coordsize + // el.getContext().setWidth_(attrs.width.nodeValue); + el.style.width = attrs.width.nodeValue + 'px'; + } else { + el.width = el.clientWidth; + } + if (attrs.height && attrs.height.specified) { + // TODO: use runtimeStyle and coordsize + // el.getContext().setHeight_(attrs.height.nodeValue); + el.style.height = attrs.height.nodeValue + 'px'; + } else { + el.height = el.clientHeight; + } + //el.getContext().setCoordsize_() + } + return el; + } + }; + + function onPropertyChange(e) { + var el = e.srcElement; + + switch (e.propertyName) { + case 'width': + el.style.width = el.attributes.width.nodeValue + 'px'; + el.getContext().clearRect(); + break; + case 'height': + el.style.height = el.attributes.height.nodeValue + 'px'; + el.getContext().clearRect(); + break; + } + } + + function onResize(e) { + var el = e.srcElement; + if (el.firstChild) { + el.firstChild.style.width = el.clientWidth + 'px'; + el.firstChild.style.height = el.clientHeight + 'px'; + } + } + + G_vmlCanvasManager_.init(); + + // precompute "00" to "FF" + var dec2hex = []; + for (var i = 0; i < 16; i++) { + for (var j = 0; j < 16; j++) { + dec2hex[i * 16 + j] = i.toString(16) + j.toString(16); + } + } + + function createMatrixIdentity() { + return [ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1] + ]; + } + + function matrixMultiply(m1, m2) { + var result = createMatrixIdentity(); + + for (var x = 0; x < 3; x++) { + for (var y = 0; y < 3; y++) { + var sum = 0; + + for (var z = 0; z < 3; z++) { + sum += m1[x][z] * m2[z][y]; + } + + result[x][y] = sum; + } + } + return result; + } + + function copyState(o1, o2) { + o2.fillStyle = o1.fillStyle; + o2.lineCap = o1.lineCap; + o2.lineJoin = o1.lineJoin; + o2.lineWidth = o1.lineWidth; + o2.miterLimit = o1.miterLimit; + o2.shadowBlur = o1.shadowBlur; + o2.shadowColor = o1.shadowColor; + o2.shadowOffsetX = o1.shadowOffsetX; + o2.shadowOffsetY = o1.shadowOffsetY; + o2.strokeStyle = o1.strokeStyle; + o2.globalAlpha = o1.globalAlpha; + o2.arcScaleX_ = o1.arcScaleX_; + o2.arcScaleY_ = o1.arcScaleY_; + o2.lineScale_ = o1.lineScale_; + } + + function processStyle(styleString) { + var str, alpha = 1; + + styleString = String(styleString); + if (styleString.substring(0, 3) == 'rgb') { + var start = styleString.indexOf('(', 3); + var end = styleString.indexOf(')', start + 1); + var guts = styleString.substring(start + 1, end).split(','); + + str = '#'; + for (var i = 0; i < 3; i++) { + str += dec2hex[Number(guts[i])]; + } + + if (guts.length == 4 && styleString.substr(3, 1) == 'a') { + alpha = guts[3]; + } + } else { + str = styleString; + } + + return {color: str, alpha: alpha}; + } + + function processLineCap(lineCap) { + switch (lineCap) { + case 'butt': + return 'flat'; + case 'round': + return 'round'; + case 'square': + default: + return 'square'; + } + } + + /** + * This class implements CanvasRenderingContext2D interface as described by + * the WHATWG. + * @param {HTMLElement} surfaceElement The element that the 2D context should + * be associated with + */ + function CanvasRenderingContext2D_(surfaceElement) { + this.m_ = createMatrixIdentity(); + + this.mStack_ = []; + this.aStack_ = []; + this.currentPath_ = []; + + // Canvas context properties + this.strokeStyle = '#000'; + this.fillStyle = '#000'; + + this.lineWidth = 1; + this.lineJoin = 'miter'; + this.lineCap = 'butt'; + this.miterLimit = Z * 1; + this.globalAlpha = 1; + this.canvas = surfaceElement; + + var el = surfaceElement.ownerDocument.createElement('div'); + el.style.width = surfaceElement.clientWidth + 'px'; + el.style.height = surfaceElement.clientHeight + 'px'; + el.style.overflow = 'hidden'; + el.style.position = 'absolute'; + surfaceElement.appendChild(el); + + this.element_ = el; + this.arcScaleX_ = 1; + this.arcScaleY_ = 1; + this.lineScale_ = 1; + } + + var contextPrototype = CanvasRenderingContext2D_.prototype; + contextPrototype.clearRect = function() { + this.element_.innerHTML = ''; + }; + + contextPrototype.beginPath = function() { + // TODO: Branch current matrix so that save/restore has no effect + // as per safari docs. + this.currentPath_ = []; + }; + + contextPrototype.moveTo = function(aX, aY) { + var p = this.getCoords_(aX, aY); + this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y}); + this.currentX_ = p.x; + this.currentY_ = p.y; + }; + + contextPrototype.lineTo = function(aX, aY) { + var p = this.getCoords_(aX, aY); + this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y}); + + this.currentX_ = p.x; + this.currentY_ = p.y; + }; + + contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, + aCP2x, aCP2y, + aX, aY) { + var p = this.getCoords_(aX, aY); + var cp1 = this.getCoords_(aCP1x, aCP1y); + var cp2 = this.getCoords_(aCP2x, aCP2y); + bezierCurveTo(this, cp1, cp2, p); + }; + + // Helper function that takes the already fixed cordinates. + function bezierCurveTo(self, cp1, cp2, p) { + self.currentPath_.push({ + type: 'bezierCurveTo', + cp1x: cp1.x, + cp1y: cp1.y, + cp2x: cp2.x, + cp2y: cp2.y, + x: p.x, + y: p.y + }); + self.currentX_ = p.x; + self.currentY_ = p.y; + } + + contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) { + // the following is lifted almost directly from + // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes + + var cp = this.getCoords_(aCPx, aCPy); + var p = this.getCoords_(aX, aY); + + var cp1 = { + x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_), + y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_) + }; + var cp2 = { + x: cp1.x + (p.x - this.currentX_) / 3.0, + y: cp1.y + (p.y - this.currentY_) / 3.0 + }; + + bezierCurveTo(this, cp1, cp2, p); + }; + + contextPrototype.arc = function(aX, aY, aRadius, + aStartAngle, aEndAngle, aClockwise) { + aRadius *= Z; + var arcType = aClockwise ? 'at' : 'wa'; + + var xStart = aX + mc(aStartAngle) * aRadius - Z2; + var yStart = aY + ms(aStartAngle) * aRadius - Z2; + + var xEnd = aX + mc(aEndAngle) * aRadius - Z2; + var yEnd = aY + ms(aEndAngle) * aRadius - Z2; + + // IE won't render arches drawn counter clockwise if xStart == xEnd. + if (xStart == xEnd && !aClockwise) { + xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something + // that can be represented in binary + } + + var p = this.getCoords_(aX, aY); + var pStart = this.getCoords_(xStart, yStart); + var pEnd = this.getCoords_(xEnd, yEnd); + + this.currentPath_.push({type: arcType, + x: p.x, + y: p.y, + radius: aRadius, + xStart: pStart.x, + yStart: pStart.y, + xEnd: pEnd.x, + yEnd: pEnd.y}); + + }; + + contextPrototype.rect = function(aX, aY, aWidth, aHeight) { + this.moveTo(aX, aY); + this.lineTo(aX + aWidth, aY); + this.lineTo(aX + aWidth, aY + aHeight); + this.lineTo(aX, aY + aHeight); + this.closePath(); + }; + + contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) { + var oldPath = this.currentPath_; + this.beginPath(); + + this.moveTo(aX, aY); + this.lineTo(aX + aWidth, aY); + this.lineTo(aX + aWidth, aY + aHeight); + this.lineTo(aX, aY + aHeight); + this.closePath(); + this.stroke(); + + this.currentPath_ = oldPath; + }; + + contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) { + var oldPath = this.currentPath_; + this.beginPath(); + + this.moveTo(aX, aY); + this.lineTo(aX + aWidth, aY); + this.lineTo(aX + aWidth, aY + aHeight); + this.lineTo(aX, aY + aHeight); + this.closePath(); + this.fill(); + + this.currentPath_ = oldPath; + }; + + contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) { + var gradient = new CanvasGradient_('gradient'); + gradient.x0_ = aX0; + gradient.y0_ = aY0; + gradient.x1_ = aX1; + gradient.y1_ = aY1; + return gradient; + }; + + contextPrototype.createRadialGradient = function(aX0, aY0, aR0, + aX1, aY1, aR1) { + var gradient = new CanvasGradient_('gradientradial'); + gradient.x0_ = aX0; + gradient.y0_ = aY0; + gradient.r0_ = aR0; + gradient.x1_ = aX1; + gradient.y1_ = aY1; + gradient.r1_ = aR1; + return gradient; + }; + + contextPrototype.drawImage = function(image, var_args) { + var dx, dy, dw, dh, sx, sy, sw, sh; + + // to find the original width we overide the width and height + var oldRuntimeWidth = image.runtimeStyle.width; + var oldRuntimeHeight = image.runtimeStyle.height; + image.runtimeStyle.width = 'auto'; + image.runtimeStyle.height = 'auto'; + + // get the original size + var w = image.width; + var h = image.height; + + // and remove overides + image.runtimeStyle.width = oldRuntimeWidth; + image.runtimeStyle.height = oldRuntimeHeight; + + if (arguments.length == 3) { + dx = arguments[1]; + dy = arguments[2]; + sx = sy = 0; + sw = dw = w; + sh = dh = h; + } else if (arguments.length == 5) { + dx = arguments[1]; + dy = arguments[2]; + dw = arguments[3]; + dh = arguments[4]; + sx = sy = 0; + sw = w; + sh = h; + } else if (arguments.length == 9) { + sx = arguments[1]; + sy = arguments[2]; + sw = arguments[3]; + sh = arguments[4]; + dx = arguments[5]; + dy = arguments[6]; + dw = arguments[7]; + dh = arguments[8]; + } else { + throw Error('Invalid number of arguments'); + } + + var d = this.getCoords_(dx, dy); + + var w2 = sw / 2; + var h2 = sh / 2; + + var vmlStr = []; + + var W = 10; + var H = 10; + + // For some reason that I've now forgotten, using divs didn't work + vmlStr.push(' <g_vml_:group', + ' coordsize="', Z * W, ',', Z * H, '"', + ' coordorigin="0,0"' , + ' style="width:', W, 'px;height:', H, 'px;position:absolute;'); + + // If filters are necessary (rotation exists), create them + // filters are bog-slow, so only create them if abbsolutely necessary + // The following check doesn't account for skews (which don't exist + // in the canvas spec (yet) anyway. + + if (this.m_[0][0] != 1 || this.m_[0][1]) { + var filter = []; + + // Note the 12/21 reversal + filter.push('M11=', this.m_[0][0], ',', + 'M12=', this.m_[1][0], ',', + 'M21=', this.m_[0][1], ',', + 'M22=', this.m_[1][1], ',', + 'Dx=', mr(d.x / Z), ',', + 'Dy=', mr(d.y / Z), ''); + + // Bounding box calculation (need to minimize displayed area so that + // filters don't waste time on unused pixels. + var max = d; + var c2 = this.getCoords_(dx + dw, dy); + var c3 = this.getCoords_(dx, dy + dh); + var c4 = this.getCoords_(dx + dw, dy + dh); + + max.x = m.max(max.x, c2.x, c3.x, c4.x); + max.y = m.max(max.y, c2.y, c3.y, c4.y); + + vmlStr.push('padding:0 ', mr(max.x / Z), 'px ', mr(max.y / Z), + 'px 0;filter:progid:DXImageTransform.Microsoft.Matrix(', + filter.join(''), ", sizingmethod='clip');") + } else { + vmlStr.push('top:', mr(d.y / Z), 'px;left:', mr(d.x / Z), 'px;'); + } + + vmlStr.push(' ">' , + '<g_vml_:image src="', image.src, '"', + ' style="width:', Z * dw, 'px;', + ' height:', Z * dh, 'px;"', + ' cropleft="', sx / w, '"', + ' croptop="', sy / h, '"', + ' cropright="', (w - sx - sw) / w, '"', + ' cropbottom="', (h - sy - sh) / h, '"', + ' />', + '</g_vml_:group>'); + + this.element_.insertAdjacentHTML('BeforeEnd', + vmlStr.join('')); + }; + + contextPrototype.stroke = function(aFill) { + var lineStr = []; + var lineOpen = false; + var a = processStyle(aFill ? this.fillStyle : this.strokeStyle); + var color = a.color; + var opacity = a.alpha * this.globalAlpha; + + var W = 10; + var H = 10; + + lineStr.push('<g_vml_:shape', + ' filled="', !!aFill, '"', + ' style="position:absolute;width:', W, 'px;height:', H, 'px;"', + ' coordorigin="0 0" coordsize="', Z * W, ' ', Z * H, '"', + ' stroked="', !aFill, '"', + ' path="'); + + var newSeq = false; + var min = {x: null, y: null}; + var max = {x: null, y: null}; + + for (var i = 0; i < this.currentPath_.length; i++) { + var p = this.currentPath_[i]; + var c; + + switch (p.type) { + case 'moveTo': + c = p; + lineStr.push(' m ', mr(p.x), ',', mr(p.y)); + break; + case 'lineTo': + lineStr.push(' l ', mr(p.x), ',', mr(p.y)); + break; + case 'close': + lineStr.push(' x '); + p = null; + break; + case 'bezierCurveTo': + lineStr.push(' c ', + mr(p.cp1x), ',', mr(p.cp1y), ',', + mr(p.cp2x), ',', mr(p.cp2y), ',', + mr(p.x), ',', mr(p.y)); + break; + case 'at': + case 'wa': + lineStr.push(' ', p.type, ' ', + mr(p.x - this.arcScaleX_ * p.radius), ',', + mr(p.y - this.arcScaleY_ * p.radius), ' ', + mr(p.x + this.arcScaleX_ * p.radius), ',', + mr(p.y + this.arcScaleY_ * p.radius), ' ', + mr(p.xStart), ',', mr(p.yStart), ' ', + mr(p.xEnd), ',', mr(p.yEnd)); + break; + } + + + // TODO: Following is broken for curves due to + // move to proper paths. + + // Figure out dimensions so we can do gradient fills + // properly + if (p) { + if (min.x == null || p.x < min.x) { + min.x = p.x; + } + if (max.x == null || p.x > max.x) { + max.x = p.x; + } + if (min.y == null || p.y < min.y) { + min.y = p.y; + } + if (max.y == null || p.y > max.y) { + max.y = p.y; + } + } + } + lineStr.push(' ">'); + + if (!aFill) { + var lineWidth = this.lineScale_ * this.lineWidth; + + // VML cannot correctly render a line if the width is less than 1px. + // In that case, we dilute the color to make the line look thinner. + if (lineWidth < 1) { + opacity *= lineWidth; + } + + lineStr.push( + '<g_vml_:stroke', + ' opacity="', opacity, '"', + ' joinstyle="', this.lineJoin, '"', + ' miterlimit="', this.miterLimit, '"', + ' endcap="', processLineCap(this.lineCap), '"', + ' weight="', lineWidth, 'px"', + ' color="', color, '" />' + ); + } else if (typeof this.fillStyle == 'object') { + var fillStyle = this.fillStyle; + var angle = 0; + var focus = {x: 0, y: 0}; + + // additional offset + var shift = 0; + // scale factor for offset + var expansion = 1; + + if (fillStyle.type_ == 'gradient') { + var x0 = fillStyle.x0_ / this.arcScaleX_; + var y0 = fillStyle.y0_ / this.arcScaleY_; + var x1 = fillStyle.x1_ / this.arcScaleX_; + var y1 = fillStyle.y1_ / this.arcScaleY_; + var p0 = this.getCoords_(x0, y0); + var p1 = this.getCoords_(x1, y1); + var dx = p1.x - p0.x; + var dy = p1.y - p0.y; + angle = Math.atan2(dx, dy) * 180 / Math.PI; + + // The angle should be a non-negative number. + if (angle < 0) { + angle += 360; + } + + // Very small angles produce an unexpected result because they are + // converted to a scientific notation string. + if (angle < 1e-6) { + angle = 0; + } + } else { + var p0 = this.getCoords_(fillStyle.x0_, fillStyle.y0_); + var width = max.x - min.x; + var height = max.y - min.y; + focus = { + x: (p0.x - min.x) / width, + y: (p0.y - min.y) / height + }; + + width /= this.arcScaleX_ * Z; + height /= this.arcScaleY_ * Z; + var dimension = m.max(width, height); + shift = 2 * fillStyle.r0_ / dimension; + expansion = 2 * fillStyle.r1_ / dimension - shift; + } + + // We need to sort the color stops in ascending order by offset, + // otherwise IE won't interpret it correctly. + var stops = fillStyle.colors_; + stops.sort(function(cs1, cs2) { + return cs1.offset - cs2.offset; + }); + + var length = stops.length; + var color1 = stops[0].color; + var color2 = stops[length - 1].color; + var opacity1 = stops[0].alpha * this.globalAlpha; + var opacity2 = stops[length - 1].alpha * this.globalAlpha; + + var colors = []; + for (var i = 0; i < length; i++) { + var stop = stops[i]; + colors.push(stop.offset * expansion + shift + ' ' + stop.color); + } + + // When colors attribute is used, the meanings of opacity and o:opacity2 + // are reversed. + lineStr.push('<g_vml_:fill type="', fillStyle.type_, '"', + ' method="none" focus="100%"', + ' color="', color1, '"', + ' color2="', color2, '"', + ' colors="', colors.join(','), '"', + ' opacity="', opacity2, '"', + ' g_o_:opacity2="', opacity1, '"', + ' angle="', angle, '"', + ' focusposition="', focus.x, ',', focus.y, '" />'); + } else { + lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, + '" />'); + } + + lineStr.push('</g_vml_:shape>'); + + this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); + }; + + contextPrototype.fill = function() { + this.stroke(true); + } + + contextPrototype.closePath = function() { + this.currentPath_.push({type: 'close'}); + }; + + /** + * @private + */ + contextPrototype.getCoords_ = function(aX, aY) { + var m = this.m_; + return { + x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2, + y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2 + } + }; + + contextPrototype.save = function() { + var o = {}; + copyState(this, o); + this.aStack_.push(o); + this.mStack_.push(this.m_); + this.m_ = matrixMultiply(createMatrixIdentity(), this.m_); + }; + + contextPrototype.restore = function() { + copyState(this.aStack_.pop(), this); + this.m_ = this.mStack_.pop(); + }; + + function matrixIsFinite(m) { + for (var j = 0; j < 3; j++) { + for (var k = 0; k < 2; k++) { + if (!isFinite(m[j][k]) || isNaN(m[j][k])) { + return false; + } + } + } + return true; + } + + function setM(ctx, m, updateLineScale) { + if (!matrixIsFinite(m)) { + return; + } + ctx.m_ = m; + + if (updateLineScale) { + // Get the line scale. + // Determinant of this.m_ means how much the area is enlarged by the + // transformation. So its square root can be used as a scale factor + // for width. + var det = m[0][0] * m[1][1] - m[0][1] * m[1][0]; + ctx.lineScale_ = sqrt(abs(det)); + } + } + + contextPrototype.translate = function(aX, aY) { + var m1 = [ + [1, 0, 0], + [0, 1, 0], + [aX, aY, 1] + ]; + + setM(this, matrixMultiply(m1, this.m_), false); + }; + + contextPrototype.rotate = function(aRot) { + var c = mc(aRot); + var s = ms(aRot); + + var m1 = [ + [c, s, 0], + [-s, c, 0], + [0, 0, 1] + ]; + + setM(this, matrixMultiply(m1, this.m_), false); + }; + + contextPrototype.scale = function(aX, aY) { + this.arcScaleX_ *= aX; + this.arcScaleY_ *= aY; + var m1 = [ + [aX, 0, 0], + [0, aY, 0], + [0, 0, 1] + ]; + + setM(this, matrixMultiply(m1, this.m_), true); + }; + + contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) { + var m1 = [ + [m11, m12, 0], + [m21, m22, 0], + [dx, dy, 1] + ]; + + setM(this, matrixMultiply(m1, this.m_), true); + }; + + contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) { + var m = [ + [m11, m12, 0], + [m21, m22, 0], + [dx, dy, 1] + ]; + + setM(this, m, true); + }; + + /******** STUBS ********/ + contextPrototype.clip = function() { + // TODO: Implement + }; + + contextPrototype.arcTo = function() { + // TODO: Implement + }; + + contextPrototype.createPattern = function() { + return new CanvasPattern_; + }; + + // Gradient / Pattern Stubs + function CanvasGradient_(aType) { + this.type_ = aType; + this.x0_ = 0; + this.y0_ = 0; + this.r0_ = 0; + this.x1_ = 0; + this.y1_ = 0; + this.r1_ = 0; + this.colors_ = []; + } + + CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) { + aColor = processStyle(aColor); + this.colors_.push({offset: aOffset, + color: aColor.color, + alpha: aColor.alpha}); + }; + + function CanvasPattern_() {} + + // set up externs + G_vmlCanvasManager = G_vmlCanvasManager_; + CanvasRenderingContext2D = CanvasRenderingContext2D_; + CanvasGradient = CanvasGradient_; + CanvasPattern = CanvasPattern_; + +})(); + +} // if diff --git a/js/fabric.js b/js/fabric.js new file mode 100644 index 0000000..e438f56 --- /dev/null +++ b/js/fabric.js @@ -0,0 +1,13962 @@ +/* build: `node build.js modules=ALL` */ +/*! Fabric.js Copyright 2008-2012, Printio (Juriy Zaytsev, Maxim Chernyak) */ + +var fabric = fabric || { version: "0.9.13" }; + +if (typeof exports != 'undefined') { + exports.fabric = fabric; +} + +if (typeof document != 'undefined' && typeof window != 'undefined') { + fabric.document = document; + fabric.window = window; +} +else { + // assume we're running under node.js when document/window are not present + fabric.document = require("jsdom").jsdom("<!DOCTYPE html><html><head></head><body></body></html>"); + fabric.window = fabric.document.createWindow(); +} + +/** + * True when in environment that supports touch events + * @property isTouchSupported + * @type boolean + */ +fabric.isTouchSupported = "ontouchstart" in fabric.document.documentElement; + +/** + * True when in environment that's probably Node.js + * @property isLikelyNode + * @type boolean + */ +fabric.isLikelyNode = typeof Buffer !== 'undefined' && typeof window === 'undefined'; +/*! + * Copyright (c) 2009 Simo Kinnunen. + * Licensed under the MIT license. + */ + +var Cufon = (function() { + + var api = function() { + return api.replace.apply(null, arguments); + }; + + var DOM = api.DOM = { + + ready: (function() { + + var complete = false, readyStatus = { loaded: 1, complete: 1 }; + + var queue = [], perform = function() { + if (complete) return; + complete = true; + for (var fn; fn = queue.shift(); fn()); + }; + + // Gecko, Opera, WebKit r26101+ + + if (fabric.document.addEventListener) { + fabric.document.addEventListener('DOMContentLoaded', perform, false); + fabric.window.addEventListener('pageshow', perform, false); // For cached Gecko pages + } + + // Old WebKit, Internet Explorer + + if (!fabric.window.opera && fabric.document.readyState) (function() { + readyStatus[fabric.document.readyState] ? perform() : setTimeout(arguments.callee, 10); + })(); + + // Internet Explorer + + if (fabric.document.readyState && fabric.document.createStyleSheet) (function() { + try { + fabric.document.body.doScroll('left'); + perform(); + } + catch (e) { + setTimeout(arguments.callee, 1); + } + })(); + + addEvent(fabric.window, 'load', perform); // Fallback + + return function(listener) { + if (!arguments.length) perform(); + else complete ? listener() : queue.push(listener); + }; + + })() + + }; + + var CSS = api.CSS = { + + Size: function(value, base) { + + this.value = parseFloat(value); + this.unit = String(value).match(/[a-z%]*$/)[0] || 'px'; + + this.convert = function(value) { + return value / base * this.value; + }; + + this.convertFrom = function(value) { + return value / this.value * base; + }; + + this.toString = function() { + return this.value + this.unit; + }; + + }, + + getStyle: function(el) { + return new Style(el.style); + /* + var view = document.defaultView; + if (view && view.getComputedStyle) return new Style(view.getComputedStyle(el, null)); + if (el.currentStyle) return new Style(el.currentStyle); + return new Style(el.style); + */ + }, + + quotedList: cached(function(value) { + // doesn't work properly with empty quoted strings (""), but + // it's not worth the extra code. + var list = [], re = /\s*((["'])([\s\S]*?[^\\])\2|[^,]+)\s*/g, match; + while (match = re.exec(value)) list.push(match[3] || match[1]); + return list; + }), + + ready: (function() { + + var complete = false; + + var queue = [], perform = function() { + complete = true; + for (var fn; fn = queue.shift(); fn()); + }; + + // Safari 2 does not include <style> elements in document.styleSheets. + // Safari 2 also does not support Object.prototype.propertyIsEnumerable. + + var styleElements = Object.prototype.propertyIsEnumerable ? elementsByTagName('style') : { length: 0 }; + var linkElements = elementsByTagName('link'); + + DOM.ready(function() { + // These checks are actually only needed for WebKit-based browsers, but don't really hurt other browsers. + var linkStyles = 0, link; + for (var i = 0, l = linkElements.length; link = linkElements[i], i < l; ++i) { + // WebKit does not load alternate stylesheets. + if (!link.disabled && link.rel.toLowerCase() == 'stylesheet') ++linkStyles; + } + if (fabric.document.styleSheets.length >= styleElements.length + linkStyles) perform(); + else setTimeout(arguments.callee, 10); + }); + + return function(listener) { + if (complete) listener(); + else queue.push(listener); + }; + + })(), + + supports: function(property, value) { + var checker = fabric.document.createElement('span').style; + if (checker[property] === undefined) return false; + checker[property] = value; + return checker[property] === value; + }, + + textAlign: function(word, style, position, wordCount) { + if (style.get('textAlign') == 'right') { + if (position > 0) word = ' ' + word; + } + else if (position < wordCount - 1) word += ' '; + return word; + }, + + textDecoration: function(el, style) { + if (!style) style = this.getStyle(el); + var types = { + underline: null, + overline: null, + 'line-through': null + }; + for (var search = el; search.parentNode && search.parentNode.nodeType == 1; ) { + var foundAll = true; + for (var type in types) { + if (types[type]) continue; + if (style.get('textDecoration').indexOf(type) != -1) types[type] = style.get('color'); + foundAll = false; + } + if (foundAll) break; // this is rather unlikely to happen + style = this.getStyle(search = search.parentNode); + } + return types; + }, + + textShadow: cached(function(value) { + if (value == 'none') return null; + var shadows = [], currentShadow = {}, result, offCount = 0; + var re = /(#[a-f0-9]+|[a-z]+\(.*?\)|[a-z]+)|(-?[\d.]+[a-z%]*)|,/ig; + while (result = re.exec(value)) { + if (result[0] == ',') { + shadows.push(currentShadow); + currentShadow = {}, offCount = 0; + } + else if (result[1]) { + currentShadow.color = result[1]; + } + else { + currentShadow[[ 'offX', 'offY', 'blur' ][offCount++]] = result[2]; + } + } + shadows.push(currentShadow); + return shadows; + }), + + color: cached(function(value) { + var parsed = {}; + parsed.color = value.replace(/^rgba\((.*?),\s*([\d.]+)\)/, function($0, $1, $2) { + parsed.opacity = parseFloat($2); + return 'rgb(' + $1 + ')'; + }); + return parsed; + }), + + textTransform: function(text, style) { + return text[{ + uppercase: 'toUpperCase', + lowercase: 'toLowerCase' + }[style.get('textTransform')] || 'toString'](); + } + + }; + + function Font(data) { + + var face = this.face = data.face; + this.glyphs = data.glyphs; + this.w = data.w; + this.baseSize = parseInt(face['units-per-em'], 10); + + this.family = face['font-family'].toLowerCase(); + this.weight = face['font-weight']; + this.style = face['font-style'] || 'normal'; + + this.viewBox = (function () { + var parts = face.bbox.split(/\s+/); + var box = { + minX: parseInt(parts[0], 10), + minY: parseInt(parts[1], 10), + maxX: parseInt(parts[2], 10), + maxY: parseInt(parts[3], 10) + }; + box.width = box.maxX - box.minX, + box.height = box.maxY - box.minY; + box.toString = function() { + return [ this.minX, this.minY, this.width, this.height ].join(' '); + }; + return box; + })(); + + this.ascent = -parseInt(face.ascent, 10); + this.descent = -parseInt(face.descent, 10); + + this.height = -this.ascent + this.descent; + + } + + function FontFamily() { + + var styles = {}, mapping = { + oblique: 'italic', + italic: 'oblique' + }; + + this.add = function(font) { + (styles[font.style] || (styles[font.style] = {}))[font.weight] = font; + }; + + this.get = function(style, weight) { + var weights = styles[style] || styles[mapping[style]] + || styles.normal || styles.italic || styles.oblique; + if (!weights) return null; + // we don't have to worry about "bolder" and "lighter" + // because IE's currentStyle returns a numeric value for it, + // and other browsers use the computed value anyway + weight = { + normal: 400, + bold: 700 + }[weight] || parseInt(weight, 10); + if (weights[weight]) return weights[weight]; + // http://www.w3.org/TR/CSS21/fonts.html#propdef-font-weight + // Gecko uses x99/x01 for lighter/bolder + var up = { + 1: 1, + 99: 0 + }[weight % 100], alts = [], min, max; + if (up === undefined) up = weight > 400; + if (weight == 500) weight = 400; + for (var alt in weights) { + alt = parseInt(alt, 10); + if (!min || alt < min) min = alt; + if (!max || alt > max) max = alt; + alts.push(alt); + } + if (weight < min) weight = min; + if (weight > max) weight = max; + alts.sort(function(a, b) { + return (up + ? (a > weight && b > weight) ? a < b : a > b + : (a < weight && b < weight) ? a > b : a < b) ? -1 : 1; + }); + return weights[alts[0]]; + }; + + } + + function HoverHandler() { + + function contains(node, anotherNode) { + if (node.contains) return node.contains(anotherNode); + return node.compareDocumentPosition(anotherNode) & 16; + } + + function onOverOut(e) { + var related = e.relatedTarget; + if (!related || contains(this, related)) return; + trigger(this); + } + + function onEnterLeave(e) { + trigger(this); + } + + function trigger(el) { + // A timeout is needed so that the event can actually "happen" + // before replace is triggered. This ensures that styles are up + // to date. + setTimeout(function() { + api.replace(el, sharedStorage.get(el).options, true); + }, 10); + } + + this.attach = function(el) { + if (el.onmouseenter === undefined) { + addEvent(el, 'mouseover', onOverOut); + addEvent(el, 'mouseout', onOverOut); + } + else { + addEvent(el, 'mouseenter', onEnterLeave); + addEvent(el, 'mouseleave', onEnterLeave); + } + }; + + } + + function Storage() { + + var map = {}, at = 0; + + function identify(el) { + return el.cufid || (el.cufid = ++at); + } + + this.get = function(el) { + var id = identify(el); + return map[id] || (map[id] = {}); + }; + + } + + function Style(style) { + + var custom = {}, sizes = {}; + + this.get = function(property) { + return custom[property] != undefined ? custom[property] : style[property]; + }; + + this.getSize = function(property, base) { + return sizes[property] || (sizes[property] = new CSS.Size(this.get(property), base)); + }; + + this.extend = function(styles) { + for (var property in styles) custom[property] = styles[property]; + return this; + }; + + } + + function addEvent(el, type, listener) { + if (el.addEventListener) { + el.addEventListener(type, listener, false); + } + else if (el.attachEvent) { + el.attachEvent('on' + type, function() { + return listener.call(el, fabric.window.event); + }); + } + } + + function attach(el, options) { + var storage = sharedStorage.get(el); + if (storage.options) return el; + if (options.hover && options.hoverables[el.nodeName.toLowerCase()]) { + hoverHandler.attach(el); + } + storage.options = options; + return el; + } + + function cached(fun) { + var cache = {}; + return function(key) { + if (!cache.hasOwnProperty(key)) cache[key] = fun.apply(null, arguments); + return cache[key]; + }; + } + + function getFont(el, style) { + if (!style) style = CSS.getStyle(el); + var families = CSS.quotedList(style.get('fontFamily').toLowerCase()), family; + for (var i = 0, l = families.length; i < l; ++i) { + family = families[i]; + if (fonts[family]) return fonts[family].get(style.get('fontStyle'), style.get('fontWeight')); + } + return null; + } + + function elementsByTagName(query) { + return fabric.document.getElementsByTagName(query); + } + + function merge() { + var merged = {}, key; + for (var i = 0, l = arguments.length; i < l; ++i) { + for (key in arguments[i]) merged[key] = arguments[i][key]; + } + return merged; + } + + function process(font, text, style, options, node, el) { + + var separate = options.separate; + if (separate == 'none') return engines[options.engine].apply(null, arguments); + var fragment = fabric.document.createDocumentFragment(), processed; + var parts = text.split(separators[separate]), needsAligning = (separate == 'words'); + if (needsAligning && HAS_BROKEN_REGEXP) { + // @todo figure out a better way to do this + if (/^\s/.test(text)) parts.unshift(''); + if (/\s$/.test(text)) parts.push(''); + } + for (var i = 0, l = parts.length; i < l; ++i) { + processed = engines[options.engine](font, + needsAligning ? CSS.textAlign(parts[i], style, i, l) : parts[i], + style, options, node, el, i < l - 1); + if (processed) fragment.appendChild(processed); + } + return fragment; + } + + function replaceElement(el, options) { + var font, style, nextNode, redraw; + for (var node = attach(el, options).firstChild; node; node = nextNode) { + nextNode = node.nextSibling; + redraw = false; + if (node.nodeType == 1) { + if (!node.firstChild) continue; + if (!/cufon/.test(node.className)) { + arguments.callee(node, options); + continue; + } + else redraw = true; + } + if (!style) style = CSS.getStyle(el).extend(options); + if (!font) font = getFont(el, style); + + if (!font) continue; + if (redraw) { + engines[options.engine](font, null, style, options, node, el); + continue; + } + var text = node.data; + //for some reason, the carriage return is not stripped by IE but "\n" is, so let's keep \r as a new line marker... + if (typeof G_vmlCanvasManager != 'undefined') { + text = text.replace(/\r/g, "\n"); + } + if (text === '') continue; + var processed = process(font, text, style, options, node, el); + if (processed) node.parentNode.replaceChild(processed, node); + else node.parentNode.removeChild(node); + } + } + + var HAS_BROKEN_REGEXP = ' '.split(/\s+/).length == 0; + + var sharedStorage = new Storage(); + var hoverHandler = new HoverHandler(); + var replaceHistory = []; + + var engines = {}, fonts = {}, defaultOptions = { + engine: null, + //fontScale: 1, + //fontScaling: false, + hover: false, + hoverables: { + a: true + }, + printable: true, + //rotation: 0, + //selectable: false, + selector: ( + fabric.window.Sizzle + || (fabric.window.jQuery && function(query) { return jQuery(query); }) // avoid noConflict issues + || (fabric.window.dojo && dojo.query) + || (fabric.window.$$ && function(query) { return $$(query); }) + || (fabric.window.$ && function(query) { return $(query); }) + || (fabric.document.querySelectorAll && function(query) { return fabric.document.querySelectorAll(query); }) + || elementsByTagName + ), + separate: 'words', // 'none' and 'characters' are also accepted + textShadow: 'none' + }; + + var separators = { + words: /\s+/, + characters: '' + }; + + api.now = function() { + DOM.ready(); + return api; + }; + + api.refresh = function() { + var currentHistory = replaceHistory.splice(0, replaceHistory.length); + for (var i = 0, l = currentHistory.length; i < l; ++i) { + api.replace.apply(null, currentHistory[i]); + } + return api; + }; + + api.registerEngine = function(id, engine) { + if (!engine) return api; + engines[id] = engine; + return api.set('engine', id); + }; + + api.registerFont = function(data) { + var font = new Font(data), family = font.family; + if (!fonts[family]) fonts[family] = new FontFamily(); + fonts[family].add(font); + return api.set('fontFamily', '"' + family + '"'); + }; + + api.replace = function(elements, options, ignoreHistory) { + options = merge(defaultOptions, options); + if (!options.engine) return api; // there's no browser support so we'll just stop here + if (typeof options.textShadow == 'string' && options.textShadow) + options.textShadow = CSS.textShadow(options.textShadow); + if (!ignoreHistory) replaceHistory.push(arguments); + if (elements.nodeType || typeof elements == 'string') elements = [ elements ]; + CSS.ready(function() { + for (var i = 0, l = elements.length; i < l; ++i) { + var el = elements[i]; + if (typeof el == 'string') api.replace(options.selector(el), options, true); + else replaceElement(el, options); + } + }); + return api; + }; + + api.replaceElement = function(el, options) { + options = merge(defaultOptions, options); + if (typeof options.textShadow == 'string' && options.textShadow) + options.textShadow = CSS.textShadow(options.textShadow); + return replaceElement(el, options); + }; + + // ==> + api.engines = engines; + api.fonts = fonts; + api.getOptions = function() { + return merge(defaultOptions); + } + // <== + + api.set = function(option, value) { + defaultOptions[option] = value; + return api; + }; + + return api; + +})(); + +Cufon.registerEngine('canvas', (function() { + + // Safari 2 doesn't support .apply() on native methods + var HAS_INLINE_BLOCK = Cufon.CSS.supports('display', 'inline-block'); + + // Firefox 2 w/ non-strict doctype (almost standards mode) + var HAS_BROKEN_LINEHEIGHT = !HAS_INLINE_BLOCK && (fabric.document.compatMode == 'BackCompat' || /frameset|transitional/i.test(fabric.document.doctype.publicId)); + + var styleSheet = fabric.document.createElement('style'); + styleSheet.type = 'text/css'; + + var textNode = fabric.document.createTextNode( + '.cufon-canvas{text-indent:0}' + + '@media screen,projection{' + + '.cufon-canvas{display:inline;display:inline-block;position:relative;vertical-align:middle' + + (HAS_BROKEN_LINEHEIGHT + ? '' + : ';font-size:1px;line-height:1px') + + '}.cufon-canvas .cufon-alt{display:-moz-inline-box;display:inline-block;width:0;height:0;overflow:hidden}' + + (HAS_INLINE_BLOCK + ? '.cufon-canvas canvas{position:relative}' + : '.cufon-canvas canvas{position:absolute}') + + '}' + + '@media print{' + + '.cufon-canvas{padding:0 !important}' + + '.cufon-canvas canvas{display:none}' + + '.cufon-canvas .cufon-alt{display:inline}' + + '}' + ) + + try { + styleSheet.appendChild(textNode); + } catch(e) { + //IE8- can't do this... + styleSheet.setAttribute("type", "text/css"); + styleSheet.styleSheet.cssText = textNode.data; + } + fabric.document.getElementsByTagName('head')[0].appendChild(styleSheet); + + function generateFromVML(path, context) { + var atX = 0, atY = 0; + var code = [], re = /([mrvxe])([^a-z]*)/g, match; + generate: for (var i = 0; match = re.exec(path); ++i) { + var c = match[2].split(','); + switch (match[1]) { + case 'v': + code[i] = { m: 'bezierCurveTo', a: [ atX + ~~c[0], atY + ~~c[1], atX + ~~c[2], atY + ~~c[3], atX += ~~c[4], atY += ~~c[5] ] }; + break; + case 'r': + code[i] = { m: 'lineTo', a: [ atX += ~~c[0], atY += ~~c[1] ] }; + break; + case 'm': + code[i] = { m: 'moveTo', a: [ atX = ~~c[0], atY = ~~c[1] ] }; + break; + case 'x': + code[i] = { m: 'closePath', a: [] }; + break; + case 'e': + break generate; + } + context[code[i].m].apply(context, code[i].a); + } + return code; + } + + function interpret(code, context) { + for (var i = 0, l = code.length; i < l; ++i) { + var line = code[i]; + context[line.m].apply(context, line.a); + } + } + + return function(font, text, style, options, node, el) { + + var redraw = (text === null); + + var viewBox = font.viewBox; + + var size = style.getSize('fontSize', font.baseSize); + + var letterSpacing = style.get('letterSpacing'); + letterSpacing = (letterSpacing == 'normal') ? 0 : size.convertFrom(parseInt(letterSpacing, 10)); + + var expandTop = 0, expandRight = 0, expandBottom = 0, expandLeft = 0; + var shadows = options.textShadow, shadowOffsets = []; + + Cufon.textOptions.shadowOffsets = [ ]; + Cufon.textOptions.shadows = null; + + if (shadows) { + Cufon.textOptions.shadows = shadows; + for (var i = 0, l = shadows.length; i < l; ++i) { + var shadow = shadows[i]; + var x = size.convertFrom(parseFloat(shadow.offX)); + var y = size.convertFrom(parseFloat(shadow.offY)); + shadowOffsets[i] = [ x, y ]; + //if (y < expandTop) expandTop = y; + //if (x > expandRight) expandRight = x; + //if (y > expandBottom) expandBottom = y; + //if (x < expandLeft) expandLeft = x; + } + } + + var chars = Cufon.CSS.textTransform(redraw ? node.alt : text, style).split(''); + + var width = 0, lastWidth = null; + + var maxWidth = 0, lines = 1, lineWidths = [ ]; + for (var i = 0, l = chars.length; i < l; ++i) { + if (chars[i] === '\n') { + lines++; + if (width > maxWidth) { + maxWidth = width; + } + lineWidths.push(width); + width = 0; + continue; + } + var glyph = font.glyphs[chars[i]] || font.missingGlyph; + if (!glyph) continue; + width += lastWidth = Number(glyph.w || font.w) + letterSpacing; + } + lineWidths.push(width); + + width = Math.max(maxWidth, width); + + var lineOffsets = [ ]; + for (var i = lineWidths.length; i--; ) { + lineOffsets[i] = width - lineWidths[i]; + } + + if (lastWidth === null) return null; // there's nothing to render + + expandRight += (viewBox.width - lastWidth); + expandLeft += viewBox.minX; + + var wrapper, canvas; + + if (redraw) { + wrapper = node; + canvas = node.firstChild; + } + else { + wrapper = fabric.document.createElement('span'); + wrapper.className = 'cufon cufon-canvas'; + wrapper.alt = text; + + canvas = fabric.document.createElement('canvas'); + wrapper.appendChild(canvas); + + if (options.printable) { + var print = fabric.document.createElement('span'); + print.className = 'cufon-alt'; + print.appendChild(fabric.document.createTextNode(text)); + wrapper.appendChild(print); + } + } + + var wStyle = wrapper.style; + var cStyle = canvas.style || { }; + + var height = size.convert(viewBox.height - expandTop + expandBottom); + var roundedHeight = Math.ceil(height); + var roundingFactor = roundedHeight / height; + + canvas.width = Math.ceil(size.convert(width + expandRight - expandLeft) * roundingFactor); + canvas.height = roundedHeight; + + expandTop += viewBox.minY; + + cStyle.top = Math.round(size.convert(expandTop - font.ascent)) + 'px'; + cStyle.left = Math.round(size.convert(expandLeft)) + 'px'; + + var _width = Math.ceil(size.convert(width * roundingFactor)); + var wrapperWidth = _width + 'px'; + var _height = size.convert(font.height); + var totalLineHeight = (options.lineHeight - 1) * size.convert(-font.ascent / 5) * (lines - 1); + + Cufon.textOptions.width = _width; + Cufon.textOptions.height = (_height * lines) + totalLineHeight; + Cufon.textOptions.lines = lines; + Cufon.textOptions.totalLineHeight = totalLineHeight; + + if (HAS_INLINE_BLOCK) { + wStyle.width = wrapperWidth; + wStyle.height = _height + 'px'; + } + else { + wStyle.paddingLeft = wrapperWidth; + wStyle.paddingBottom = (_height - 1) + 'px'; + } + + var g = Cufon.textOptions.context || canvas.getContext('2d'), + scale = roundedHeight / viewBox.height; + + Cufon.textOptions.fontAscent = font.ascent * scale; + Cufon.textOptions.boundaries = null; + + for (var offsets = Cufon.textOptions.shadowOffsets, i = shadowOffsets.length; i--; ) { + offsets[i] = [ shadowOffsets[i][0] * scale, shadowOffsets[i][1] * scale ]; + } + + g.save(); + g.scale(scale, scale); + + g.translate( + // we're at the center of an object and need to jump to the top left corner + // where first character is to be drawn + -expandLeft - ((1/scale * canvas.width) / 2) + (Cufon.fonts[font.family].offsetLeft || 0), + -expandTop - ((Cufon.textOptions.height / scale) / 2) + (Cufon.fonts[font.family].offsetTop || 0) + ); + + g.lineWidth = font.face['underline-thickness']; + + g.save(); + + function line(y, color) { + g.strokeStyle = color; + + g.beginPath(); + + g.moveTo(0, y); + g.lineTo(width, y); + + g.stroke(); + } + + var textDecoration = Cufon.getTextDecoration(options), + isItalic = options.fontStyle === 'italic'; + + function renderBackground() { + g.save(); + + g.fillStyle = options.backgroundColor; + + var left = 0, lineNum = 0, boundaries = [{ left: 0 }]; + + if (options.textAlign === 'right') { + g.translate(lineOffsets[lineNum], 0); + boundaries[0].left = lineOffsets[lineNum] * scale; + } + else if (options.textAlign === 'center') { + g.translate(lineOffsets[lineNum] / 2, 0); + boundaries[0].left = lineOffsets[lineNum] / 2 * scale; + } + + for (var i = 0, l = chars.length; i < l; ++i) { + if (chars[i] === '\n') { + + lineNum++; + + var topOffset = -font.ascent - ((font.ascent / 5) * options.lineHeight); + var boundary = boundaries[boundaries.length - 1]; + var nextBoundary = { left: 0 }; + + boundary.width = left * scale; + boundary.height = (-font.ascent + font.descent) * scale; + + if (options.textAlign === 'right') { + g.translate(-width, topOffset); + g.translate(lineOffsets[lineNum], 0); + nextBoundary.left = lineOffsets[lineNum] * scale; + } + else if (options.textAlign === 'center') { + // offset to the start of text in previous line AND half of its offset + // (essentially moving caret to the left edge of bounding box) + g.translate(-left - (lineOffsets[lineNum - 1] / 2), topOffset); + g.translate(lineOffsets[lineNum] / 2, 0); + nextBoundary.left = lineOffsets[lineNum] / 2 * scale; + } + else { + g.translate(-left, topOffset); + } + + /* push next boundary (for the next line) */ + boundaries.push(nextBoundary); + + left = 0; + + continue; + } + var glyph = font.glyphs[chars[i]] || font.missingGlyph; + if (!glyph) continue; + + var charWidth = Number(glyph.w || font.w) + letterSpacing; + + // only draw background when there's some kind of value + if (options.backgroundColor) { + g.save(); + g.translate(0, font.ascent); + g.fillRect(0, 0, charWidth + 10, -font.ascent + font.descent); + g.restore(); + } + + g.translate(charWidth, 0); + left += charWidth; + + if (i == l-1) { + boundaries[boundaries.length - 1].width = left * scale; + boundaries[boundaries.length - 1].height = (-font.ascent + font.descent) * scale; + } + } + g.restore(); + + Cufon.textOptions.boundaries = boundaries; + } + + function renderText(color) { + g.fillStyle = color || Cufon.textOptions.color || style.get('color'); + + var left = 0, lineNum = 0; + + if (options.textAlign === 'right') { + g.translate(lineOffsets[lineNum], 0); + } + else if (options.textAlign === 'center') { + g.translate(lineOffsets[lineNum] / 2, 0); + } + + for (var i = 0, l = chars.length; i < l; ++i) { + if (chars[i] === '\n') { + + lineNum++; + + var topOffset = -font.ascent - ((font.ascent / 5) * options.lineHeight); + + if (options.textAlign === 'right') { + g.translate(-width, topOffset); + g.translate(lineOffsets[lineNum], 0); + } + else if (options.textAlign === 'center') { + // offset to the start of text in previous line AND half of its offset + // (essentially moving caret to the left edge of bounding box) + g.translate(-left - (lineOffsets[lineNum - 1] / 2), topOffset); + g.translate(lineOffsets[lineNum] / 2, 0); + } + else { + g.translate(-left, topOffset); + } + + left = 0; + + continue; + } + var glyph = font.glyphs[chars[i]] || font.missingGlyph; + if (!glyph) continue; + + var charWidth = Number(glyph.w || font.w) + letterSpacing; + + if (textDecoration) { + g.save(); + g.strokeStyle = g.fillStyle; + + // add 2x more thickness — closer to SVG rendering + g.lineWidth += g.lineWidth; + + g.beginPath(); + if (textDecoration.underline) { + g.moveTo(0, -font.face['underline-position'] + 0.5); + g.lineTo(charWidth, -font.face['underline-position'] + 0.5); + } + if (textDecoration.overline) { + g.moveTo(0, font.ascent + 0.5); + g.lineTo(charWidth, font.ascent + 0.5); + } + if (textDecoration['line-through']) { + g.moveTo(0, -font.descent + 0.5); + g.lineTo(charWidth, -font.descent + 0.5); + } + g.stroke(); + g.restore(); + } + + if (isItalic) { + g.save(); + g.transform(1, 0, -0.25, 1, 0, 0); + } + + g.beginPath(); + if (glyph.d) { + if (glyph.code) interpret(glyph.code, g); + else glyph.code = generateFromVML('m' + glyph.d, g); + } + + g.fill(); + + if (options.strokeStyle) { + g.closePath(); + g.save(); + g.lineWidth = options.strokeWidth; + g.strokeStyle = options.strokeStyle; + g.stroke(); + g.restore(); + } + + if (isItalic) { + g.restore(); + } + + g.translate(charWidth, 0); + left += charWidth; + } + } + + g.save(); + renderBackground(); + if (shadows) { + for (var i = 0, l = shadows.length; i < l; ++i) { + var shadow = shadows[i]; + g.save(); + g.translate.apply(g, shadowOffsets[i]); + renderText(shadow.color); + g.restore(); + } + } + renderText(); + g.restore(); + g.restore(); + g.restore(); + + return wrapper; + + }; + +})()); + +Cufon.registerEngine('vml', (function() { + + if (!fabric.document.namespaces) return; + + var canvasEl = fabric.document.createElement('canvas'); + if (canvasEl && canvasEl.getContext && canvasEl.getContext.apply) return; + + if (fabric.document.namespaces.cvml == null) { + fabric.document.namespaces.add('cvml', 'urn:schemas-microsoft-com:vml'); + } + + var check = fabric.document.createElement('cvml:shape'); + check.style.behavior = 'url(#default#VML)'; + if (!check.coordsize) return; // VML isn't supported + check = null; + + fabric.document.write('<style type="text/css">' + + '.cufon-vml-canvas{text-indent:0}' + + '@media screen{' + + 'cvml\\:shape,cvml\\:shadow{behavior:url(#default#VML);display:block;antialias:true;position:absolute}' + + '.cufon-vml-canvas{position:absolute;text-align:left}' + + '.cufon-vml{display:inline-block;position:relative;vertical-align:middle}' + + '.cufon-vml .cufon-alt{position:absolute;left:-10000in;font-size:1px}' + + 'a .cufon-vml{cursor:pointer}' + + '}' + + '@media print{' + + '.cufon-vml *{display:none}' + + '.cufon-vml .cufon-alt{display:inline}' + + '}' + + '</style>'); + + function getFontSizeInPixels(el, value) { + return getSizeInPixels(el, /(?:em|ex|%)$/i.test(value) ? '1em' : value); + } + + // Original by Dead Edwards. + // Combined with getFontSizeInPixels it also works with relative units. + function getSizeInPixels(el, value) { + if (/px$/i.test(value)) return parseFloat(value); + var style = el.style.left, runtimeStyle = el.runtimeStyle.left; + el.runtimeStyle.left = el.currentStyle.left; + el.style.left = value; + var result = el.style.pixelLeft; + el.style.left = style; + el.runtimeStyle.left = runtimeStyle; + return result; + } + + return function(font, text, style, options, node, el, hasNext) { + var redraw = (text === null); + + if (redraw) text = node.alt; + + // @todo word-spacing, text-decoration + + var viewBox = font.viewBox; + + var size = style.computedFontSize || + (style.computedFontSize = new Cufon.CSS.Size(getFontSizeInPixels(el, style.get('fontSize')) + 'px', font.baseSize)); + + var letterSpacing = style.computedLSpacing; + + if (letterSpacing == undefined) { + letterSpacing = style.get('letterSpacing'); + style.computedLSpacing = letterSpacing = + (letterSpacing == 'normal') ? 0 : ~~size.convertFrom(getSizeInPixels(el, letterSpacing)); + } + + var wrapper, canvas; + + if (redraw) { + wrapper = node; + canvas = node.firstChild; + } + else { + wrapper = fabric.document.createElement('span'); + wrapper.className = 'cufon cufon-vml'; + wrapper.alt = text; + + canvas = fabric.document.createElement('span'); + canvas.className = 'cufon-vml-canvas'; + wrapper.appendChild(canvas); + + if (options.printable) { + var print = fabric.document.createElement('span'); + print.className = 'cufon-alt'; + print.appendChild(fabric.document.createTextNode(text)); + wrapper.appendChild(print); + } + + // ie6, for some reason, has trouble rendering the last VML element in the document. + // we can work around this by injecting a dummy element where needed. + // @todo find a better solution + if (!hasNext) wrapper.appendChild(fabric.document.createElement('cvml:shape')); + } + + var wStyle = wrapper.style; + var cStyle = canvas.style; + + var height = size.convert(viewBox.height), roundedHeight = Math.ceil(height); + var roundingFactor = roundedHeight / height; + var minX = viewBox.minX, minY = viewBox.minY; + + cStyle.height = roundedHeight; + cStyle.top = Math.round(size.convert(minY - font.ascent)); + cStyle.left = Math.round(size.convert(minX)); + + wStyle.height = size.convert(font.height) + 'px'; + + var textDecoration = Cufon.getTextDecoration(options); + + var color = style.get('color'); + + var chars = Cufon.CSS.textTransform(text, style).split(''); + + var width = 0, offsetX = 0, advance = null; + + var glyph, shape, shadows = options.textShadow; + + // pre-calculate width + for (var i = 0, k = 0, l = chars.length; i < l; ++i) { + glyph = font.glyphs[chars[i]] || font.missingGlyph; + if (glyph) width += advance = ~~(glyph.w || font.w) + letterSpacing; + } + + if (advance === null) return null; + + var fullWidth = -minX + width + (viewBox.width - advance); + + var shapeWidth = size.convert(fullWidth * roundingFactor), roundedShapeWidth = Math.round(shapeWidth); + + var coordSize = fullWidth + ',' + viewBox.height, coordOrigin; + var stretch = 'r' + coordSize + 'nsnf'; + + for (i = 0; i < l; ++i) { + + glyph = font.glyphs[chars[i]] || font.missingGlyph; + if (!glyph) continue; + + if (redraw) { + // some glyphs may be missing so we can't use i + shape = canvas.childNodes[k]; + if (shape.firstChild) shape.removeChild(shape.firstChild); // shadow + } + else { + shape = fabric.document.createElement('cvml:shape'); + canvas.appendChild(shape); + } + + shape.stroked = 'f'; + shape.coordsize = coordSize; + shape.coordorigin = coordOrigin = (minX - offsetX) + ',' + minY; + shape.path = (glyph.d ? 'm' + glyph.d + 'xe' : '') + 'm' + coordOrigin + stretch; + shape.fillcolor = color; + + // it's important to not set top/left or IE8 will grind to a halt + var sStyle = shape.style; + sStyle.width = roundedShapeWidth; + sStyle.height = roundedHeight; + + if (shadows) { + // due to the limitations of the VML shadow element there + // can only be two visible shadows. opacity is shared + // for all shadows. + var shadow1 = shadows[0], shadow2 = shadows[1]; + var color1 = Cufon.CSS.color(shadow1.color), color2; + var shadow = fabric.document.createElement('cvml:shadow'); + shadow.on = 't'; + shadow.color = color1.color; + shadow.offset = shadow1.offX + ',' + shadow1.offY; + if (shadow2) { + color2 = Cufon.CSS.color(shadow2.color); + shadow.type = 'double'; + shadow.color2 = color2.color; + shadow.offset2 = shadow2.offX + ',' + shadow2.offY; + } + shadow.opacity = color1.opacity || (color2 && color2.opacity) || 1; + shape.appendChild(shadow); + } + + offsetX += ~~(glyph.w || font.w) + letterSpacing; + + ++k; + + } + + wStyle.width = Math.max(Math.ceil(size.convert(width * roundingFactor)), 0); + + return wrapper; + + }; + +})()); + +Cufon.getTextDecoration = function(options) { + return { + underline: options.textDecoration === 'underline', + overline: options.textDecoration === 'overline', + 'line-through': options.textDecoration === 'line-through' + }; +}; + +if (typeof exports != 'undefined') { + exports.Cufon = Cufon; +} + +/* + json2.js + 2011-10-19 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + See http://www.JSON.org/js.html + + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. + + + This file creates a global JSON object containing two methods: stringify + and parse. + + JSON.stringify(value, replacer, space) + value any JavaScript value, usually an object or array. + + replacer an optional parameter that determines how object + values are stringified for objects. It can be a + function or an array of strings. + + space an optional parameter that specifies the indentation + of nested structures. If it is omitted, the text will + be packed without extra whitespace. If it is a number, + it will specify the number of spaces to indent at each + level. If it is a string (such as '\t' or ' '), + it contains the characters used to indent at each level. + + This method produces a JSON text from a JavaScript value. + + When an object value is found, if the object contains a toJSON + method, its toJSON method will be called and the result will be + stringified. A toJSON method does not serialize: it returns the + value represented by the name/value pair that should be serialized, + or undefined if nothing should be serialized. The toJSON method + will be passed the key associated with the value, and this will be + bound to the value + + For example, this would serialize Dates as ISO strings. + + Date.prototype.toJSON = function (key) { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + return this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'; + }; + + You can provide an optional replacer method. It will be passed the + key and value of each member, with this bound to the containing + object. The value that is returned from your method will be + serialized. If your method returns undefined, then the member will + be excluded from the serialization. + + If the replacer parameter is an array of strings, then it will be + used to select the members to be serialized. It filters the results + such that only members with keys listed in the replacer array are + stringified. + + Values that do not have JSON representations, such as undefined or + functions, will not be serialized. Such values in objects will be + dropped; in arrays they will be replaced with null. You can use + a replacer function to replace those with JSON values. + JSON.stringify(undefined) returns undefined. + + The optional space parameter produces a stringification of the + value that is filled with line breaks and indentation to make it + easier to read. + + If the space parameter is a non-empty string, then that string will + be used for indentation. If the space parameter is a number, then + the indentation will be that many spaces. + + Example: + + text = JSON.stringify(['e', {pluribus: 'unum'}]); + // text is '["e",{"pluribus":"unum"}]' + + + text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); + // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + + text = JSON.stringify([new Date()], function (key, value) { + return this[key] instanceof Date ? + 'Date(' + this[key] + ')' : value; + }); + // text is '["Date(---current time---)"]' + + + JSON.parse(text, reviver) + This method parses a JSON text to produce an object or array. + It can throw a SyntaxError exception. + + The optional reviver parameter is a function that can filter and + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. + + Example: + + // Parse the text. Values that look like ISO date strings will + // be converted to Date objects. + + myData = JSON.parse(text, function (key, value) { + var a; + if (typeof value === 'string') { + a = +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); + if (a) { + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], + +a[5], +a[6])); + } + } + return value; + }); + + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { + var d; + if (typeof value === 'string' && + value.slice(0, 5) === 'Date(' && + value.slice(-1) === ')') { + d = new Date(value.slice(5, -1)); + if (d) { + return d; + } + } + return value; + }); + + + This is a reference implementation. You are free to copy, modify, or + redistribute. +*/ + +/*jslint evil: true, regexp: true */ + +/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf +*/ + + +// Create a JSON object only if one does not already exist. We create the +// methods in a closure to avoid creating global variables. + +var JSON; +if (!JSON) { + JSON = {}; +} + +(function () { + 'use strict'; + + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + if (typeof Date.prototype.toJSON !== 'function') { + + Date.prototype.toJSON = function (key) { + + return isFinite(this.valueOf()) + ? this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' + : null; + }; + + String.prototype.toJSON = + Number.prototype.toJSON = + Boolean.prototype.toJSON = function (key) { + return this.valueOf(); + }; + } + + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; + + + function quote(string) { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can safely slap some quotes around it. +// Otherwise we must also replace the offending characters with safe escape +// sequences. + + escapable.lastIndex = 0; + return escapable.test(string) ? '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' + ? c + : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : '"' + string + '"'; + } + + + function str(key, holder) { + +// Produce a string from holder[key]. + + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; + +// If the value has a toJSON method, call it to obtain a replacement value. + + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + +// If we were called with a replacer function, then call the replacer to +// obtain a replacement value. + + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } + +// What happens next depends on the value's type. + + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + +// JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + +// If the value is a boolean or null, convert it to a string. Note: +// typeof null does not produce 'null'. The case is included here in +// the remote chance that this gets fixed someday. + + return String(value); + +// If the type is 'object', we might be dealing with an object or an array or +// null. + + case 'object': + +// Due to a specification blunder in ECMAScript, typeof null is 'object', +// so watch out for that case. + + if (!value) { + return 'null'; + } + +// Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + +// Is the value an array? + + if (Object.prototype.toString.apply(value) === '[object Array]') { + +// The value is an array. Stringify every element. Use null as a placeholder +// for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + +// Join all of the elements together, separated with commas, and wrap them in +// brackets. + + v = partial.length === 0 + ? '[]' + : gap + ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' + : '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + +// If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + if (typeof rep[i] === 'string') { + k = rep[i]; + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } else { + +// Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } + +// Join all of the member texts together, separated with commas, +// and wrap them in braces. + + v = partial.length === 0 + ? '{}' + : gap + ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' + : '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + } + +// If the JSON object does not yet have a stringify method, give it one. + + if (typeof JSON.stringify !== 'function') { + JSON.stringify = function (value, replacer, space) { + +// The stringify method takes a value and an optional replacer, and an optional +// space parameter, and returns a JSON text. The replacer can be a function +// that can replace values, or an array of strings that will select the keys. +// A default replacer method can be provided. Use of the space parameter can +// produce text that is more easily readable. + + var i; + gap = ''; + indent = ''; + +// If the space parameter is a number, make an indent string containing that +// many spaces. + + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; + } + +// If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === 'string') { + indent = space; + } + +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } + +// Make a fake root object containing our value under the key of ''. +// Return the result of stringifying the value. + + return str('', {'': value}); + }; + } + + +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== 'function') { + JSON.parse = function (text, reviver) { + +// The parse method takes a text and an optional reviver function, and returns +// a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + +// The walk method is used to recursively walk the resulting structure so +// that modifications can be made. + + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. + + text = String(text); + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with '()' and 'new' +// because they can cause invocation, and '=' because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around +// crippling inefficiencies in IE's and Safari's regexp engines. First we +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we +// replace all simple value tokens with ']' characters. Third, we delete all +// open brackets that follow a colon or comma or that begin the text. Finally, +// we look to see that the remaining characters are only whitespace or ']' or +// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. + + if (/^[\],:{}\s]*$/ + .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') + .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + +// In the third stage we use the eval function to compile the text into a +// JavaScript structure. The '{' operator is subject to a syntactic ambiguity +// in JavaScript: it can begin a block or an object literal. We wrap the text +// in parens to eliminate the ambiguity. + + j = eval('(' + text + ')'); + +// In the optional fourth stage, we recursively walk the new structure, passing +// each name/value pair to a reviver function for possible transformation. + + return typeof reviver === 'function' + ? walk({'': j}, '') + : j; + } + +// If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError('JSON.parse'); + }; + } +}()); +/** + * Wrapper around `console.log` (when available) + * @method log + * @param {Any} Values to log + */ +fabric.log = function() { }; + +/** + * Wrapper around `console.warn` (when available) + * @method warn + * @param {Any} Values to log as a warning + */ +fabric.warn = function() { }; + +if (typeof console !== 'undefined') { + if (typeof console.log !== 'undefined' && console.log.apply) { + fabric.log = function() { + return console.log.apply(console, arguments); + }; + } + if (typeof console.warn !== 'undefined' && console.warn.apply) { + fabric.warn = function() { + return console.warn.apply(console, arguments); + }; + } +} + +/** + * @namespace + */ +fabric.Observable = { + + /** + * Observes specified event + * @method observe + * @depracated Since 0.8.34. Use `on` instead. + * @param {String} eventName + * @param {Function} handler + */ + observe: function(eventName, handler) { + if (!this.__eventListeners) { + this.__eventListeners = { }; + } + // one object with key/value pairs was passed + if (arguments.length === 1) { + for (var prop in eventName) { + this.on(prop, eventName[prop]); + } + } + else { + if (!this.__eventListeners[eventName]) { + this.__eventListeners[eventName] = [ ]; + } + this.__eventListeners[eventName].push(handler); + } + }, + + /** + * Stops event observing for a particular event handler + * @method stopObserving + * @depracated Since 0.8.34. Use `off` instead. + * @param {String} eventName + * @param {Function} handler + */ + stopObserving: function(eventName, handler) { + if (!this.__eventListeners) { + this.__eventListeners = { }; + } + if (this.__eventListeners[eventName]) { + fabric.util.removeFromArray(this.__eventListeners[eventName], handler); + } + }, + + /** + * Fires event with an optional options object + * @method fire + * @param {String} eventName + * @param {Object} [options] + */ + fire: function(eventName, options) { + if (!this.__eventListeners) { + this.__eventListeners = { } + } + var listenersForEvent = this.__eventListeners[eventName]; + if (!listenersForEvent) return; + for (var i = 0, len = listenersForEvent.length; i < len; i++) { + // avoiding try/catch for perf. reasons + listenersForEvent[i](options || { }); + } + } +}; + +/** + * Alias for observe + * @method observe + * @memberOf fabric.Observable + */ +fabric.Observable.on = fabric.Observable.observe; + +/** + * Alias for stopObserving + * @method off + */ +fabric.Observable.off = fabric.Observable.stopObserving; +(function() { + + /** + * @namespace + */ + fabric.util = { }; + + /** + * Removes value from an array. + * Presence of value (and its position in an array) is determined via `Array.prototype.indexOf` + * @static + * @memberOf fabric.util + * @method removeFromArray + * @param {Array} array + * @param {Any} value + * @return {Array} original array + */ + function removeFromArray(array, value) { + var idx = array.indexOf(value); + if (idx !== -1) { + array.splice(idx, 1); + } + return array; + }; + + /** + * Returns random number between 2 specified ones. + * @static + * @method getRandomInt + * @memberOf fabric.util + * @param {Number} min lower limit + * @param {Number} max upper limit + * @return {Number} random value (between min and max) + */ + function getRandomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; + } + + var PiBy180 = Math.PI / 180; + + /** + * Transforms degrees to radians. + * @static + * @method degreesToRadians + * @memberOf fabric.util + * @param {Number} degrees value in degrees + * @return {Number} value in radians + */ + function degreesToRadians(degrees) { + return degrees * PiBy180; + } + + /** + * A wrapper around Number#toFixed, which contrary to native method returns number, not string. + * @static + * @method toFixed + * @memberOf fabric.util + * @param {Number | String} number number to operate on + * @param {Number} fractionDigits number of fraction digits to "leave" + * @return {Number} + */ + function toFixed(number, fractionDigits) { + return parseFloat(Number(number).toFixed(fractionDigits)); + } + + /** + * Function which always returns `false`. + * @static + * @method falseFunction + * @memberOf fabric.util + * @return {Boolean} + */ + function falseFunction() { + return false; + } + + /** + * Changes value from one to another within certain period of time, invoking callbacks as value is being changed. + * @method animate + * @memberOf fabric.util + * @param {Object} [options] Animation options + * @param {Function} [options.onChange] Callback; invoked on every value change + * @param {Function} [options.onComplete] Callback; invoked when value change is completed + * @param {Number} [options.startValue=0] Starting value + * @param {Number} [options.endValue=100] Ending value + * @param {Number} [options.byValue=100] Value to modify the property by + * @param {Function} [options.easing] Easing function + * @param {Number} [options.duration=500] Duration of change + */ + function animate(options) { + + options || (options = { }); + + var start = +new Date(), + duration = options.duration || 500, + finish = start + duration, time, pos, + onChange = options.onChange || function() { }, + abort = options.abort || function() { return false; }, + easing = options.easing || function(t, b, c, d) {return -c * Math.cos(t/d * (Math.PI/2)) + c + b;}, + startValue = 'startValue' in options ? options.startValue : 0, + endValue = 'endValue' in options ? options.endValue : 100; + byValue = options.byValue || endValue - startValue; + + options.onStart && options.onStart(); + + (function tick() { + time = +new Date(); + currentTime = time > finish ? duration : (time - start); + onChange(easing(currentTime, startValue, byValue, duration)); + if (time > finish || abort()) { + options.onComplete && options.onComplete(); + return; + } + requestAnimFrame(tick); + })(); + } + + var _requestAnimFrame = fabric.window.requestAnimationFrame || + fabric.window.webkitRequestAnimationFrame || + fabric.window.mozRequestAnimationFrame || + fabric.window.oRequestAnimationFrame || + fabric.window.msRequestAnimationFrame || + function(callback, element) { + fabric.window.setTimeout(callback, 1000 / 60); + }; + /** + * requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/ + * @method requestAnimFrame + * @memberOf fabric.util + * @param {Function} callback Callback to invoke + * @param {DOMElement} element optional Element to associate with animation + */ + var requestAnimFrame = function() { + return _requestAnimFrame.apply(fabric.window, arguments); + }; + + /** + * Loads image element from given url and passes it to a callback + * @method loadImage + * @memberOf fabric.util + * @param {String} url URL representing an image + * @param {Function} callback Callback; invoked with loaded image + * @param {Any} context optional Context to invoke callback in + */ + function loadImage(url, callback, context) { + if (url) { + var img = new Image(); + /** @ignore */ + img.onload = function () { + callback && callback.call(context, img); + img = img.onload = null; + }; + img.src = url; + } + else { + callback && callback.call(context, url); + } + } + + function enlivenObjects(objects, callback) { + + function getKlass(type) { + return fabric[fabric.util.string.camelize(fabric.util.string.capitalize(type))]; + } + + function onLoaded() { + if (++numLoadedObjects === numTotalObjects) { + if (callback) { + callback(enlivenedObjects); + } + } + } + + var enlivenedObjects = [ ], + numLoadedObjects = 0, + numTotalObjects = objects.length; + + objects.forEach(function (o, index) { + if (!o.type) { + return; + } + var klass = getKlass(o.type); + if (klass.async) { + klass.fromObject(o, function (o) { + enlivenedObjects[index] = o; + onLoaded(); + }); + } + else { + enlivenedObjects[index] = klass.fromObject(o); + onLoaded(); + } + }); + } + + function groupSVGElements(elements, options, path) { + var object = elements.length > 1 + ? new fabric.PathGroup(elements, options) + : elements[0]; + + if (typeof path !== 'undefined') { + object.setSourcePath(path); + } + return object; + } + + fabric.util.removeFromArray = removeFromArray; + fabric.util.degreesToRadians = degreesToRadians; + fabric.util.toFixed = toFixed; + fabric.util.getRandomInt = getRandomInt; + fabric.util.falseFunction = falseFunction; + fabric.util.animate = animate; + fabric.util.requestAnimFrame = requestAnimFrame; + fabric.util.loadImage = loadImage; + fabric.util.enlivenObjects = enlivenObjects; + fabric.util.groupSVGElements = groupSVGElements; +})(); +(function() { + + var slice = Array.prototype.slice; + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { + if (this === void 0 || this === null) { + throw new TypeError(); + } + var t = Object(this), len = t.length >>> 0; + if (len === 0) { + return -1; + } + var n = 0; + if (arguments.length > 0) { + n = Number(arguments[1]); + if (n !== n) { // shortcut for verifying if it's NaN + n = 0; + } + else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) { + n = (n > 0 || -1) * Math.floor(Math.abs(n)); + } + } + if (n >= len) { + return -1; + } + var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); + for (; k < len; k++) { + if (k in t && t[k] === searchElement) { + return k; + } + } + return -1; + } + } + + if (!Array.prototype.forEach) { + Array.prototype.forEach = function(fn, context) { + for (var i = 0, len = this.length >>> 0; i < len; i++) { + if (i in this) { + fn.call(context, this[i], i, this); + } + } + }; + } + + if (!Array.prototype.map) { + Array.prototype.map = function(fn, context) { + var result = [ ]; + for (var i = 0, len = this.length >>> 0; i < len; i++) { + if (i in this) { + result[i] = fn.call(context, this[i], i, this); + } + } + return result; + }; + } + + if (!Array.prototype.every) { + Array.prototype.every = function(fn, context) { + for (var i = 0, len = this.length >>> 0; i < len; i++) { + if (i in this && !fn.call(context, this[i], i, this)) { + return false; + } + } + return true; + }; + } + + if (!Array.prototype.some) { + Array.prototype.some = function(fn, context) { + for (var i = 0, len = this.length >>> 0; i < len; i++) { + if (i in this && fn.call(context, this[i], i, this)) { + return true; + } + } + return false; + }; + } + + if (!Array.prototype.filter) { + Array.prototype.filter = function(fn, context) { + var result = [ ], val; + for (var i = 0, len = this.length >>> 0; i < len; i++) { + if (i in this) { + val = this[i]; // in case fn mutates this + if (fn.call(context, val, i, this)) { + result.push(val); + } + } + } + return result; + }; + } + + if (!Array.prototype.reduce) { + Array.prototype.reduce = function(fn /*, initial*/) { + var len = this.length >>> 0, + i = 0, + rv; + + if (arguments.length > 1) { + rv = arguments[1]; + } + else { + do { + if (i in this) { + rv = this[i++]; + break; + } + // if array contains no values, no initial value to return + if (++i >= len) { + throw new TypeError(); + } + } + while (true); + } + for (; i < len; i++) { + if (i in this) { + rv = fn.call(null, rv, this[i], i, this); + } + } + return rv; + }; + } + + /** + * Invokes method on all items in a given array + * @method invoke + * @memberOf fabric.util.array + * @param {Array} array Array to iterate over + * @param {String} method Name of a method to invoke + */ + function invoke(array, method) { + var args = slice.call(arguments, 2), result = [ ]; + for (var i = 0, len = array.length; i < len; i++) { + result[i] = args.length ? array[i][method].apply(array[i], args) : array[i][method].call(array[i]); + } + return result; + } + + /** + * Finds maximum value in array (not necessarily "first" one) + * @method max + * @memberOf fabric.util.array + * @param {Array} array Array to iterate over + * @param {String} byProperty + */ + function max(array, byProperty) { + if (!array || array.length === 0) return undefined; + + var i = array.length - 1, + result = byProperty ? array[i][byProperty] : array[i]; + if (byProperty) { + while (i--) { + if (array[i][byProperty] >= result) { + result = array[i][byProperty]; + } + } + } + else { + while (i--) { + if (array[i] >= result) { + result = array[i]; + } + } + } + return result; + } + + /** + * Finds minimum value in array (not necessarily "first" one) + * @method min + * @memberOf fabric.util.array + * @param {Array} array Array to iterate over + * @param {String} byProperty + */ + function min(array, byProperty) { + if (!array || array.length === 0) return undefined; + + var i = array.length - 1, + result = byProperty ? array[i][byProperty] : array[i]; + + if (byProperty) { + while (i--) { + if (array[i][byProperty] < result) { + result = array[i][byProperty]; + } + } + } + else { + while (i--) { + if (array[i] < result) { + result = array[i]; + } + } + } + return result; + } + + /** @namespace */ + fabric.util.array = { + invoke: invoke, + min: min, + max: max + }; + +})(); +(function(){ + + /** + * Copies all enumerable properties of one object to another + * @memberOf fabric.util.object + * @method extend + * @param {Object} destination Where to copy to + * @param {Object} source Where to copy from + */ + function extend(destination, source) { + // JScript DontEnum bug is not taken care of + for (var property in source) { + destination[property] = source[property]; + } + return destination; + } + + /** + * Creates an empty object and copies all enumerable properties of another object to it + * @method clone + * @memberOf fabric.util.object + * @param {Object} object Object to clone + */ + function clone(object) { + return extend({ }, object); + } + + /** @namespace fabric.util.object */ + fabric.util.object = { + extend: extend, + clone: clone + }; + +})(); +(function() { + +if (!String.prototype.trim) { + /** + * Trims a string (removing whitespace from the beginning and the end) + * @method trim + * @see <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/Trim">String#trim on MDN</a> + */ + String.prototype.trim = function () { + // this trim is not fully ES3 or ES5 compliant, but it should cover most cases for now + return this.replace(/^[\s\xA0]+/, '').replace(/[\s\xA0]+$/, ''); + }; +} + +/** + * Camelizes a string + * @memberOf fabric.util.string + * @method camelize + * @param {String} string String to camelize + * @return {String} Camelized version of a string + */ +function camelize(string) { + return string.replace(/-+(.)?/g, function(match, character) { + return character ? character.toUpperCase() : ''; + }); +} + +/** + * Capitalizes a string + * @memberOf fabric.util.string + * @method capitalize + * @param {String} string String to capitalize + * @return {String} Capitalized version of a string + */ +function capitalize(string) { + return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase(); +} + +function escapeXml(string) { + return string.replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(/</g, '<') + .replace(/>/g, '>'); +} + +/** @namespace */ +fabric.util.string = { + camelize: camelize, + capitalize: capitalize, + escapeXml: escapeXml +}; +}()); + +(function() { + + var slice = Array.prototype.slice, + apply = Function.prototype.apply, + dummy = function() { }; + + if (!Function.prototype.bind) { + /** + * Cross-browser approximation of ES5 Function.prototype.bind (not fully spec conforming) + * @see <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind">Function#bind on MDN</a> + * @param {Object} thisArg Object to bind function to + * @param {Any[]} [...] Values to pass to a bound function + * @return {Function} + */ + Function.prototype.bind = function(thisArg) { + var fn = this, args = slice.call(arguments, 1), bound; + if (args.length) { + bound = function() { + return apply.call(fn, this instanceof dummy ? this : thisArg, args.concat(slice.call(arguments))); + }; + } + else { + bound = function() { + return apply.call(fn, this instanceof dummy ? this : thisArg, arguments); + }; + } + dummy.prototype = this.prototype; + bound.prototype = new dummy; + + return bound; + }; + } + +})(); +(function() { + + var slice = Array.prototype.slice, emptyFunction = function() { }; + + var IS_DONTENUM_BUGGY = (function(){ + for (var p in { toString: 1 }) { + if (p === 'toString') return false; + } + return true; + })(); + + /** @ignore */ + var addMethods = function(klass, source, parent) { + for (var property in source) { + + if (property in klass.prototype && typeof klass.prototype[property] == 'function') { + + klass.prototype[property] = (function(property) { + return function() { + + var superclass = this.constructor.superclass; + this.constructor.superclass = parent; + var returnValue = source[property].apply(this, arguments); + this.constructor.superclass = superclass; + + if (property !== 'initialize') { + return returnValue; + } + } + })(property); + } + else { + klass.prototype[property] = source[property]; + } + + if (IS_DONTENUM_BUGGY) { + if (source.toString !== Object.prototype.toString) { + klass.prototype.toString = source.toString; + } + if (source.valueOf !== Object.prototype.valueOf) { + klass.prototype.valueOf = source.valueOf; + } + } + } + }; + + function subclass() { }; + + /** + * Helper for creation of "classes" + * @method createClass + * @memberOf fabric.util + */ + function createClass() { + var parent = null, + properties = slice.call(arguments, 0); + + if (typeof properties[0] === 'function') { + parent = properties.shift(); + } + function klass() { + this.initialize.apply(this, arguments); + } + + klass.superclass = parent; + klass.subclasses = [ ]; + + if (parent) { + subclass.prototype = parent.prototype; + klass.prototype = new subclass; + parent.subclasses.push(klass); + } + for (var i = 0, length = properties.length; i < length; i++) { + addMethods(klass, properties[i], parent); + } + if (!klass.prototype.initialize) { + klass.prototype.initialize = emptyFunction; + } + klass.prototype.constructor = klass; + return klass; + } + + fabric.util.createClass = createClass; +})(); +(function (global) { + + /* EVENT HANDLING */ + + function areHostMethods(object) { + var methodNames = Array.prototype.slice.call(arguments, 1), + t, i, len = methodNames.length; + for (i = 0; i < len; i++) { + t = typeof object[methodNames[i]]; + if (!(/^(?:function|object|unknown)$/).test(t)) return false; + } + return true; + } + var getUniqueId = (function () { + if (typeof fabric.document.documentElement.uniqueID !== 'undefined') { + return function (element) { + return element.uniqueID; + }; + } + var uid = 0; + return function (element) { + return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++); + }; + })(); + + /** @ignore */ + var getElement, setElement; + + (function () { + var elements = { }; + /** @ignore */ + getElement = function (uid) { + return elements[uid]; + }; + /** @ignore */ + setElement = function (uid, element) { + elements[uid] = element; + }; + })(); + + function createListener(uid, handler) { + return { + handler: handler, + wrappedHandler: createWrappedHandler(uid, handler) + }; + } + + function createWrappedHandler(uid, handler) { + return function (e) { + handler.call(getElement(uid), e || fabric.window.event); + }; + } + + function createDispatcher(uid, eventName) { + return function (e) { + if (handlers[uid] && handlers[uid][eventName]) { + var handlersForEvent = handlers[uid][eventName]; + for (var i = 0, len = handlersForEvent.length; i < len; i++) { + handlersForEvent[i].call(this, e || fabric.window.event); + } + } + }; + } + + var shouldUseAddListenerRemoveListener = ( + areHostMethods(fabric.document.documentElement, 'addEventListener', 'removeEventListener') && + areHostMethods(fabric.window, 'addEventListener', 'removeEventListener')), + + shouldUseAttachEventDetachEvent = ( + areHostMethods(fabric.document.documentElement, 'attachEvent', 'detachEvent') && + areHostMethods(fabric.window, 'attachEvent', 'detachEvent')), + + // IE branch + listeners = { }, + + // DOM L0 branch + handlers = { }, + + addListener, removeListener; + + if (shouldUseAddListenerRemoveListener) { + /** @ignore */ + addListener = function (element, eventName, handler) { + element.addEventListener(eventName, handler, false); + }; + /** @ignore */ + removeListener = function (element, eventName, handler) { + element.removeEventListener(eventName, handler, false); + }; + } + + else if (shouldUseAttachEventDetachEvent) { + /** @ignore */ + addListener = function (element, eventName, handler) { + var uid = getUniqueId(element); + setElement(uid, element); + if (!listeners[uid]) { + listeners[uid] = { }; + } + if (!listeners[uid][eventName]) { + listeners[uid][eventName] = [ ]; + + } + var listener = createListener(uid, handler); + listeners[uid][eventName].push(listener); + element.attachEvent('on' + eventName, listener.wrappedHandler); + }; + /** @ignore */ + removeListener = function (element, eventName, handler) { + var uid = getUniqueId(element), listener; + if (listeners[uid] && listeners[uid][eventName]) { + for (var i = 0, len = listeners[uid][eventName].length; i < len; i++) { + listener = listeners[uid][eventName][i]; + if (listener && listener.handler === handler) { + element.detachEvent('on' + eventName, listener.wrappedHandler); + listeners[uid][eventName][i] = null; + } + } + } + }; + } + else { + /** @ignore */ + addListener = function (element, eventName, handler) { + var uid = getUniqueId(element); + if (!handlers[uid]) { + handlers[uid] = { }; + } + if (!handlers[uid][eventName]) { + handlers[uid][eventName] = [ ]; + var existingHandler = element['on' + eventName]; + if (existingHandler) { + handlers[uid][eventName].push(existingHandler); + } + element['on' + eventName] = createDispatcher(uid, eventName); + } + handlers[uid][eventName].push(handler); + }; + /** @ignore */ + removeListener = function (element, eventName, handler) { + var uid = getUniqueId(element); + if (handlers[uid] && handlers[uid][eventName]) { + var handlersForEvent = handlers[uid][eventName]; + for (var i = 0, len = handlersForEvent.length; i < len; i++) { + if (handlersForEvent[i] === handler) { + handlersForEvent.splice(i, 1); + } + } + } + }; + } + + /** + * Adds an event listener to an element + * @mthod addListener + * @memberOf fabric.util + * @function + * @param {HTMLElement} element + * @param {String} eventName + * @param {Function} handler + */ + fabric.util.addListener = addListener; + + /** + * Removes an event listener from an element + * @mthod removeListener + * @memberOf fabric.util + * @function + * @param {HTMLElement} element + * @param {String} eventName + * @param {Function} handler + */ + fabric.util.removeListener = removeListener; + + /** + * Cross-browser wrapper for getting event's coordinates + * @method getPointer + * @memberOf fabric.util + * @param {Event} event + */ + function getPointer(event) { + // TODO (kangax): this method needs fixing + return { x: pointerX(event), y: pointerY(event) }; + } + + function pointerX(event) { + var docElement = fabric.document.documentElement, + body = fabric.document.body || { scrollLeft: 0 }; + + // looks like in IE (<9) clientX at certain point (apparently when mouseup fires on VML element) + // is represented as COM object, with all the consequences, like "unknown" type and error on [[Get]] + // need to investigate later + return event.pageX || ((typeof event.clientX != 'unknown' ? event.clientX : 0) + + (docElement.scrollLeft || body.scrollLeft) - + (docElement.clientLeft || 0)); + } + + function pointerY(event) { + var docElement = fabric.document.documentElement, + body = fabric.document.body || { scrollTop: 0 }; + + return event.pageY || ((typeof event.clientY != 'unknown' ? event.clientY : 0) + + (docElement.scrollTop || body.scrollTop) - + (docElement.clientTop || 0)); + } + + if (fabric.isTouchSupported) { + pointerX = function(event) { + return event.touches && event.touches[0] && event.touches[0].pageX || event.clientX; + }; + pointerY = function(event) { + return event.touches && event.touches[0] && event.touches[0].pageY || event.clientY; + }; + } + + fabric.util.getPointer = getPointer; + + fabric.util.object.extend(fabric.util, fabric.Observable); + +})(this); +(function () { + + /** + * Cross-browser wrapper for setting element's style + * @method setStyle + * @memberOf fabric.util + * @param {HTMLElement} element + * @param {Object} styles + * @return {HTMLElement} Element that was passed as a first argument + */ + function setStyle(element, styles) { + var elementStyle = element.style, match; + if (!elementStyle) { + return element; + } + if (typeof styles === 'string') { + element.style.cssText += ';' + styles; + return styles.indexOf('opacity') > -1 + ? setOpacity(element, styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) + : element; + } + for (var property in styles) { + if (property === 'opacity') { + setOpacity(element, styles[property]); + } + else { + var normalizedProperty = (property === 'float' || property === 'cssFloat') + ? (typeof elementStyle.styleFloat === 'undefined' ? 'cssFloat' : 'styleFloat') + : property; + elementStyle[normalizedProperty] = styles[property]; + } + } + return element; + } + + var parseEl = fabric.document.createElement('div'), + supportsOpacity = typeof parseEl.style.opacity === 'string', + supportsFilters = typeof parseEl.style.filter === 'string', + view = fabric.document.defaultView, + supportsGCS = view && typeof view.getComputedStyle !== 'undefined', + reOpacity = /alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/, + + /** @ignore */ + setOpacity = function (element) { return element; }; + + if (supportsOpacity) { + /** @ignore */ + setOpacity = function(element, value) { + element.style.opacity = value; + return element; + }; + } + else if (supportsFilters) { + /** @ignore */ + setOpacity = function(element, value) { + var es = element.style; + if (element.currentStyle && !element.currentStyle.hasLayout) { + es.zoom = 1; + } + if (reOpacity.test(es.filter)) { + value = value >= 0.9999 ? '' : ('alpha(opacity=' + (value * 100) + ')'); + es.filter = es.filter.replace(reOpacity, value); + } + else { + es.filter += ' alpha(opacity=' + (value * 100) + ')'; + } + return element; + }; + } + + fabric.util.setStyle = setStyle; + +})(); +(function() { + + var _slice = Array.prototype.slice; + + /** + * Takes id and returns an element with that id (if one exists in a document) + * @method getById + * @memberOf fabric.util + * @param {String|HTMLElement} id + * @return {HTMLElement|null} + */ + function getById(id) { + return typeof id === 'string' ? fabric.document.getElementById(id) : id; + } + + /** + * Converts an array-like object (e.g. arguments or NodeList) to an array + * @method toArray + * @memberOf fabric.util + * @param {Object} arrayLike + * @return {Array} + */ + function toArray(arrayLike) { + return _slice.call(arrayLike, 0); + } + + try { + var sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array; + } + catch(err) { } + + if (!sliceCanConvertNodelists) { + toArray = function(arrayLike) { + var arr = new Array(arrayLike.length), i = arrayLike.length; + while (i--) { + arr[i] = arrayLike[i]; + } + return arr; + }; + } + + /** + * Creates specified element with specified attributes + * @method makeElement + * @memberOf fabric.util + * @param {String} tagName Type of an element to create + * @param {Object} [attributes] Attributes to set on an element + * @return {HTMLElement} Newly created element + */ + function makeElement(tagName, attributes) { + var el = fabric.document.createElement(tagName); + for (var prop in attributes) { + if (prop === 'class') { + el.className = attributes[prop]; + } + else if (prop === 'for') { + el.htmlFor = attributes[prop]; + } + else { + el.setAttribute(prop, attributes[prop]); + } + } + return el; + } + + /** + * Adds class to an element + * @method addClass + * @memberOf fabric.util + * @param {HTMLElement} element Element to add class to + * @param {String} className Class to add to an element + */ + function addClass(element, className) { + if ((' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) { + element.className += (element.className ? ' ' : '') + className; + } + } + + /** + * Wraps element with another element + * @method wrapElement + * @memberOf fabric.util + * @param {HTMLElement} element Element to wrap + * @param {HTMLElement|String} wrapper Element to wrap with + * @param {Object} [attributes] Attributes to set on a wrapper + * @return {HTMLElement} wrapper + */ + function wrapElement(element, wrapper, attributes) { + if (typeof wrapper === 'string') { + wrapper = makeElement(wrapper, attributes); + } + if (element.parentNode) { + element.parentNode.replaceChild(wrapper, element); + } + wrapper.appendChild(element); + return wrapper; + } + + /** + * Returns offset for a given element + * @method getElementOffset + * @function + * @memberOf fabric.util + * @param {HTMLElement} element Element to get offset for + * @return {Object} Object with "left" and "top" properties + */ + function getElementOffset(element) { + // TODO (kangax): need to fix this method + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } + while (element); + return ({ left: valueL, top: valueT }); + } + + (function () { + var style = fabric.document.documentElement.style; + + var selectProp = 'userSelect' in style + ? 'userSelect' + : 'MozUserSelect' in style + ? 'MozUserSelect' + : 'WebkitUserSelect' in style + ? 'WebkitUserSelect' + : 'KhtmlUserSelect' in style + ? 'KhtmlUserSelect' + : ''; + + /** + * Makes element unselectable + * @method makeElementUnselectable + * @memberOf fabric.util + * @param {HTMLElement} element Element to make unselectable + * @return {HTMLElement} Element that was passed in + */ + function makeElementUnselectable(element) { + if (typeof element.onselectstart !== 'undefined') { + element.onselectstart = fabric.util.falseFunction; + } + if (selectProp) { + element.style[selectProp] = 'none'; + } + else if (typeof element.unselectable == 'string') { + element.unselectable = 'on'; + } + return element; + } + + /** + * Makes element selectable + * @method makeElementSelectable + * @memberOf fabric.util + * @param {HTMLElement} element Element to make selectable + * @return {HTMLElement} Element that was passed in + */ + function makeElementSelectable(element) { + if (typeof element.onselectstart !== 'undefined') { + element.onselectstart = null; + } + if (selectProp) { + element.style[selectProp] = ''; + } + else if (typeof element.unselectable == 'string') { + element.unselectable = ''; + } + return element; + } + + fabric.util.makeElementUnselectable = makeElementUnselectable; + fabric.util.makeElementSelectable = makeElementSelectable; + })(); + + (function() { + + /** + * Inserts a script element with a given url into a document; invokes callback, when that script is finished loading + * @method getScript + * @memberOf fabric.util + * @param {String} url URL of a script to load + * @param {Function} callback Callback to execute when script is finished loading + */ + function getScript(url, callback) { + var headEl = fabric.document.getElementsByTagName("head")[0], + scriptEl = fabric.document.createElement('script'), + loading = true; + + scriptEl.type = 'text/javascript'; + scriptEl.setAttribute('runat', 'server'); + + /** @ignore */ + scriptEl.onload = /** @ignore */ scriptEl.onreadystatechange = function(e) { + if (loading) { + if (typeof this.readyState == 'string' && + this.readyState !== 'loaded' && + this.readyState !== 'complete') return; + loading = false; + callback(e || fabric.window.event); + scriptEl = scriptEl.onload = scriptEl.onreadystatechange = null; + } + }; + scriptEl.src = url; + headEl.appendChild(scriptEl); + // causes issue in Opera + // headEl.removeChild(scriptEl); + } + + fabric.util.getScript = getScript; + })(); + + fabric.util.getById = getById; + fabric.util.toArray = toArray; + fabric.util.makeElement = makeElement; + fabric.util.addClass = addClass; + fabric.util.wrapElement = wrapElement; + fabric.util.getElementOffset = getElementOffset; + +})(); +(function(){ + + function addParamToUrl(url, param) { + return url + (/\?/.test(url) ? '&' : '?') + param; + } + + var makeXHR = (function() { + var factories = [ + function() { return new ActiveXObject("Microsoft.XMLHTTP"); }, + function() { return new ActiveXObject("Msxml2.XMLHTTP"); }, + function() { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); }, + function() { return new XMLHttpRequest(); } + ]; + for (var i = factories.length; i--; ) { + try { + var req = factories[i](); + if (req) { + return factories[i]; + } + } + catch (err) { } + } + })(); + + function emptyFn() { }; + + /** + * Cross-browser abstraction for sending XMLHttpRequest + * @method request + * @memberOf fabric.util + * @param {String} url URL to send XMLHttpRequest to + * @param {Object} [options] Options object + * @param {String} [options.method="GET"] + * @param {Function} options.onComplete Callback to invoke when request is completed + * @return {XMLHttpRequest} request + */ + function request(url, options) { + + options || (options = { }); + + var method = options.method ? options.method.toUpperCase() : 'GET', + onComplete = options.onComplete || function() { }, + request = makeXHR(), + body; + + /** @ignore */ + request.onreadystatechange = function() { + if (request.readyState === 4) { + onComplete(request); + request.onreadystatechange = emptyFn; + } + }; + + if (method === 'GET') { + body = null; + if (typeof options.parameters == 'string') { + url = addParamToUrl(url, options.parameters); + } + } + + request.open(method, url, true); + + if (method === 'POST' || method === 'PUT') { + request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + } + + request.send(body); + return request; + }; + + fabric.util.request = request; +})(); +(function() { + + /** + * @method easeInQuad + * @memberOf fabric.util.ease + */ + function easeInQuad(t, b, c, d) { + return c*(t/=d)*t + b; + } + + /** + * @method easeOutQuad + * @memberOf fabric.util.ease + */ + function easeOutQuad(t, b, c, d) { + return -c *(t/=d)*(t-2) + b; + } + + /** + * @method easeInOutQuad + * @memberOf fabric.util.ease + */ + function easeInOutQuad(t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t + b; + return -c/2 * ((--t)*(t-2) - 1) + b; + } + + /** + * @method easeInCubic + * @memberOf fabric.util.ease + */ + function easeInCubic(t, b, c, d) { + return c*(t/=d)*t*t + b; + } + + /** + * @method easeOutCubic + * @memberOf fabric.util.ease + */ + function easeOutCubic(t, b, c, d) { + return c*((t=t/d-1)*t*t + 1) + b; + } + + /** + * @method easeInOutCubic + * @memberOf fabric.util.ease + */ + function easeInOutCubic(t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t*t + b; + return c/2*((t-=2)*t*t + 2) + b; + } + + /** + * @method easeInQuart + * @memberOf fabric.util.ease + */ + function easeInQuart(t, b, c, d) { + return c*(t/=d)*t*t*t + b; + } + + /** + * @method easeOutQuart + * @memberOf fabric.util.ease + */ + function easeOutQuart(t, b, c, d) { + return -c * ((t=t/d-1)*t*t*t - 1) + b; + } + + /** + * @method easeInOutQuart + * @memberOf fabric.util.ease + */ + function easeInOutQuart(t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t*t*t + b; + return -c/2 * ((t-=2)*t*t*t - 2) + b; + } + + /** + * @method easeInQuint + * @memberOf fabric.util.ease + */ + function easeInQuint(t, b, c, d) { + return c*(t/=d)*t*t*t*t + b; + } + + /** + * @method easeOutQuint + * @memberOf fabric.util.ease + */ + function easeOutQuint(t, b, c, d) { + return c*((t=t/d-1)*t*t*t*t + 1) + b; + } + + /** + * @method easeInOutQuint + * @memberOf fabric.util.ease + */ + function easeInOutQuint(t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b; + return c/2*((t-=2)*t*t*t*t + 2) + b; + } + + /** + * @method easeInSine + * @memberOf fabric.util.ease + */ + function easeInSine(t, b, c, d) { + return -c * Math.cos(t/d * (Math.PI/2)) + c + b; + } + + /** + * @method easeOutSine + * @memberOf fabric.util.ease + */ + function easeOutSine(t, b, c, d) { + return c * Math.sin(t/d * (Math.PI/2)) + b; + } + + /** + * @method easeInOutSine + * @memberOf fabric.util.ease + */ + function easeInOutSine(t, b, c, d) { + return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b; + } + + /** + * @method easeInExpo + * @memberOf fabric.util.ease + */ + function easeInExpo(t, b, c, d) { + return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b; + } + + /** + * @method easeOutExpo + * @memberOf fabric.util.ease + */ + function easeOutExpo(t, b, c, d) { + return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b; + } + + /** + * @method easeInOutExpo + * @memberOf fabric.util.ease + */ + function easeInOutExpo(t, b, c, d) { + if (t==0) return b; + if (t==d) return b+c; + if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b; + return c/2 * (-Math.pow(2, -10 * --t) + 2) + b; + } + + /** + * @method easeInCirc + * @memberOf fabric.util.ease + */ + function easeInCirc(t, b, c, d) { + return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b; + } + + /** + * @method easeOutCirc + * @memberOf fabric.util.ease + */ + function easeOutCirc(t, b, c, d) { + return c * Math.sqrt(1 - (t=t/d-1)*t) + b; + } + + /** + * @method easeInOutCirc + * @memberOf fabric.util.ease + */ + function easeInOutCirc(t, b, c, d) { + if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b; + return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b; + } + + /** + * @method easeInElastic + * @memberOf fabric.util.ease + */ + function easeInElastic(t, b, c, d) { + var s=1.70158;var p=0;var a=c; + if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3; + if (a < Math.abs(c)) { a=c; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (c/a); + return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b; + } + + /** + * @method easeOutElastic + * @memberOf fabric.util.ease + */ + function easeOutElastic(t, b, c, d) { + var s=1.70158;var p=0;var a=c; + if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3; + if (a < Math.abs(c)) { a=c; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (c/a); + return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b; + } + + /** + * @method easeInOutElastic + * @memberOf fabric.util.ease + */ + function easeInOutElastic(t, b, c, d) { + var s=1.70158;var p=0;var a=c; + if (t==0) return b; if ((t/=d/2)==2) return b+c; if (!p) p=d*(.3*1.5); + if (a < Math.abs(c)) { a=c; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (c/a); + if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b; + return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b; + } + + /** + * @method easeInBack + * @memberOf fabric.util.ease + */ + function easeInBack(t, b, c, d, s) { + if (s == undefined) s = 1.70158; + return c*(t/=d)*t*((s+1)*t - s) + b; + } + + /** + * @method easeOutBack + * @memberOf fabric.util.ease + */ + function easeOutBack(t, b, c, d, s) { + if (s == undefined) s = 1.70158; + return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b; + } + + /** + * @method easeInOutBack + * @memberOf fabric.util.ease + */ + function easeInOutBack(t, b, c, d, s) { + if (s == undefined) s = 1.70158; + if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b; + return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b; + } + + /** + * @method easeInBounce + * @memberOf fabric.util.ease + */ + function easeInBounce(t, b, c, d) { + return c - easeOutBounce (d-t, 0, c, d) + b; + } + + /** + * @method easeOutBounce + * @memberOf fabric.util.ease + */ + function easeOutBounce(t, b, c, d) { + if ((t/=d) < (1/2.75)) { + return c*(7.5625*t*t) + b; + } else if (t < (2/2.75)) { + return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b; + } else if (t < (2.5/2.75)) { + return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b; + } else { + return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b; + } + } + + /** + * @method easeInOutBounce + * @memberOf fabric.util.ease + */ + function easeInOutBounce(t, b, c, d) { + if (t < d/2) return easeInBounce (t*2, 0, c, d) * .5 + b; + return easeOutBounce (t*2-d, 0, c, d) * .5 + c*.5 + b; + } + + /** @namespace fabric.util.ease */ + fabric.util.ease = { + easeInQuad: easeInQuad, + easeOutQuad: easeOutQuad, + easeInOutQuad: easeInOutQuad, + easeInCubic: easeInCubic, + easeOutCubic: easeOutCubic, + easeInOutCubic: easeInOutCubic, + easeInQuart: easeInQuart, + easeOutQuart: easeOutQuart, + easeInOutQuart: easeInOutQuart, + easeInQuint: easeInQuint, + easeOutQuint: easeOutQuint, + easeInOutQuint: easeInOutQuint, + easeInSine: easeInSine, + easeOutSine: easeOutSine, + easeInOutSine: easeInOutSine, + easeInExpo: easeInExpo, + easeOutExpo: easeOutExpo, + easeInOutExpo: easeInOutExpo, + easeInCirc: easeInCirc, + easeOutCirc: easeOutCirc, + easeInOutCirc: easeInOutCirc, + easeInElastic: easeInElastic, + easeOutElastic: easeOutElastic, + easeInOutElastic: easeInOutElastic, + easeInBack: easeInBack, + easeOutBack: easeOutBack, + easeInOutBack: easeInOutBack, + easeInBounce: easeInBounce, + easeOutBounce: easeOutBounce, + easeInOutBounce: easeInOutBounce + }; + +}()); +(function(global) { + + "use strict"; + + /** + * @name fabric + * @namespace + */ + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + capitalize = fabric.util.string.capitalize, + clone = fabric.util.object.clone; + + var attributesMap = { + 'cx': 'left', + 'x': 'left', + 'cy': 'top', + 'y': 'top', + 'r': 'radius', + 'fill-opacity': 'opacity', + 'fill-rule': 'fillRule', + 'stroke-width': 'strokeWidth', + 'transform': 'transformMatrix', + 'text-decoration': 'textDecoration', + 'font-size': 'fontSize', + 'font-weight': 'fontWeight', + 'font-style': 'fontStyle', + 'font-family': 'fontFamily' + }; + + function normalizeAttr(attr) { + // transform attribute names + if (attr in attributesMap) { + return attributesMap[attr]; + } + return attr; + } + + /** + * Returns an object of attributes' name/value, given element and an array of attribute names; + * Parses parent "g" nodes recursively upwards. + * @static + * @memberOf fabric + * @method parseAttributes + * @param {DOMElement} element Element to parse + * @param {Array} attributes Array of attributes to parse + * @return {Object} object containing parsed attributes' names/values + */ + function parseAttributes(element, attributes) { + + if (!element) { + return; + } + + var value, + parsed, + parentAttributes = { }; + + // if there's a parent container (`g` node), parse its attributes recursively upwards + if (element.parentNode && /^g$/i.test(element.parentNode.nodeName)) { + parentAttributes = fabric.parseAttributes(element.parentNode, attributes); + } + + var ownAttributes = attributes.reduce(function(memo, attr) { + value = element.getAttribute(attr); + parsed = parseFloat(value); + if (value) { + // "normalize" attribute values + if ((attr === 'fill' || attr === 'stroke') && value === 'none') { + value = ''; + } + if (attr === 'fill-rule') { + value = (value === 'evenodd') ? 'destination-over' : value; + } + if (attr === 'transform') { + value = fabric.parseTransformAttribute(value); + } + attr = normalizeAttr(attr); + memo[attr] = isNaN(parsed) ? value : parsed; + } + return memo; + }, { }); + + // add values parsed from style, which take precedence over attributes + // (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes) + + ownAttributes = extend(ownAttributes, extend(getGlobalStylesForElement(element), fabric.parseStyleAttribute(element))); + return extend(parentAttributes, ownAttributes); + }; + + /** + * Parses "transform" attribute, returning an array of values + * @static + * @function + * @memberOf fabric + * @method parseTransformAttribute + * @param attributeValue {String} string containing attribute value + * @return {Array} array of 6 elements representing transformation matrix + */ + fabric.parseTransformAttribute = (function() { + function rotateMatrix(matrix, args) { + var angle = args[0]; + + matrix[0] = Math.cos(angle); + matrix[1] = Math.sin(angle); + matrix[2] = -Math.sin(angle); + matrix[3] = Math.cos(angle); + } + + function scaleMatrix(matrix, args) { + var multiplierX = args[0], + multiplierY = (args.length === 2) ? args[1] : args[0]; + + matrix[0] = multiplierX; + matrix[3] = multiplierY; + } + + function skewXMatrix(matrix, args) { + matrix[2] = args[0]; + } + + function skewYMatrix(matrix, args) { + matrix[1] = args[0]; + } + + function translateMatrix(matrix, args) { + matrix[4] = args[0]; + if (args.length === 2) { + matrix[5] = args[1]; + } + } + + // identity matrix + var iMatrix = [ + 1, // a + 0, // b + 0, // c + 1, // d + 0, // e + 0 // f + ], + + // == begin transform regexp + number = '(?:[-+]?\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?)', + comma_wsp = '(?:\\s+,?\\s*|,\\s*)', + + skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))', + skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))', + rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' + comma_wsp + '(' + number + ')' + comma_wsp + '(' + number + '))?\\s*\\))', + scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' + comma_wsp + '(' + number + '))?\\s*\\))', + translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' + comma_wsp + '(' + number + '))?\\s*\\))', + + matrix = '(?:(matrix)\\s*\\(\\s*' + + '(' + number + ')' + comma_wsp + + '(' + number + ')' + comma_wsp + + '(' + number + ')' + comma_wsp + + '(' + number + ')' + comma_wsp + + '(' + number + ')' + comma_wsp + + '(' + number + ')' + + '\\s*\\))', + + transform = '(?:' + + matrix + '|' + + translate + '|' + + scale + '|' + + rotate + '|' + + skewX + '|' + + skewY + + ')', + + transforms = '(?:' + transform + '(?:' + comma_wsp + transform + ')*' + ')', + + transform_list = '^\\s*(?:' + transforms + '?)\\s*$', + + // http://www.w3.org/TR/SVG/coords.html#TransformAttribute + reTransformList = new RegExp(transform_list), + // == end transform regexp + + reTransform = new RegExp(transform); + + return function(attributeValue) { + + // start with identity matrix + var matrix = iMatrix.concat(); + + // return if no argument was given or + // an argument does not match transform attribute regexp + if (!attributeValue || (attributeValue && !reTransformList.test(attributeValue))) { + return matrix; + } + + attributeValue.replace(reTransform, function(match) { + + var m = new RegExp(transform).exec(match).filter(function (match) { + return (match !== '' && match != null); + }), + operation = m[1], + args = m.slice(2).map(parseFloat); + + switch(operation) { + case 'translate': + translateMatrix(matrix, args); + break; + case 'rotate': + rotateMatrix(matrix, args); + break; + case 'scale': + scaleMatrix(matrix, args); + break; + case 'skewX': + skewXMatrix(matrix, args); + break; + case 'skewY': + skewYMatrix(matrix, args); + break; + case 'matrix': + matrix = args; + break; + } + }) + return matrix; + } + })(); + + /** + * Parses "points" attribute, returning an array of values + * @static + * @memberOf fabric + * @method parsePointsAttribute + * @param points {String} points attribute string + * @return {Array} array of points + */ + function parsePointsAttribute(points) { + + // points attribute is required and must not be empty + if (!points) return null; + + points = points.trim(); + var asPairs = points.indexOf(',') > -1; + + points = points.split(/\s+/); + var parsedPoints = [ ]; + + // points could look like "10,20 30,40" or "10 20 30 40" + if (asPairs) { + for (var i = 0, len = points.length; i < len; i++) { + var pair = points[i].split(','); + parsedPoints.push({ x: parseFloat(pair[0]), y: parseFloat(pair[1]) }); + } + } + else { + for (var i = 0, len = points.length; i < len; i+=2) { + parsedPoints.push({ x: parseFloat(points[i]), y: parseFloat(points[i+1]) }); + } + } + + // odd number of points is an error + if (parsedPoints.length % 2 !== 0) { + // return null; + } + + return parsedPoints; + }; + + /** + * Parses "style" attribute, retuning an object with values + * @static + * @memberOf fabric + * @method parseStyleAttribute + * @param {SVGElement} element Element to parse + * @return {Object} Objects with values parsed from style attribute of an element + */ + function parseStyleAttribute(element) { + var oStyle = { }, + style = element.getAttribute('style'); + if (style) { + if (typeof style == 'string') { + style = style.replace(/;$/, '').split(';').forEach(function (current) { + var attr = current.split(':'); + oStyle[normalizeAttr(attr[0].trim().toLowerCase())] = attr[1].trim(); + }); + } else { + for (var prop in style) { + if (typeof style[prop] !== 'undefined') { + oStyle[normalizeAttr(prop.toLowerCase())] = style[prop]; + } + } + } + } + return oStyle; + }; + + function resolveGradients(instances) { + for (var i = instances.length; i--; ) { + var instanceFillValue = instances[i].get('fill'); + + if (/^url\(/.test(instanceFillValue)) { + + var gradientId = instanceFillValue.slice(5, instanceFillValue.length - 1); + + if (fabric.gradientDefs[gradientId]) { + instances[i].set('fill', + fabric.Gradient.fromElement(fabric.gradientDefs[gradientId], instances[i])); + } + } + } + } + + /** + * Transforms an array of svg elements to corresponding fabric.* instances + * @static + * @memberOf fabric + * @method parseElements + * @param {Array} elements Array of elements to parse + * @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements) + * @param {Object} options Options object + * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. + */ + function parseElements(elements, callback, options, reviver) { + var instances = Array(elements.length), i = elements.length; + + function checkIfDone() { + if (--i === 0) { + instances = instances.filter(function(el) { + return el != null; + }); + resolveGradients(instances); + callback(instances); + } + } + + for (var index = 0, el, len = elements.length; index < len; index++) { + el = elements[index]; + var klass = fabric[capitalize(el.tagName)]; + if (klass && klass.fromElement) { + try { + if (klass.async) { + klass.fromElement(el, (function(index, el) { + return function(obj) { + reviver && reviver(el, obj); + instances.splice(index, 0, obj); + checkIfDone(); + }; + })(index), options); + } + else { + var obj = klass.fromElement(el, options); + reviver && reviver(el, obj); + instances.splice(index, 0, obj); + checkIfDone(); + } + } + catch(e) { + fabric.log(e.message || e); + } + } + else { + checkIfDone(); + } + } + }; + + /** + * Returns CSS rules for a given SVG document + * @static + * @function + * @memberOf fabric + * @method getCSSRules + * @param {SVGDocument} doc SVG document to parse + * @return {Object} CSS rules of this document + */ + function getCSSRules(doc) { + var styles = doc.getElementsByTagName('style'), + allRules = { }, + rules; + + // very crude parsing of style contents + for (var i = 0, len = styles.length; i < len; i++) { + var styleContents = styles[0].textContent; + + // remove comments + styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, ''); + + rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g); + rules = rules.map(function(rule) { return rule.trim() }); + + rules.forEach(function(rule) { + var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/), + rule = match[1], + declaration = match[2].trim(), + propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/); + + if (!allRules[rule]) { + allRules[rule] = { }; + } + + for (var i = 0, len = propertyValuePairs.length; i < len; i++) { + var pair = propertyValuePairs[i].split(/\s*:\s*/), + property = pair[0], + value = pair[1]; + + allRules[rule][property] = value; + } + }); + } + + return allRules; + } + + function getGlobalStylesForElement(element) { + var nodeName = element.nodeName, + className = element.getAttribute('class'), + id = element.getAttribute('id'), + styles = { }; + + for (var rule in fabric.cssRules) { + var ruleMatchesElement = (className && new RegExp('^\\.' + className).test(rule)) || + (id && new RegExp('^#' + id).test(rule)) || + (new RegExp('^' + nodeName).test(rule)); + + if (ruleMatchesElement) { + for (var property in fabric.cssRules[rule]) { + styles[property] = fabric.cssRules[rule][property]; + } + } + } + + return styles; + } + + /** + * Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback + * @static + * @function + * @memberOf fabric + * @method parseSVGDocument + * @param {SVGDocument} doc SVG document to parse + * @param {Function} callback Callback to call when parsing is finished; It's being passed an array of elements (parsed from a document). + * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. + */ + fabric.parseSVGDocument = (function() { + + var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/; + + // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute + // \d doesn't quite cut it (as we need to match an actual float number) + + // matches, e.g.: +14.56e-12, etc. + var reNum = '(?:[-+]?\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?)'; + + var reViewBoxAttrValue = new RegExp( + '^' + + '\\s*(' + reNum + '+)\\s*,?' + + '\\s*(' + reNum + '+)\\s*,?' + + '\\s*(' + reNum + '+)\\s*,?' + + '\\s*(' + reNum + '+)\\s*' + + '$' + ); + + function hasAncestorWithNodeName(element, nodeName) { + while (element && (element = element.parentNode)) { + if (nodeName.test(element.nodeName)) { + return true; + } + } + return false; + } + + return function(doc, callback, reviver) { + if (!doc) return; + + var startTime = new Date(), + descendants = fabric.util.toArray(doc.getElementsByTagName('*')); + + if (descendants.length === 0) { + // we're likely in node, where "o3-xml" library fails to gEBTN("*") + // https://github.com/ajaxorg/node-o3-xml/issues/21 + descendants = doc.selectNodes("//*[name(.)!='svg']"); + var arr = [ ]; + for (var i = 0, len = descendants.length; i < len; i++) { + arr[i] = descendants[i]; + } + descendants = arr; + } + + var elements = descendants.filter(function(el) { + return reAllowedSVGTagNames.test(el.tagName) && + !hasAncestorWithNodeName(el, /^(?:pattern|defs)$/); // http://www.w3.org/TR/SVG/struct.html#DefsElement + }); + + if (!elements || (elements && !elements.length)) return; + + var viewBoxAttr = doc.getAttribute('viewBox'), + widthAttr = doc.getAttribute('width'), + heightAttr = doc.getAttribute('height'), + width = null, + height = null, + minX, + minY; + + if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) { + minX = parseInt(viewBoxAttr[1], 10); + minY = parseInt(viewBoxAttr[2], 10); + width = parseInt(viewBoxAttr[3], 10); + height = parseInt(viewBoxAttr[4], 10); + } + + // values of width/height attributes overwrite those extracted from viewbox attribute + width = widthAttr ? parseFloat(widthAttr) : width; + height = heightAttr ? parseFloat(heightAttr) : height; + + var options = { + width: width, + height: height + }; + + fabric.gradientDefs = fabric.getGradientDefs(doc); + fabric.cssRules = getCSSRules(doc); + + // Precedence of rules: style > class > attribute + + fabric.parseElements(elements, function(instances) { + fabric.documentParsingTime = new Date() - startTime; + if (callback) { + callback(instances, options); + } + }, clone(options), reviver); + }; + })(); + + /** + * Used for caching SVG documents (loaded via `fabric.Canvas#loadSVGFromURL`) + * @property + * @namespace + */ + var svgCache = { + + /** + * @method has + * @param {String} name + * @param {Function} callback + */ + has: function (name, callback) { + callback(false); + }, + + /** + * @method get + * @param {String} url + * @param {Function} callback + */ + get: function (url, callback) { + /* NOOP */ + }, + + /** + * @method set + * @param {String} url + * @param {Object} object + */ + set: function (url, object) { + /* NOOP */ + } + }; + + /** + * Takes url corresponding to an SVG document, and parses it into a set of fabric objects + * @method loadSVGFromURL + * @param {String} url + * @param {Function} callback + * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. + */ + function loadSVGFromURL(url, callback, reviver) { + + url = url.replace(/^\n\s*/, '').trim(); + + svgCache.has(url, function (hasUrl) { + if (hasUrl) { + svgCache.get(url, function (value) { + var enlivedRecord = _enlivenCachedObject(value); + callback(enlivedRecord.objects, enlivedRecord.options); + }); + } + else { + new fabric.util.request(url, { + method: 'get', + onComplete: onComplete + }); + } + }); + + function onComplete(r) { + + var xml = r.responseXML; + if (!xml.documentElement && fabric.window.ActiveXObject && r.responseText) { + xml = new ActiveXObject('Microsoft.XMLDOM'); + xml.async = 'false'; + //IE chokes on DOCTYPE + xml.loadXML(r.responseText.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i,'')); + } + if (!xml.documentElement) return; + + fabric.parseSVGDocument(xml.documentElement, function (results, options) { + svgCache.set(url, { + objects: fabric.util.array.invoke(results, 'toObject'), + options: options + }); + callback(results, options); + }, reviver); + } + } + + /** + * @method _enlivenCachedObject + */ + function _enlivenCachedObject(cachedObject) { + + var objects = cachedObject.objects, + options = cachedObject.options; + + objects = objects.map(function (o) { + return fabric[capitalize(o.type)].fromObject(o); + }); + + return ({ objects: objects, options: options }); + } + + /** + * Takes string corresponding to an SVG document, and parses it into a set of fabric objects + * @method loadSVGFromString + * @param {String} string + * @param {Function} callback + * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. + */ + function loadSVGFromString(string, callback, reviver) { + string = string.trim(); + var doc; + if (typeof DOMParser !== 'undefined') { + var parser = new DOMParser(); + if (parser && parser.parseFromString) { + doc = parser.parseFromString(string, 'text/xml'); + } + } + else if (fabric.window.ActiveXObject) { + var doc = new ActiveXObject('Microsoft.XMLDOM'); + doc.async = 'false'; + //IE chokes on DOCTYPE + doc.loadXML(string.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i,'')); + } + + fabric.parseSVGDocument(doc.documentElement, function (results, options) { + callback(results, options); + }, reviver); + } + + function createSVGFontFacesMarkup(objects) { + var markup = ''; + + for (var i = 0, len = objects.length; i < len; i++) { + if (objects[i].type !== 'text' || !objects[i].path) continue; + + markup += [ + '@font-face {', + 'font-family: ', objects[i].fontFamily, '; ', + 'src: url(\'', objects[i].path, '\')', + '}' + ].join(''); + } + + if (markup) { + markup = [ + '<defs>', + '<style type="text/css">', + '<![CDATA[', + markup, + ']]>', + '</style>', + '</defs>' + ].join(''); + } + + return markup; + } + + extend(fabric, { + + parseAttributes: parseAttributes, + parseElements: parseElements, + parseStyleAttribute: parseStyleAttribute, + parsePointsAttribute: parsePointsAttribute, + getCSSRules: getCSSRules, + + loadSVGFromURL: loadSVGFromURL, + loadSVGFromString: loadSVGFromString, + + createSVGFontFacesMarkup: createSVGFontFacesMarkup + }); + +})(typeof exports != 'undefined' ? exports : this); + +(function() { + + function getColorStopFromStyle(el) { + var style = el.getAttribute('style'); + + if (style) { + var keyValuePairs = style.split(/\s*;\s*/); + + if (keyValuePairs[keyValuePairs.length-1] === '') { + keyValuePairs.pop(); + } + + for (var i = keyValuePairs.length; i--; ) { + + var split = keyValuePairs[i].split(/\s*:\s*/), + key = split[0].trim(), + value = split[1].trim(); + + if (key === 'stop-color') { + return value; + } + } + } + } + + /** + * @class Object + * @memberOf fabric + */ + fabric.Gradient = fabric.util.createClass(/** @scope fabric.Gradient.prototype */ { + + initialize: function(options) { + + options || (options = { }); + + this.x1 = options.x1 || 0; + this.y1 = options.y1 || 0; + this.x2 = options.x2 || 0; + this.y2 = options.y2 || 0; + + this.colorStops = options.colorStops; + }, + + toObject: function() { + return { + x1: this.x1, + x2: this.x2, + y1: this.y1, + y2: this.y2, + colorStops: this.colorStops + }; + }, + + toLiveGradient: function(ctx) { + var gradient = ctx.createLinearGradient( + this.x1, this.y1, this.x2 || ctx.canvas.width, this.y2); + + for (var position in this.colorStops) { + var colorValue = this.colorStops[position]; + gradient.addColorStop(parseFloat(position), colorValue); + } + + return gradient; + } + }); + + fabric.util.object.extend(fabric.Gradient, { + + /** + * @method fromElement + * @static + * @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement + */ + fromElement: function(el, instance) { + + /** + * @example: + * + * <linearGradient id="grad1"> + * <stop offset="0%" stop-color="white"/> + * <stop offset="100%" stop-color="black"/> + * </linearGradient> + * + * OR + * + * <linearGradient id="grad1"> + * <stop offset="0%" style="stop-color:rgb(255,255,255)"/> + * <stop offset="100%" style="stop-color:rgb(0,0,0)"/> + * </linearGradient> + * + */ + + var colorStopEls = el.getElementsByTagName('stop'), + el, + offset, + colorStops = { }, + colorStopFromStyle, + coords = { + x1: el.getAttribute('x1') || 0, + y1: el.getAttribute('y1') || 0, + x2: el.getAttribute('x2') || '100%', + y2: el.getAttribute('y2') || 0 + }; + + for (var i = colorStopEls.length; i--; ) { + el = colorStopEls[i]; + offset = el.getAttribute('offset'); + + // convert percents to absolute values + offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1); + colorStops[offset] = getColorStopFromStyle(el) || el.getAttribute('stop-color'); + } + + _convertPercentUnitsToValues(instance, coords); + + return new fabric.Gradient({ + x1: coords.x1, + y1: coords.y1, + x2: coords.x2, + y2: coords.y2, + colorStops: colorStops + }); + }, + + /** + * @method forObject + * @static + */ + forObject: function(obj, options) { + options || (options = { }); + _convertPercentUnitsToValues(obj, options); + return new fabric.Gradient(options); + } + }); + + function _convertPercentUnitsToValues(object, options) { + for (var prop in options) { + if (typeof options[prop] === 'string' && /^\d+%$/.test(options[prop])) { + var percents = parseFloat(options[prop], 10); + if (prop === 'x1' || prop === 'x2') { + options[prop] = object.width * percents / 100; + } + else if (prop === 'y1' || prop === 'y2') { + options[prop] = object.height * percents / 100; + } + } + // normalize rendering point (should be from top/left corner rather than center of the shape) + if (prop === 'x1' || prop === 'x2') { + options[prop] -= object.width / 2; + } + else if (prop === 'y1' || prop === 'y2') { + options[prop] -= object.height / 2; + } + } + } + + /** + * Parses an SVG document, returning all of the gradient declarations found in it + * @static + * @function + * @memberOf fabric + * @method getGradientDefs + * @param {SVGDocument} doc SVG document to parse + * @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element + */ + function getGradientDefs(doc) { + var linearGradientEls = doc.getElementsByTagName('linearGradient'), + radialGradientEls = doc.getElementsByTagName('radialGradient'), + el, + gradientDefs = { }; + + for (var i = linearGradientEls.length; i--; ) { + el = linearGradientEls[i]; + gradientDefs[el.getAttribute('id')] = el; + } + + for (var i = radialGradientEls.length; i--; ) { + el = radialGradientEls[i]; + gradientDefs[el.getAttribute('id')] = el; + } + + return gradientDefs; + } + + fabric.getGradientDefs = getGradientDefs; + +})(); +(function(global) { + + "use strict"; + + /* Adaptation of work of Kevin Lindsey ([email protected]) */ + + var fabric = global.fabric || (global.fabric = { }); + + if (fabric.Point) { + fabric.warn('fabric.Point is already defined'); + return; + } + + fabric.Point = Point; + + /** + * @name Point + * @memberOf fabric + * @constructor + * @param {Number} x + * @param {Number} y + * @return {fabric.Point} thisArg + */ + function Point(x, y) { + if (arguments.length > 0) { + this.init(x, y); + } + } + + Point.prototype = /** @scope fabric.Point.prototype */ { + + constructor: Point, + + /** + * @method init + * @param {Number} x + * @param {Number} y + */ + init: function (x, y) { + this.x = x; + this.y = y; + }, + + /** + * @method add + * @param {fabric.Point} that + * @return {fabric.Point} new Point instance with added values + */ + add: function (that) { + return new Point(this.x + that.x, this.y + that.y); + }, + + /** + * @method addEquals + * @param {fabric.Point} that + * @return {fabric.Point} thisArg + */ + addEquals: function (that) { + this.x += that.x; + this.y += that.y; + return this; + }, + + /** + * @method scalarAdd + * @param {Number} scalar + * @return {fabric.Point} new Point with added value + */ + scalarAdd: function (scalar) { + return new Point(this.x + scalar, this.y + scalar); + }, + + /** + * @method scalarAddEquals + * @param {Number} scalar + * @param {fabric.Point} thisArg + */ + scalarAddEquals: function (scalar) { + this.x += scalar; + this.y += scalar; + return this; + }, + + /** + * @method subtract + * @param {fabric.Point} that + * @return {fabric.Point} new Point object with subtracted values + */ + subtract: function (that) { + return new Point(this.x - that.x, this.y - that.y); + }, + + /** + * @method subtractEquals + * @param {fabric.Point} that + * @return {fabric.Point} thisArg + */ + subtractEquals: function (that) { + this.x -= that.x; + this.y -= that.y; + return this; + }, + + scalarSubtract: function (scalar) { + return new Point(this.x - scalar, this.y - scalar); + }, + + scalarSubtractEquals: function (scalar) { + this.x -= scalar; + this.y -= scalar; + return this; + }, + + multiply: function (scalar) { + return new Point(this.x * scalar, this.y * scalar); + }, + + multiplyEquals: function (scalar) { + this.x *= scalar; + this.y *= scalar; + return this; + }, + + divide: function (scalar) { + return new Point(this.x / scalar, this.y / scalar); + }, + + divideEquals: function (scalar) { + this.x /= scalar; + this.y /= scalar; + return this; + }, + + eq: function (that) { + return (this.x == that.x && this.y == that.y); + }, + + lt: function (that) { + return (this.x < that.x && this.y < that.y); + }, + + lte: function (that) { + return (this.x <= that.x && this.y <= that.y); + }, + + gt: function (that) { + return (this.x > that.x && this.y > that.y); + }, + + gte: function (that) { + return (this.x >= that.x && this.y >= that.y); + }, + + lerp: function (that, t) { + return new Point(this.x + (that.x - this.x) * t, this.y + (that.y - this.y) * t); + }, + + distanceFrom: function (that) { + var dx = this.x - that.x, + dy = this.y - that.y; + return Math.sqrt(dx * dx + dy * dy); + }, + + min: function (that) { + return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y)); + }, + + max: function (that) { + return new Point(Math.max(this.x, that.x), Math.max(this.y, that.y)); + }, + + toString: function () { + return this.x + "," + this.y; + }, + + setXY: function (x, y) { + this.x = x; + this.y = y; + }, + + setFromPoint: function (that) { + this.x = that.x; + this.y = that.y; + }, + + swap: function (that) { + var x = this.x, + y = this.y; + this.x = that.x; + this.y = that.y; + that.x = x; + that.y = y; + } + }; + +})(typeof exports != 'undefined' ? exports : this); +(function(global) { + + "use strict"; + + /* Adaptation of work of Kevin Lindsey ([email protected]) */ + + var fabric = global.fabric || (global.fabric = { }); + + if (fabric.Intersection) { + fabric.warn('fabric.Intersection is already defined'); + return; + } + + /** + * @class Intersection + * @memberOf fabric + */ + function Intersection(status) { + if (arguments.length > 0) { + this.init(status); + } + } + + fabric.Intersection = Intersection; + + fabric.Intersection.prototype = /** @scope fabric.Intersection.prototype */ { + + /** + * @method init + * @param {String} status + */ + init: function (status) { + this.status = status; + this.points = []; + }, + + /** + * @method appendPoint + * @param {String} status + */ + appendPoint: function (point) { + this.points.push(point); + }, + + /** + * @method appendPoints + * @param {String} status + */ + appendPoints: function (points) { + this.points = this.points.concat(points); + } + }; + + /** + * @static + * @method intersectLineLine + */ + fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) { + var result, + ua_t = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), + ub_t = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x), + u_b = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y); + if (u_b != 0) { + var ua = ua_t / u_b, + ub = ub_t / u_b; + if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) { + result = new Intersection("Intersection"); + result.points.push(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y))); + } + else { + result = new Intersection("No Intersection"); + } + } + else { + if (ua_t == 0 || ub_t == 0) { + result = new Intersection("Coincident"); + } + else { + result = new Intersection("Parallel"); + } + } + return result; + }; + + /** + * @method intersectLinePolygon + */ + fabric.Intersection.intersectLinePolygon = function(a1,a2,points){ + var result = new Intersection("No Intersection"), + length = points.length; + + for (var i = 0; i < length; i++) { + var b1 = points[i], + b2 = points[(i+1) % length], + inter = Intersection.intersectLineLine(a1, a2, b1, b2); + + result.appendPoints(inter.points); + } + if (result.points.length > 0) { + result.status = "Intersection"; + } + return result; + }; + + /** + * @method intersectPolygonPolygon + */ + fabric.Intersection.intersectPolygonPolygon = function (points1, points2) { + var result = new Intersection("No Intersection"), + length = points1.length; + + for (var i = 0; i < length; i++) { + var a1 = points1[i], + a2 = points1[(i+1) % length], + inter = Intersection.intersectLinePolygon(a1, a2, points2); + + result.appendPoints(inter.points); + } + if (result.points.length > 0) { + result.status = "Intersection"; + } + return result; + }; + + /** + * @method intersectPolygonRectangle + */ + fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) { + var min = r1.min(r2), + max = r1.max(r2), + topRight = new fabric.Point(max.x, min.y), + bottomLeft = new fabric.Point(min.x, max.y), + inter1 = Intersection.intersectLinePolygon(min, topRight, points), + inter2 = Intersection.intersectLinePolygon(topRight, max, points), + inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points), + inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points), + result = new Intersection("No Intersection"); + + result.appendPoints(inter1.points); + result.appendPoints(inter2.points); + result.appendPoints(inter3.points); + result.appendPoints(inter4.points); + if (result.points.length > 0) { + result.status="Intersection"; + } + return result; + }; + +})(typeof exports != 'undefined' ? exports : this); +(function(global) { + + "use strict"; + + var fabric = global.fabric || (global.fabric = { }); + + if (fabric.Color) { + fabric.warn('fabric.Color is already defined.'); + return; + } + + /** + * The purpose of {@link fabric.Color} is to abstract and encapsulate common color operations; + * {@link fabric.Color} is a constructor and creates instances of {@link fabric.Color} objects. + * + * @class Color + * @memberOf fabric + * @param {String} color (optional) in hex or rgb(a) format + */ + function Color(color) { + if (!color) { + this.setSource([0, 0, 0, 1]); + } + else { + this._tryParsingColor(color); + } + } + + fabric.Color = Color; + + fabric.Color.prototype = /** @scope fabric.Color.prototype */ { + + /** + * @private + * @method _tryParsingColor + */ + _tryParsingColor: function(color) { + var source = Color.sourceFromHex(color); + if (!source) { + source = Color.sourceFromRgb(color); + } + if (source) { + this.setSource(source); + } + }, + + /** + * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1]) + * @method getSource + * @return {Array} + */ + getSource: function() { + return this._source; + }, + + /** + * Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1]) + * @method setSource + * @param {Array} source + */ + setSource: function(source) { + this._source = source; + }, + + /** + * Returns color represenation in RGB format + * @method toRgb + * @return {String} ex: rgb(0-255,0-255,0-255) + */ + toRgb: function() { + var source = this.getSource(); + return 'rgb(' + source[0] + ',' + source[1] + ',' + source[2] + ')'; + }, + + /** + * Returns color represenation in RGBA format + * @method toRgba + * @return {String} ex: rgba(0-255,0-255,0-255,0-1) + */ + toRgba: function() { + var source = this.getSource(); + return 'rgba(' + source[0] + ',' + source[1] + ',' + source[2] + ',' + source[3] + ')'; + }, + + /** + * Returns color represenation in HEX format + * @method toHex + * @return {String} ex: FF5555 + */ + toHex: function() { + var source = this.getSource(); + + var r = source[0].toString(16); + r = (r.length == 1) ? ('0' + r) : r; + + var g = source[1].toString(16); + g = (g.length == 1) ? ('0' + g) : g; + + var b = source[2].toString(16); + b = (b.length == 1) ? ('0' + b) : b; + + return r.toUpperCase() + g.toUpperCase() + b.toUpperCase(); + }, + + /** + * Gets value of alpha channel for this color + * @method getAlpha + * @return {Number} 0-1 + */ + getAlpha: function() { + return this.getSource()[3]; + }, + + /** + * Sets value of alpha channel for this color + * @method setAlpha + * @param {Number} 0-1 + * @return {fabric.Color} thisArg + */ + setAlpha: function(alpha) { + var source = this.getSource(); + source[3] = alpha; + this.setSource(source); + return this; + }, + + /** + * Transforms color to its grayscale representation + * @method toGrayscale + * @return {fabric.Color} thisArg + */ + toGrayscale: function() { + var source = this.getSource(), + average = parseInt((source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), 10), + currentAlpha = source[3]; + this.setSource([average, average, average, currentAlpha]); + return this; + }, + + /** + * Transforms color to its black and white representation + * @method toGrayscale + * @return {fabric.Color} thisArg + */ + toBlackWhite: function(threshold) { + var source = this.getSource(), + average = (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), + currentAlpha = source[3], + threshold = threshold || 127; + + average = (Number(average) < Number(threshold)) ? 0 : 255; + this.setSource([average, average, average, currentAlpha]); + return this; + }, + + /** + * Overlays color with another color + * @method overlayWith + * @param {String|fabric.Color} otherColor + * @return {fabric.Color} thisArg + */ + overlayWith: function(otherColor) { + if (!(otherColor instanceof Color)) { + otherColor = new Color(otherColor); + } + + var result = [], + alpha = this.getAlpha(), + otherAlpha = 0.5, + source = this.getSource(), + otherSource = otherColor.getSource(); + + for (var i = 0; i < 3; i++) { + result.push(Math.round((source[i] * (1 - otherAlpha)) + (otherSource[i] * otherAlpha))); + } + + result[3] = alpha; + this.setSource(result); + return this; + } + }; + + /** + * Regex matching color in RGB or RGBA formats (ex: rgb(0, 0, 0), rgb(255, 100, 10, 0.5), rgb(1,1,1)) + * @static + * @field + */ + fabric.Color.reRGBa = /^rgba?\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})(?:\s*,\s*(\d+(?:\.\d+)?))?\)$/; + + /** + * Regex matching color in HEX format (ex: #FF5555, 010155, aff) + * @static + * @field + */ + fabric.Color.reHex = /^#?([0-9a-f]{6}|[0-9a-f]{3})$/i; + + /** + * Returns new color object, when given a color in RGB format + * @method fromRgb + * @param {String} color ex: rgb(0-255,0-255,0-255) + * @return {fabric.Color} + */ + fabric.Color.fromRgb = function(color) { + return Color.fromSource(Color.sourceFromRgb(color)); + }; + + /** + * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format + * @method sourceFromRgb + * @param {String} color ex: rgb(0-255,0-255,0-255) + * @return {Array} source + */ + fabric.Color.sourceFromRgb = function(color) { + var match = color.match(Color.reRGBa); + if (match) { + return [ + parseInt(match[1], 10), + parseInt(match[2], 10), + parseInt(match[3], 10), + match[4] ? parseFloat(match[4]) : 1 + ]; + } + }; + + /** + * Returns new color object, when given a color in RGBA format + * @static + * @function + * @method fromRgba + * @param {String} color + * @return {fabric.Color} + */ + fabric.Color.fromRgba = Color.fromRgb; + + /** + * Returns new color object, when given a color in HEX format + * @static + * @method fromHex + * @return {fabric.Color} + */ + fabric.Color.fromHex = function(color) { + return Color.fromSource(Color.sourceFromHex(color)); + }; + + /** + * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HEX format + * @static + * @method sourceFromHex + * @param {String} color ex: FF5555 + * @return {Array} source + */ + fabric.Color.sourceFromHex = function(color) { + if (color.match(Color.reHex)) { + var value = color.slice(color.indexOf('#') + 1), + isShortNotation = (value.length === 3), + r = isShortNotation ? (value.charAt(0) + value.charAt(0)) : value.substring(0, 2), + g = isShortNotation ? (value.charAt(1) + value.charAt(1)) : value.substring(2, 4), + b = isShortNotation ? (value.charAt(2) + value.charAt(2)) : value.substring(4, 6); + + return [ + parseInt(r, 16), + parseInt(g, 16), + parseInt(b, 16), + 1 + ]; + } + }; + + /** + * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5]) + * @static + * @method fromSource + * @return {fabric.Color} + */ + fabric.Color.fromSource = function(source) { + var oColor = new Color(); + oColor.setSource(source); + return oColor; + }; + +})(typeof exports != 'undefined' ? exports : this); +(function (global) { + + "use strict"; + + if (fabric.StaticCanvas) { + fabric.warn('fabric.StaticCanvas is already defined.'); + return; + } + + // aliases for faster resolution + var extend = fabric.util.object.extend, + getElementOffset = fabric.util.getElementOffset, + removeFromArray = fabric.util.removeFromArray, + removeListener = fabric.util.removeListener, + + CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element'); + + /** + * @class fabric.StaticCanvas + * @constructor + * @param {HTMLElement | String} el <canvas> element to initialize instance on + * @param {Object} [options] Options object + */ + fabric.StaticCanvas = function (el, options) { + options || (options = { }); + + this._initStatic(el, options); + fabric.StaticCanvas.activeInstance = this; + }; + + extend(fabric.StaticCanvas.prototype, fabric.Observable); + + extend(fabric.StaticCanvas.prototype, /** @scope fabric.StaticCanvas.prototype */ { + + /** + * Background color of canvas instance + * @property + * @type String + */ + backgroundColor: 'rgba(0, 0, 0, 0)', + + /** + * Background image of canvas instance + * Should be set via `setBackgroundImage` + * @property + * @type String + */ + backgroundImage: '', + + /** + * Opacity of the background image of the canvas instance + * @property + * @type Float + */ + backgroundImageOpacity: 1.0, + + /** + * Indicatus whether the background image should be stretched to fit the + * dimensions of the canvas instance. + * @property + * @type Boolean + */ + backgroundImageStretch: true, + + /** + * Indicates whether toObject/toDatalessObject should include default values + * @property + * @type Boolean + */ + includeDefaultValues: true, + + /** + * Indicates whether objects' state should be saved + * @property + * @type Boolean + */ + stateful: true, + + /** + * Indicates whether fabric.Canvas#add should also re-render canvas. + * Disabling this option could give a great performance boost when adding a lot of objects to canvas at once + * (followed by a manual rendering after addition) + */ + renderOnAddition: true, + + /** + * Function that determines clipping of entire canvas area + * Being passed context as first argument. See clipping canvas area in https://github.com/kangax/fabric.js/wiki/FAQ + * @property + * @type Function + */ + clipTo: null, + + /** + * Indicates whether object controls (borders/corners) are rendered above overlay image + * @property + * @type Boolean + */ + controlsAboveOverlay: false, + + /** + * Callback; invoked right before object is about to be scaled/rotated + * @method onBeforeScaleRotate + * @param {fabric.Object} target Object that's about to be scaled/rotated + */ + onBeforeScaleRotate: function (target) { + /* NOOP */ + }, + + /** + * Callback; invoked on every redraw of canvas and is being passed a number indicating current fps + * @method onFpsUpdate + * @param {Number} fps + */ + onFpsUpdate: null, + + _initStatic: function(el, options) { + this._objects = []; + + this._createLowerCanvas(el); + this._initOptions(options); + + if (options.overlayImage) { + this.setOverlayImage(options.overlayImage, this.renderAll.bind(this)); + } + if (options.backgroundImage) { + this.setBackgroundImage(options.backgroundImage, this.renderAll.bind(this)); + } + this.calcOffset(); + }, + + /** + * Calculates canvas element offset relative to the document + * This method is also attached as "resize" event handler of window + * @method calcOffset + * @return {fabric.Canvas} instance + * @chainable + */ + calcOffset: function () { + this._offset = getElementOffset(this.lowerCanvasEl); + return this; + }, + + /** + * Sets overlay image for this canvas + * @method setOverlayImage + * @param {String} url url of an image to set overlay to + * @param {Function} callback callback to invoke when image is loaded and set as an overlay + * @return {fabric.Canvas} thisArg + * @chainable + */ + setOverlayImage: function (url, callback) { // TODO (kangax): test callback + fabric.util.loadImage(url, function(img) { + this.overlayImage = img; + callback && callback(); + }, this); + return this; + }, + + /** + * Sets background image for this canvas + * @method setBackgroundImage + * @param {String} url url of an image to set background to + * @param {Function} callback callback to invoke when image is loaded and set as background + * @param {Object} options optional options to set for the background image + * @return {fabric.Canvas} thisArg + * @chainable + */ + setBackgroundImage: function (url, callback, options) { + return fabric.util.loadImage(url, function(img) { + this.backgroundImage = img; + if (options && ('backgroundImageOpacity' in options)) { + this.backgroundImageOpacity = options.backgroundImageOpacity; + } + if (options && ('backgroundImageStretch' in options)) { + this.backgroundImageStretch = options.backgroundImageStretch; + } + callback && callback(); + }, this); + }, + + /** + * @private + * @method _createCanvasElement + * @param {Element} element + */ + _createCanvasElement: function() { + var element = fabric.document.createElement('canvas'); + if (!element.style) { + element.style = { }; + } + if (!element) { + throw CANVAS_INIT_ERROR; + } + this._initCanvasElement(element); + return element; + }, + + _initCanvasElement: function(element) { + if (typeof element.getContext === 'undefined' && + typeof G_vmlCanvasManager !== 'undefined' && + G_vmlCanvasManager.initElement) { + + G_vmlCanvasManager.initElement(element); + } + if (typeof element.getContext === 'undefined') { + throw CANVAS_INIT_ERROR; + } + }, + + /** + * @method _initOptions + * @param {Object} options + */ + _initOptions: function (options) { + for (var prop in options) { + this[prop] = options[prop]; + } + + this.width = parseInt(this.lowerCanvasEl.width, 10) || 0; + this.height = parseInt(this.lowerCanvasEl.height, 10) || 0; + + if (!this.lowerCanvasEl.style) return; + + this.lowerCanvasEl.style.width = this.width + 'px'; + this.lowerCanvasEl.style.height = this.height + 'px'; + }, + + /** + * Creates a secondary canvas + * @method _createLowerCanvas + */ + _createLowerCanvas: function (canvasEl) { + this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement(); + this._initCanvasElement(this.lowerCanvasEl); + + fabric.util.addClass(this.lowerCanvasEl, 'lower-canvas'); + + if (this.interactive) { + this._applyCanvasStyle(this.lowerCanvasEl); + } + + this.contextContainer = this.lowerCanvasEl.getContext('2d'); + }, + + /** + * Returns canvas width + * @method getWidth + * @return {Number} + */ + getWidth: function () { + return this.width; + }, + + /** + * Returns canvas height + * @method getHeight + * @return {Number} + */ + getHeight: function () { + return this.height; + }, + + /** + * Sets width of this canvas instance + * @method setWidth + * @param {Number} width value to set width to + * @return {fabric.Canvas} instance + * @chainable true + */ + setWidth: function (value) { + return this._setDimension('width', value); + }, + + /** + * Sets height of this canvas instance + * @method setHeight + * @param {Number} height value to set height to + * @return {fabric.Canvas} instance + * @chainable true + */ + setHeight: function (value) { + return this._setDimension('height', value); + }, + + /** + * Sets dimensions (width, height) of this canvas instance + * @method setDimensions + * @param {Object} dimensions + * @return {fabric.Canvas} thisArg + * @chainable + */ + setDimensions: function(dimensions) { + for (var prop in dimensions) { + this._setDimension(prop, dimensions[prop]); + } + return this; + }, + + /** + * Helper for setting width/height + * @private + * @method _setDimensions + * @param {String} prop property (width|height) + * @param {Number} value value to set property to + * @return {fabric.Canvas} instance + * @chainable true + */ + _setDimension: function (prop, value) { + this.lowerCanvasEl[prop] = value; + this.lowerCanvasEl.style[prop] = value + 'px'; + + if (this.upperCanvasEl) { + this.upperCanvasEl[prop] = value; + this.upperCanvasEl.style[prop] = value + 'px'; + } + + if (this.wrapperEl) { + this.wrapperEl.style[prop] = value + 'px'; + } + + this[prop] = value; + + this.calcOffset(); + this.renderAll(); + + return this; + }, + + /** + * Returns <canvas> element corresponding to this instance + * @method getElement + * @return {HTMLCanvasElement} + */ + getElement: function () { + return this.lowerCanvasEl; + }, + + // placeholder + getActiveObject: function() { + return null; + }, + + // placeholder + getActiveGroup: function() { + return null; + }, + + /** + * Given a context, renders an object on that context + * @param ctx {Object} context to render object on + * @param object {Object} object to render + * @private + */ + _draw: function (ctx, object) { + if (!object) return; + + if (this.controlsAboveOverlay) { + var hasBorders = object.hasBorders, hasCorners = object.hasCorners; + object.hasBorders = object.hasCorners = false; + object.render(ctx); + object.hasBorders = hasBorders; + object.hasCorners = hasCorners; + } + else { + object.render(ctx); + } + }, + + /** + * Adds objects to canvas, then renders canvas; + * Objects should be instances of (or inherit from) fabric.Object + * @method add + * @return {fabric.Canvas} thisArg + * @chainable + */ + add: function () { + this._objects.push.apply(this._objects, arguments); + for (var i = arguments.length; i--; ) { + this._initObject(arguments[i]); + } + this.renderOnAddition && this.renderAll(); + return this; + }, + + /** + * @private + * @method _initObject + */ + _initObject: function(obj) { + this.stateful && obj.setupState(); + obj.setCoords(); + obj.canvas = this; + this.fire('object:added', { target: obj }); + obj.fire('added'); + }, + + /** + * Inserts an object to canvas at specified index and renders canvas. + * An object should be an instance of (or inherit from) fabric.Object + * @method insertAt + * @param object {Object} Object to insert + * @param index {Number} index to insert object at + * @param nonSplicing {Boolean} when `true`, no splicing (shifting) of objects occurs + * @return {fabric.Canvas} instance + */ + insertAt: function (object, index, nonSplicing) { + if (nonSplicing) { + this._objects[index] = object; + } + else { + this._objects.splice(index, 0, object); + } + this._initObject(object); + this.renderOnAddition && this.renderAll(); + return this; + }, + + /** + * Returns an array of objects this instance has + * @method getObjects + * @return {Array} + */ + getObjects: function () { + return this._objects; + }, + + /** + * Clears specified context of canvas element + * @method clearContext + * @param context {Object} ctx context to clear + * @return {fabric.Canvas} thisArg + * @chainable + */ + clearContext: function(ctx) { + ctx.clearRect(0, 0, this.width, this.height); + return this; + }, + + /** + * Returns context of canvas where objects are drawn + * @method getContext + * @return {CanvasRenderingContext2D} + */ + getContext: function () { + return this.contextContainer; + }, + + /** + * Clears all contexts (background, main, top) of an instance + * @method clear + * @return {fabric.Canvas} thisArg + * @chainable + */ + clear: function () { + this._objects.length = 0; + this.clearContext(this.contextContainer); + if (this.contextTop) { + this.clearContext(this.contextTop); + } + this.renderAll(); + return this; + }, + + /** + * Renders both the top canvas and the secondary container canvas. + * @method renderAll + * @param allOnTop {Boolean} optional Whether we want to force all images to be rendered on the top canvas + * @return {fabric.Canvas} instance + * @chainable + */ + renderAll: function (allOnTop) { + + var canvasToDrawOn = this[(allOnTop === true && this.interactive) ? 'contextTop' : 'contextContainer']; + + if (this.contextTop) { + this.clearContext(this.contextTop); + } + + if (allOnTop === false || (typeof allOnTop === 'undefined')) { + this.clearContext(canvasToDrawOn); + } + + var length = this._objects.length, + activeGroup = this.getActiveGroup(), + startTime = new Date(); + + if (this.clipTo) { + canvasToDrawOn.save(); + canvasToDrawOn.beginPath(); + this.clipTo(canvasToDrawOn); + canvasToDrawOn.clip(); + } + + canvasToDrawOn.fillStyle = this.backgroundColor; + canvasToDrawOn.fillRect(0, 0, this.width, this.height); + + if (typeof this.backgroundImage == 'object') { + canvasToDrawOn.save(); + canvasToDrawOn.globalAlpha = this.backgroundImageOpacity; + + if (this.backgroundImageStretch) { + canvasToDrawOn.drawImage(this.backgroundImage, 0, 0, this.width, this.height); + } + else { + canvasToDrawOn.drawImage(this.backgroundImage, 0, 0); + } + canvasToDrawOn.restore(); + } + + if (length) { + for (var i = 0; i < length; ++i) { + if (!activeGroup || + (activeGroup && this._objects[i] && !activeGroup.contains(this._objects[i]))) { + this._draw(canvasToDrawOn, this._objects[i]); + } + } + } + + if (this.clipTo) { + canvasToDrawOn.restore(); + } + + // delegate rendering to group selection (if one exists) + if (activeGroup) { + this._draw(this.contextTop, activeGroup); + } + + if (this.overlayImage) { + this.contextContainer.drawImage(this.overlayImage, 0, 0); + } + + if (this.controlsAboveOverlay) { + this.drawControls(this.contextContainer); + } + + if (this.onFpsUpdate) { + var elapsedTime = new Date() - startTime; + this.onFpsUpdate(~~(1000 / elapsedTime)); + } + + this.fire('after:render'); + + return this; + }, + + /** + * Method to render only the top canvas. + * Also used to render the group selection box. + * @method renderTop + * @return {fabric.Canvas} thisArg + * @chainable + */ + renderTop: function () { + this.clearContext(this.contextTop || this.contextContainer); + + if (this.overlayImage) { + this.contextContainer.drawImage(this.overlayImage, 0, 0); + } + + // we render the top context - last object + if (this.selection && this._groupSelector) { + this._drawSelection(); + } + + // delegate rendering to group selection if one exists + // used for drawing selection borders/corners + var activeGroup = this.getActiveGroup(); + if (activeGroup) { + activeGroup.render(this.contextTop); + } + + this.fire('after:render'); + + return this; + }, + + /** + * Draws objects' controls (borders/corners) + * @method drawControls + * @param {Object} ctx context to render controls on + */ + drawControls: function(ctx) { + var activeGroup = this.getActiveGroup(); + if (activeGroup) { + ctx.save(); + fabric.Group.prototype.transform.call(activeGroup, ctx); + activeGroup.drawBorders(ctx).drawCorners(ctx); + ctx.restore(); + } + else { + for (var i = 0, len = this._objects.length; i < len; ++i) { + if (!this._objects[i].active) continue; + + ctx.save(); + fabric.Object.prototype.transform.call(this._objects[i], ctx); + this._objects[i].drawBorders(ctx).drawCorners(ctx); + ctx.restore(); + } + } + }, + + /** + * Exports canvas element to a dataurl image. + * @method toDataURL + * @param {String} format the format of the output image. Either "jpeg" or "png". + * @param {Number} quality quality level (0..1) + * @return {String} + */ + toDataURL: function (format, quality) { + var canvasEl = this.upperCanvasEl || this.lowerCanvasEl; + + this.renderAll(true); + var data = (fabric.StaticCanvas.supports('toDataURLWithQuality')) + ? canvasEl.toDataURL('image/' + format, quality) + : canvasEl.toDataURL('image/' + format); + this.renderAll(); + return data; + }, + + /** + * Exports canvas element to a dataurl image (allowing to change image size via multiplier). + * @method toDataURLWithMultiplier + * @param {String} format (png|jpeg) + * @param {Number} multiplier + * @param {Number} quality (0..1) + * @return {String} + */ + toDataURLWithMultiplier: function (format, multiplier, quality) { + + var origWidth = this.getWidth(), + origHeight = this.getHeight(), + scaledWidth = origWidth * multiplier, + scaledHeight = origHeight * multiplier, + activeObject = this.getActiveObject(), + activeGroup = this.getActiveGroup(); + + this.setWidth(scaledWidth).setHeight(scaledHeight); + this.contextTop.scale(multiplier, multiplier); + + if (activeGroup) { + // not removing group due to complications with restoring it with correct state afterwords + this._tempRemoveBordersCornersFromGroup(activeGroup); + } + else if (activeObject) { + this.deactivateAll(); + } + + // restoring width, height for `renderAll` to draw + // background properly (while context is scaled) + this.width = origWidth; + this.height = origHeight; + + this.renderAll(true); + + var dataURL = this.toDataURL(format, quality); + + this.contextTop.scale(1 / multiplier, 1 / multiplier); + this.setWidth(origWidth).setHeight(origHeight); + + if (activeGroup) { + this._restoreBordersCornersOnGroup(activeGroup); + } + else if (activeObject) { + this.setActiveObject(activeObject); + } + + this.renderAll(); + + return dataURL; + }, + + _tempRemoveBordersCornersFromGroup: function(group) { + group.origHideCorners = group.hideCorners; + group.origBorderColor = group.borderColor; + + group.hideCorners = true; + group.borderColor = 'rgba(0,0,0,0)'; + + group.forEachObject(function(o) { + o.origBorderColor = o.borderColor; + o.borderColor = 'rgba(0,0,0,0)'; + }); + }, + _restoreBordersCornersOnGroup: function(group) { + group.hideCorners = group.origHideCorners; + group.borderColor = group.origBorderColor; + + group.forEachObject(function(o) { + o.borderColor = o.origBorderColor; + delete o.origBorderColor; + }); + }, + + /** + * Returns coordinates of a center of canvas. + * Returned value is an object with top and left properties + * @method getCenter + * @return {Object} object with "top" and "left" number values + */ + getCenter: function () { + return { + top: this.getHeight() / 2, + left: this.getWidth() / 2 + }; + }, + + /** + * Centers object horizontally. + * @method centerObjectH + * @param {fabric.Object} object Object to center + * @return {fabric.Canvas} thisArg + */ + centerObjectH: function (object) { + object.set('left', this.getCenter().left); + this.renderAll(); + return this; + }, + + /** + * Centers object vertically. + * @method centerObjectH + * @param {fabric.Object} object Object to center + * @return {fabric.Canvas} thisArg + * @chainable + */ + centerObjectV: function (object) { + object.set('top', this.getCenter().top); + this.renderAll(); + return this; + }, + + /** + * Centers object vertically and horizontally. + * @method centerObject + * @param {fabric.Object} object Object to center + * @return {fabric.Canvas} thisArg + * @chainable + */ + centerObject: function (object) { + return this.centerObjectH(object).centerObjectV(object); + }, + + /** + * Returs dataless JSON representation of canvas + * @method toDatalessJSON + * @return {String} json string + */ + toDatalessJSON: function () { + return this.toDatalessObject(); + }, + + /** + * Returns object representation of canvas + * @method toObject + * @return {Object} + */ + toObject: function () { + return this._toObjectMethod('toObject'); + }, + + /** + * Returns dataless object representation of canvas + * @method toDatalessObject + * @return {Object} + */ + toDatalessObject: function () { + return this._toObjectMethod('toDatalessObject'); + }, + + /** + * @private + * @method _toObjectMethod + */ + _toObjectMethod: function (methodName) { + var data = { + objects: this._objects.map(function (instance) { + // TODO (kangax): figure out how to clean this up + if (!this.includeDefaultValues) { + var originalValue = instance.includeDefaultValues; + instance.includeDefaultValues = false; + } + var object = instance[methodName](); + if (!this.includeDefaultValues) { + instance.includeDefaultValues = originalValue; + } + return object; + }, this), + background: this.backgroundColor + }; + if (this.backgroundImage) { + data.backgroundImage = this.backgroundImage.src; + data.backgroundImageOpacity = this.backgroundImageOpacity; + data.backgroundImageStretch = this.backgroundImageStretch; + } + return data; + }, + + /** + * Returns SVG representation of canvas + * @function + * @method toSVG + * @return {String} + */ + toSVG: function() { + var markup = [ + '<?xml version="1.0" standalone="no" ?>', + '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" ', + '"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">', + '<svg ', + 'xmlns="http://www.w3.org/2000/svg" ', + 'xmlns:xlink="http://www.w3.org/1999/xlink" ', + 'version="1.1" ', + 'width="', this.width, '" ', + 'height="', this.height, '" ', + 'xml:space="preserve">', + '<desc>Created with Fabric.js ', fabric.version, '</desc>', + fabric.createSVGFontFacesMarkup(this.getObjects()) + ]; + + if (this.backgroundImage) { + markup.push( + '<image x="0" y="0" ', + 'width="', this.width, + '" height="', this.height, + '" preserveAspectRatio="', (this.backgroundImageStretch ? 'none' : 'defer'), + '" xlink:href="', this.backgroundImage.src, + '" style="opacity:', this.backgroundImageOpacity, + '"></image>' + ); + } + + for (var i = 0, objects = this.getObjects(), len = objects.length; i < len; i++) { + markup.push(objects[i].toSVG()); + } + markup.push('</svg>'); + + return markup.join(''); + }, + + /** + * Returns true if canvas contains no objects + * @method isEmpty + * @return {Boolean} true if canvas is empty + */ + isEmpty: function () { + return this._objects.length === 0; + }, + + /** + * Removes an object from canvas and returns it + * @method remove + * @param object {Object} Object to remove + * @return {Object} removed object + */ + remove: function (object) { + removeFromArray(this._objects, object); + if (this.getActiveObject() === object) { + + // removing active object should fire "selection:cleared" events + this.fire('before:selection:cleared', { target: object }); + this.discardActiveObject(); + this.fire('selection:cleared'); + } + this.renderAll(); + return object; + }, + + /** + * Moves an object to the bottom of the stack of drawn objects + * @method sendToBack + * @param object {fabric.Object} Object to send to back + * @return {fabric.Canvas} thisArg + * @chainable + */ + sendToBack: function (object) { + removeFromArray(this._objects, object); + this._objects.unshift(object); + return this.renderAll(); + }, + + /** + * Moves an object to the top of the stack of drawn objects + * @method bringToFront + * @param object {fabric.Object} Object to send + * @return {fabric.Canvas} thisArg + * @chainable + */ + bringToFront: function (object) { + removeFromArray(this._objects, object); + this._objects.push(object); + return this.renderAll(); + }, + + /** + * Moves an object one level down in stack of drawn objects + * @method sendBackwards + * @param object {fabric.Object} Object to send + * @return {fabric.Canvas} thisArg + * @chainable + */ + sendBackwards: function (object) { + var idx = this._objects.indexOf(object), + nextIntersectingIdx = idx; + + // if object is not on the bottom of stack + if (idx !== 0) { + + // traverse down the stack looking for the nearest intersecting object + for (var i=idx-1; i>=0; --i) { + if (object.intersectsWithObject(this._objects[i]) || object.isContainedWithinObject(this._objects[i])) { + nextIntersectingIdx = i; + break; + } + } + removeFromArray(this._objects, object); + this._objects.splice(nextIntersectingIdx, 0, object); + } + return this.renderAll(); + }, + + /** + * Moves an object one level up in stack of drawn objects + * @method bringForward + * @param object {fabric.Object} Object to send + * @return {fabric.Canvas} thisArg + * @chainable + */ + bringForward: function (object) { + var objects = this.getObjects(), + idx = objects.indexOf(object), + nextIntersectingIdx = idx; + + + // if object is not on top of stack (last item in an array) + if (idx !== objects.length-1) { + + // traverse up the stack looking for the nearest intersecting object + for (var i = idx + 1, l = this._objects.length; i < l; ++i) { + if (object.intersectsWithObject(objects[i]) || object.isContainedWithinObject(this._objects[i])) { + nextIntersectingIdx = i; + break; + } + } + removeFromArray(objects, object); + objects.splice(nextIntersectingIdx, 0, object); + } + this.renderAll(); + }, + + /** + * Returns object at specified index + * @method item + * @param {Number} index + * @return {fabric.Object} + */ + item: function (index) { + return this.getObjects()[index]; + }, + + /** + * Returns number representation of an instance complexity + * @method complexity + * @return {Number} complexity + */ + complexity: function () { + return this.getObjects().reduce(function (memo, current) { + memo += current.complexity ? current.complexity() : 0; + return memo; + }, 0); + }, + + /** + * Iterates over all objects, invoking callback for each one of them + * @method forEachObject + * @return {fabric.Canvas} thisArg + */ + forEachObject: function(callback, context) { + var objects = this.getObjects(), + i = objects.length; + while (i--) { + callback.call(context, objects[i], i, objects); + } + return this; + }, + + /** + * Clears a canvas element and removes all event handlers. + * @method dispose + * @return {fabric.Canvas} thisArg + * @chainable + */ + dispose: function () { + this.clear(); + if (this.interactive) { + removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); + removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); + removeListener(fabric.window, 'resize', this._onResize); + } + return this; + }, + + /** + * @private + * @method _resizeImageToFit + * @param {HTMLImageElement} imgEl + */ + _resizeImageToFit: function (imgEl) { + + var imageWidth = imgEl.width || imgEl.offsetWidth, + widthScaleFactor = this.getWidth() / imageWidth; + + // scale image down so that it has original dimensions when printed in large resolution + if (imageWidth) { + imgEl.width = imageWidth * widthScaleFactor; + } + } + }); + + /** + * Returns a string representation of an instance + * @method toString + * @return {String} string representation of an instance + */ + fabric.StaticCanvas.prototype.toString = function () { // Assign explicitly since `extend` doesn't take care of DontEnum bug yet + return '#<fabric.Canvas (' + this.complexity() + '): '+ + '{ objects: ' + this.getObjects().length + ' }>'; + }; + + extend(fabric.StaticCanvas, /** @scope fabric.StaticCanvas */ { + + /** + * @static + * @property EMPTY_JSON + * @type String + */ + EMPTY_JSON: '{"objects": [], "background": "white"}', + + /** + * Takes <canvas> element and transforms its data in such way that it becomes grayscale + * @static + * @method toGrayscale + * @param {HTMLCanvasElement} canvasEl + */ + toGrayscale: function (canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + iLen = imageData.width, + jLen = imageData.height, + index, average, i, j; + + for (i = 0; i < iLen; i++) { + for (j = 0; j < jLen; j++) { + + index = (i * 4) * jLen + (j * 4); + average = (data[index] + data[index + 1] + data[index + 2]) / 3; + + data[index] = average; + data[index + 1] = average; + data[index + 2] = average; + } + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * Provides a way to check support of some of the canvas methods + * (either those of HTMLCanvasElement itself, or rendering context) + * + * @method supports + * @param methodName {String} Method to check support for; + * Could be one of "getImageData", "toDataURL" or "toDataURLWithQuality" + * @return {Boolean | null} `true` if method is supported (or at least exists), + * `null` if canvas element or context can not be initialized + */ + supports: function (methodName) { + var el = fabric.document.createElement('canvas'); + + if (typeof G_vmlCanvasManager !== 'undefined') { + G_vmlCanvasManager.initElement(el); + } + if (!el || !el.getContext) { + return null; + } + + var ctx = el.getContext('2d'); + if (!ctx) { + return null; + } + + switch (methodName) { + + case 'getImageData': + return typeof ctx.getImageData !== 'undefined'; + + case 'toDataURL': + return typeof el.toDataURL !== 'undefined'; + + case 'toDataURLWithQuality': + try { + el.toDataURL('image/jpeg', 0); + return true; + } catch (e) { + return false; + } + + default: + return null; + } + } + }); + + /** + * Returs JSON representation of canvas + * @function + * @method toJSON + * @return {String} json string + */ + fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject; + +})(typeof exports != 'undefined' ? exports : this); + +(function() { + + var extend = fabric.util.object.extend, + getPointer = fabric.util.getPointer, + addListener = fabric.util.addListener, + removeListener = fabric.util.removeListener, + cursorMap = { + 'tr': 'ne-resize', + 'br': 'se-resize', + 'bl': 'sw-resize', + 'tl': 'nw-resize', + 'ml': 'w-resize', + 'mt': 'n-resize', + 'mr': 'e-resize', + 'mb': 's-resize' + }, + + utilMin = fabric.util.array.min, + utilMax = fabric.util.array.max, + + sqrt = Math.sqrt, + pow = Math.pow, + atan2 = Math.atan2, + abs = Math.abs, + min = Math.min, + max = Math.max, + + STROKE_OFFSET = 0.5; + + /** + * @class fabric.Canvas + * @constructor + * @extends fabric.StaticCanvas + * @param {HTMLElement | String} el <canvas> element to initialize instance on + * @param {Object} [options] Options object + */ + fabric.Canvas = function(el, options) { + options || (options = { }); + + this._initStatic(el, options); + this._initInteractive(); + this._createCacheCanvas(); + + fabric.Canvas.activeInstance = this; + }; + + function ProtoProxy(){ } + ProtoProxy.prototype = fabric.StaticCanvas.prototype; + fabric.Canvas.prototype = new ProtoProxy; + + var InteractiveMethods = /** @scope fabric.Canvas.prototype */ { + + /** + * Indicates that canvas is interactive. This property should not be changed. + * @property + * @type Boolean + */ + interactive: true, + + /** + * Indicates whether group selection should be enabled + * @property + * @type Boolean + */ + selection: true, + + /** + * Color of selection + * @property + * @type String + */ + selectionColor: 'rgba(100, 100, 255, 0.3)', // blue + + /** + * Color of the border of selection (usually slightly darker than color of selection itself) + * @property + * @type String + */ + selectionBorderColor: 'rgba(255, 255, 255, 0.3)', + + /** + * Width of a line used in object/group selection + * @property + * @type Number + */ + selectionLineWidth: 1, + + /** + * Color of the line used in free drawing mode + * @property + * @type String + */ + freeDrawingColor: 'rgb(0, 0, 0)', + + /** + * Width of a line used in free drawing mode + * @property + * @type Number + */ + freeDrawingLineWidth: 1, + + /** + * Default cursor value used when hovering over an object on canvas + * @property + * @type String + */ + hoverCursor: 'move', + + /** + * Default cursor value used when moving an object on canvas + * @property + * @type String + */ + moveCursor: 'move', + + /** + * Default cursor value used for the entire canvas + * @property + * @type String + */ + defaultCursor: 'default', + + /** + * Cursor value used for rotation point + * @property + * @type String + */ + rotationCursor: 'crosshair', + + /** + * Default element class that's given to wrapper (div) element of canvas + * @property + * @type String + */ + containerClass: 'canvas-container', + + perPixelTargetFind: false, + + targetFindTolerance: 0, + + _initInteractive: function() { + this._currentTransform = null; + this._groupSelector = null; + this._freeDrawingXPoints = [ ]; + this._freeDrawingYPoints = [ ]; + this._initWrapperElement(); + this._createUpperCanvas(); + this._initEvents(); + this.calcOffset(); + }, + + /** + * Adds mouse listeners to canvas + * @method _initEvents + * @private + * See configuration documentation for more details. + */ + _initEvents: function () { + var _this = this; + + this._onMouseDown = function (e) { + _this.__onMouseDown(e); + + addListener(fabric.document, 'mouseup', _this._onMouseUp); + fabric.isTouchSupported && addListener(fabric.document, 'touchend', _this._onMouseUp); + + addListener(fabric.document, 'mousemove', _this._onMouseMove); + fabric.isTouchSupported && addListener(fabric.document, 'touchmove', _this._onMouseMove); + + removeListener(_this.upperCanvasEl, 'mousemove', _this._onMouseMove); + fabric.isTouchSupported && removeListener(_this.upperCanvasEl, 'touchmove', _this._onMouseMove); + }; + + this._onMouseUp = function (e) { + _this.__onMouseUp(e); + + removeListener(fabric.document, 'mouseup', _this._onMouseUp); + fabric.isTouchSupported && removeListener(fabric.document, 'touchend', _this._onMouseUp); + + removeListener(fabric.document, 'mousemove', _this._onMouseMove); + fabric.isTouchSupported && removeListener(fabric.document, 'touchmove', _this._onMouseMove); + + addListener(_this.upperCanvasEl, 'mousemove', _this._onMouseMove); + fabric.isTouchSupported && addListener(_this.upperCanvasEl, 'touchmove', _this._onMouseMove); + }; + + this._onMouseMove = function (e) { + e.preventDefault && e.preventDefault(); + _this.__onMouseMove(e); + }; + + this._onResize = function (e) { + _this.calcOffset(); + }; + + + addListener(fabric.window, 'resize', this._onResize); + + if (fabric.isTouchSupported) { + addListener(this.upperCanvasEl, 'touchstart', this._onMouseDown); + addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); + } + else { + addListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); + addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); + } + }, + + /** + * Method that defines the actions when mouse is released on canvas. + * The method resets the currentTransform parameters, store the image corner + * position in the image object and render the canvas on top. + * @method __onMouseUp + * @param {Event} e Event object fired on mouseup + * + */ + __onMouseUp: function (e) { + + if (this.isDrawingMode && this._isCurrentlyDrawing) { + this._finalizeDrawingPath(); + this.fire('mouse:up', { e: e }); + return; + } + + if (this._currentTransform) { + + var transform = this._currentTransform, + target = transform.target; + + if (target._scaling) { + target._scaling = false; + } + + // determine the new coords everytime the image changes its position + var i = this._objects.length; + while (i--) { + this._objects[i].setCoords(); + } + + // only fire :modified event if target coordinates were changed during mousedown-mouseup + if (this.stateful && target.hasStateChanged()) { + target.isMoving = false; + this.fire('object:modified', { target: target }); + target.fire('modified'); + } + } + + this._currentTransform = null; + + if (this._groupSelector) { + // group selection was completed, determine its bounds + this._findSelectedObjects(e); + } + var activeGroup = this.getActiveGroup(); + if (activeGroup) { + activeGroup.setObjectsCoords(); + activeGroup.set('isMoving', false); + this._setCursor(this.defaultCursor); + } + + // clear selection + this._groupSelector = null; + this.renderAll(); + + this._setCursorFromEvent(e, target); + + // fix for FF + this._setCursor(''); + + var _this = this; + setTimeout(function () { + _this._setCursorFromEvent(e, target); + }, 50); + + this.fire('mouse:up', { target: target, e: e }); + target && target.fire('mouseup', { e: e }) + }, + + /** + * Method that defines the actions when mouse is clic ked on canvas. + * The method inits the currentTransform parameters and renders all the + * canvas so the current image can be placed on the top canvas and the rest + * in on the container one. + * @method __onMouseDown + * @param e {Event} Event object fired on mousedown + * + */ + __onMouseDown: function (e) { + + // accept only left clicks + var isLeftClick = 'which' in e ? e.which == 1 : e.button == 1; + if (!isLeftClick && !fabric.isTouchSupported) return; + + if (this.isDrawingMode) { + this._prepareForDrawing(e); + + // capture coordinates immediately; this allows to draw dots (when movement never occurs) + this._captureDrawingPath(e); + this.fire('mouse:down', { e: e }); + return; + } + + // ignore if some object is being transformed at this moment + if (this._currentTransform) return; + + var target = this.findTarget(e), + pointer = this.getPointer(e), + activeGroup = this.getActiveGroup(), + corner; + + if (this._shouldClearSelection(e)) { + + this._groupSelector = { + ex: pointer.x, + ey: pointer.y, + top: 0, + left: 0 + }; + + this.deactivateAllWithDispatch(); + } + else { + // determine if it's a drag or rotate case + // rotate and scale will happen at the same time + this.stateful && target.saveState(); + + if (corner = target._findTargetCorner(e, this._offset)) { + this.onBeforeScaleRotate(target); + } + + this._setupCurrentTransform(e, target); + + var shouldHandleGroupLogic = e.shiftKey && (activeGroup || this.getActiveObject()) && this.selection; + if (shouldHandleGroupLogic) { + this._handleGroupLogic(e, target); + } + else { + if (target !== this.getActiveGroup()) { + this.deactivateAll(); + } + this.setActiveObject(target, e); + } + } + // we must renderAll so that active image is placed on the top canvas + this.renderAll(); + + this.fire('mouse:down', { target: target, e: e }); + target && target.fire('mousedown', { e: e }); + }, + + /** + * Method that defines the actions when mouse is hovering the canvas. + * The currentTransform parameter will definde whether the user is rotating/scaling/translating + * an image or neither of them (only hovering). A group selection is also possible and would cancel + * all any other type of action. + * In case of an image transformation only the top canvas will be rendered. + * @method __onMouseMove + * @param e {Event} Event object fired on mousemove + * + */ + __onMouseMove: function (e) { + + if (this.isDrawingMode) { + if (this._isCurrentlyDrawing) { + this._captureDrawingPath(e); + } + this.fire('mouse:move', { e: e }); + return; + } + + var groupSelector = this._groupSelector; + + // We initially clicked in an empty area, so we draw a box for multiple selection. + if (groupSelector !== null) { + var pointer = getPointer(e); + groupSelector.left = pointer.x - this._offset.left - groupSelector.ex; + groupSelector.top = pointer.y - this._offset.top - groupSelector.ey; + this.renderTop(); + } + else if (!this._currentTransform) { + + // alias style to elimintate unnecessary lookup + var style = this.upperCanvasEl.style; + + // Here we are hovering the canvas then we will determine + // what part of the pictures we are hovering to change the caret symbol. + // We won't do that while dragging or rotating in order to improve the + // performance. + var target = this.findTarget(e); + + if (!target) { + // image/text was hovered-out from, we remove its borders + for (var i = this._objects.length; i--; ) { + if (this._objects[i] && !this._objects[i].active) { + this._objects[i].setActive(false); + } + } + style.cursor = this.defaultCursor; + } + else { + // set proper cursor + this._setCursorFromEvent(e, target); + if (target.isActive()) { + // display corners when hovering over an image + target.setCornersVisibility && target.setCornersVisibility(true); + } + } + } + else { + // object is being transformed (scaled/rotated/moved/etc.) + var pointer = getPointer(e), + x = pointer.x, + y = pointer.y; + + this._currentTransform.target.isMoving = true; + + if (this._currentTransform.action === 'rotate') { + // rotate object only if shift key is not pressed + // and if it is not a group we are transforming + + if (!e.shiftKey) { + this._rotateObject(x, y); + + this.fire('object:rotating', { + target: this._currentTransform.target + }); + this._currentTransform.target.fire('rotating'); + } + if (!this._currentTransform.target.hasRotatingPoint) { + this._scaleObject(x, y); + this.fire('object:scaling', { + target: this._currentTransform.target + }); + this._currentTransform.target.fire('scaling'); + } + } + else if (this._currentTransform.action === 'scale') { + this._scaleObject(x, y); + this.fire('object:scaling', { + target: this._currentTransform.target + }); + this._currentTransform.target.fire('scaling'); + } + else if (this._currentTransform.action === 'scaleX') { + this._scaleObject(x, y, 'x'); + + this.fire('object:scaling', { + target: this._currentTransform.target + }); + this._currentTransform.target.fire('scaling'); + } + else if (this._currentTransform.action === 'scaleY') { + this._scaleObject(x, y, 'y'); + + this.fire('object:scaling', { + target: this._currentTransform.target + }); + this._currentTransform.target.fire('scaling'); + } + else { + this._translateObject(x, y); + + this.fire('object:moving', { + target: this._currentTransform.target + }); + + this._setCursor(this.moveCursor); + + this._currentTransform.target.fire('moving'); + } + // only commit here. when we are actually moving the pictures + this.renderAll(); + } + this.fire('mouse:move', { target: target, e: e }); + target && target.fire('mousemove', { e: e }); + }, + + /** + * Applies one implementation of 'point inside polygon' algorithm + * @method containsPoint + * @param e { Event } event object + * @param target { fabric.Object } object to test against + * @return {Boolean} true if point contains within area of given object + */ + containsPoint: function (e, target) { + var pointer = this.getPointer(e), + xy = this._normalizePointer(target, pointer), + x = xy.x, + y = xy.y; + + // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html + // http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html + + // we iterate through each object. If target found, return it. + var iLines = target._getImageLines(target.oCoords), + xpoints = target._findCrossPoints(x, y, iLines); + + // if xcount is odd then we clicked inside the object + // For the specific case of square images xcount === 1 in all true cases + if ((xpoints && xpoints % 2 === 1) || target._findTargetCorner(e, this._offset)) { + return true; + } + return false; + }, + + /** + * @private + * @method _normalizePointer + */ + _normalizePointer: function (object, pointer) { + + var activeGroup = this.getActiveGroup(), + x = pointer.x, + y = pointer.y; + + var isObjectInGroup = ( + activeGroup && + object.type !== 'group' && + activeGroup.contains(object) + ); + + if (isObjectInGroup) { + x -= activeGroup.left; + y -= activeGroup.top; + } + return { x: x, y: y }; + }, + + _isTargetTransparent: function (target, x, y) { + var cacheContext = this.contextCache; + + var hasBorders = target.hasBorders, transparentCorners = target.transparentCorners; + target.hasBorders = target.transparentCorners = false; + + this._draw(cacheContext, target); + + target.hasBorders = hasBorders; + target.transparentCorners = transparentCorners; + + // If tolerance is > 0 adjust start coords to take into account. If moves off Canvas fix to 0 + if (this.targetFindTolerance > 0) { + if (x > this.targetFindTolerance) { + x -= this.targetFindTolerance; + } + else { + x = 0; + } + if (y > this.targetFindTolerance) { + y -= this.targetFindTolerance; + } + else { + y = 0; + } + } + + var isTransparent = true; + var imageData = cacheContext.getImageData( + x, y, (this.targetFindTolerance * 2) || 1, (this.targetFindTolerance * 2) || 1); + + // Split image data - for tolerance > 1, pixelDataSize = 4; + for (var i = 3; i < imageData.data.length; i += 4) { + var temp = imageData.data[i]; + isTransparent = temp <= 0; + if (isTransparent === false) break; //Stop if colour found + } + + imageData = null; + this.clearContext(cacheContext); + return isTransparent; + }, + + /** + * @private + * @method _shouldClearSelection + */ + _shouldClearSelection: function (e) { + var target = this.findTarget(e), + activeGroup = this.getActiveGroup(); + return ( + !target || ( + target && + activeGroup && + !activeGroup.contains(target) && + activeGroup !== target && + !e.shiftKey + ) + ); + }, + + /** + * @private + * @method _setupCurrentTransform + */ + _setupCurrentTransform: function (e, target) { + var action = 'drag', + corner, + pointer = getPointer(e); + + if (corner = target._findTargetCorner(e, this._offset)) { + action = (corner === 'ml' || corner === 'mr') + ? 'scaleX' + : (corner === 'mt' || corner === 'mb') + ? 'scaleY' + : corner === 'mtr' + ? 'rotate' + : (target.hasRotatingPoint) + ? 'scale' + : 'rotate'; + } + + this._currentTransform = { + target: target, + action: action, + scaleX: target.scaleX, + scaleY: target.scaleY, + offsetX: pointer.x - target.left, + offsetY: pointer.y - target.top, + ex: pointer.x, + ey: pointer.y, + left: target.left, + top: target.top, + theta: target._theta, + width: target.width * target.scaleX + }; + + this._currentTransform.original = { + left: target.left, + top: target.top + }; + }, + + _handleGroupLogic: function (e, target) { + if (target === this.getActiveGroup()) { + // if it's a group, find target again, this time skipping group + target = this.findTarget(e, true); + // if even object is not found, bail out + if (!target || target.isType('group')) { + return; + } + } + var activeGroup = this.getActiveGroup(); + if (activeGroup) { + if (activeGroup.contains(target)) { + activeGroup.removeWithUpdate(target); + target.setActive(false); + if (activeGroup.size() === 1) { + // remove group alltogether if after removal it only contains 1 object + this.discardActiveGroup(); + } + } + else { + activeGroup.addWithUpdate(target); + } + this.fire('selection:created', { target: activeGroup, e: e }); + activeGroup.setActive(true); + } + else { + // group does not exist + if (this._activeObject) { + // only if there's an active object + if (target !== this._activeObject) { + // and that object is not the actual target + var group = new fabric.Group([ this._activeObject, target ]); + this.setActiveGroup(group); + activeGroup = this.getActiveGroup(); + } + } + // activate target object in any case + target.setActive(true); + } + + if (activeGroup) { + activeGroup.saveCoords(); + } + }, + + /** + * @private + * @method _prepareForDrawing + */ + _prepareForDrawing: function(e) { + + this._isCurrentlyDrawing = true; + + this.discardActiveObject().renderAll(); + + var pointer = this.getPointer(e); + + this._freeDrawingXPoints.length = this._freeDrawingYPoints.length = 0; + + this._freeDrawingXPoints.push(pointer.x); + this._freeDrawingYPoints.push(pointer.y); + + this.contextTop.beginPath(); + this.contextTop.moveTo(pointer.x, pointer.y); + this.contextTop.strokeStyle = this.freeDrawingColor; + this.contextTop.lineWidth = this.freeDrawingLineWidth; + this.contextTop.lineCap = this.contextTop.lineJoin = 'round'; + }, + + /** + * @private + * @method _captureDrawingPath + */ + _captureDrawingPath: function(e) { + var pointer = this.getPointer(e); + + this._freeDrawingXPoints.push(pointer.x); + this._freeDrawingYPoints.push(pointer.y); + + this.contextTop.lineTo(pointer.x, pointer.y); + this.contextTop.stroke(); + }, + + /** + * @private + * @method _finalizeDrawingPath + */ + _finalizeDrawingPath: function() { + + this.contextTop.closePath(); + + this._isCurrentlyDrawing = false; + + var minX = utilMin(this._freeDrawingXPoints), + minY = utilMin(this._freeDrawingYPoints), + maxX = utilMax(this._freeDrawingXPoints), + maxY = utilMax(this._freeDrawingYPoints), + ctx = this.contextTop, + path = [ ], + xPoint, + yPoint, + xPoints = this._freeDrawingXPoints, + yPoints = this._freeDrawingYPoints; + + path.push('M ', xPoints[0] - minX, ' ', yPoints[0] - minY, ' '); + + for (var i = 1; xPoint = xPoints[i], yPoint = yPoints[i]; i++) { + path.push('L ', xPoint - minX, ' ', yPoint - minY, ' '); + } + + // TODO (kangax): maybe remove Path creation from here, to decouple fabric.Canvas from fabric.Path, + // and instead fire something like "drawing:completed" event with path string + + path = path.join(''); + + if (path === "M 0 0 L 0 0 ") { + // do not create 0 width/height paths, as they are rendered inconsistently across browsers + // Firefox 4, for example, renders a dot, whereas Chrome 10 renders nothing + this.renderAll(); + return; + } + + var p = new fabric.Path(path); + + p.fill = null; + p.stroke = this.freeDrawingColor; + p.strokeWidth = this.freeDrawingLineWidth; + this.add(p); + p.set("left", minX + (maxX - minX) / 2).set("top", minY + (maxY - minY) / 2).setCoords(); + this.renderAll(); + this.fire('path:created', { path: p }); + }, + + /** + * Translates object by "setting" its left/top + * @method _translateObject + * @param x {Number} pointer's x coordinate + * @param y {Number} pointer's y coordinate + */ + _translateObject: function (x, y) { + var target = this._currentTransform.target; + target.lockMovementX || target.set('left', x - this._currentTransform.offsetX); + target.lockMovementY || target.set('top', y - this._currentTransform.offsetY); + }, + + /** + * Scales object by invoking its scaleX/scaleY methods + * @method _scaleObject + * @param x {Number} pointer's x coordinate + * @param y {Number} pointer's y coordinate + * @param by {String} Either 'x' or 'y' - specifies dimension constraint by which to scale an object. + * When not provided, an object is scaled by both dimensions equally + */ + _scaleObject: function (x, y, by) { + var t = this._currentTransform, + offset = this._offset, + target = t.target; + + if (target.lockScalingX && target.lockScalingY) return; + + var lastLen = sqrt(pow(t.ey - t.top - offset.top, 2) + pow(t.ex - t.left - offset.left, 2)), + curLen = sqrt(pow(y - t.top - offset.top, 2) + pow(x - t.left - offset.left, 2)); + + target._scaling = true; + + if (!by) { + target.lockScalingX || target.set('scaleX', t.scaleX * curLen/lastLen); + target.lockScalingY || target.set('scaleY', t.scaleY * curLen/lastLen); + } + else if (by === 'x' && !target.lockUniScaling) { + target.lockScalingX || target.set('scaleX', t.scaleX * curLen/lastLen); + } + else if (by === 'y' && !target.lockUniScaling) { + target.lockScalingY || target.set('scaleY', t.scaleY * curLen/lastLen); + } + }, + + /** + * Rotates object by invoking its rotate method + * @method _rotateObject + * @param x {Number} pointer's x coordinate + * @param y {Number} pointer's y coordinate + */ + _rotateObject: function (x, y) { + + var t = this._currentTransform, + o = this._offset; + + if (t.target.lockRotation) return; + + var lastAngle = atan2(t.ey - t.top - o.top, t.ex - t.left - o.left), + curAngle = atan2(y - t.top - o.top, x - t.left - o.left); + + t.target._theta = (curAngle - lastAngle) + t.theta; + }, + + /** + * @method _setCursor + */ + _setCursor: function (value) { + this.upperCanvasEl.style.cursor = value; + }, + + /** + * Sets the cursor depending on where the canvas is being hovered. + * Note: very buggy in Opera + * @method _setCursorFromEvent + * @param e {Event} Event object + * @param target {Object} Object that the mouse is hovering, if so. + */ + _setCursorFromEvent: function (e, target) { + var s = this.upperCanvasEl.style; + if (!target) { + s.cursor = this.defaultCursor; + return false; + } + else { + var activeGroup = this.getActiveGroup(); + // only show proper corner when group selection is not active + var corner = !!target._findTargetCorner + && (!activeGroup || !activeGroup.contains(target)) + && target._findTargetCorner(e, this._offset); + + if (!corner) { + s.cursor = this.hoverCursor; + } + else { + if (corner in cursorMap) { + s.cursor = cursorMap[corner]; + } else if (corner === 'mtr' && target.hasRotatingPoint) { + s.cursor = this.rotationCursor; + } else { + s.cursor = this.defaultCursor; + return false; + } + } + } + return true; + }, + + /** + * @method _drawSelection + * @private + */ + _drawSelection: function () { + var groupSelector = this._groupSelector, + left = groupSelector.left, + top = groupSelector.top, + aleft = abs(left), + atop = abs(top); + + this.contextTop.fillStyle = this.selectionColor; + + this.contextTop.fillRect( + groupSelector.ex - ((left > 0) ? 0 : -left), + groupSelector.ey - ((top > 0) ? 0 : -top), + aleft, + atop + ); + + this.contextTop.lineWidth = this.selectionLineWidth; + this.contextTop.strokeStyle = this.selectionBorderColor; + + this.contextTop.strokeRect( + groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0 : aleft), + groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0 : atop), + aleft, + atop + ); + }, + + _findSelectedObjects: function (e) { + var target, + targetRegion, + group = [ ], + x1 = this._groupSelector.ex, + y1 = this._groupSelector.ey, + x2 = x1 + this._groupSelector.left, + y2 = y1 + this._groupSelector.top, + currentObject, + selectionX1Y1 = new fabric.Point(min(x1, x2), min(y1, y2)), + selectionX2Y2 = new fabric.Point(max(x1, x2), max(y1, y2)); + + for (var i = 0, len = this._objects.length; i < len; ++i) { + currentObject = this._objects[i]; + + if (!currentObject) continue; + + if (currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2) || + currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2)) { + + if (this.selection && currentObject.selectable) { + currentObject.setActive(true); + group.push(currentObject); + } + } + } + + // do not create group for 1 element only + if (group.length === 1) { + this.setActiveObject(group[0], e); + } + else if (group.length > 1) { + var group = new fabric.Group(group); + this.setActiveGroup(group); + group.saveCoords(); + this.fire('selection:created', { target: group }); + } + + this.renderAll(); + }, + + /** + * Method that determines what object we are clicking on + * @method findTarget + * @param {Event} e mouse event + * @param {Boolean} skipGroup when true, group is skipped and only objects are traversed through + */ + findTarget: function (e, skipGroup) { + + var target, + pointer = this.getPointer(e); + + // first check current group (if one exists) + var activeGroup = this.getActiveGroup(); + + if (activeGroup && !skipGroup && this.containsPoint(e, activeGroup)) { + target = activeGroup; + return target; + } + + // then check all of the objects on canvas + // Cache all targets where their bounding box contains point. + var possibleTargets = []; + for (var i = this._objects.length; i--; ) { + if (this._objects[i] && this.containsPoint(e, this._objects[i])) { + if (this.perPixelTargetFind || this._objects[i].perPixelTargetFind) { + possibleTargets[possibleTargets.length] = this._objects[i]; + } + else { + target = this._objects[i]; + this.relatedTarget = target; + break; + } + } + } + for (var i = 0, len = possibleTargets.length; i < len; i++) { + var pointer = this.getPointer(e); + var isTransparent = this._isTargetTransparent(possibleTargets[i], pointer.x, pointer.y); + if (!isTransparent) { + target = possibleTargets[i]; + this.relatedTarget = target; + break; + } + } + if (target && target.selectable) { + return target; + } + }, + + /** + * Returns pointer coordinates relative to canvas. + * @method getPointer + * @return {Object} object with "x" and "y" number values + */ + getPointer: function (e) { + var pointer = getPointer(e); + return { + x: pointer.x - this._offset.left, + y: pointer.y - this._offset.top + }; + }, + + /** + * @method _createUpperCanvas + * @param {HTMLElement|String} canvasEl Canvas element + * @throws {CANVAS_INIT_ERROR} If canvas can not be initialized + */ + _createUpperCanvas: function () { + this.upperCanvasEl = this._createCanvasElement(); + this.upperCanvasEl.className = 'upper-canvas'; + + this.wrapperEl.appendChild(this.upperCanvasEl); + + this._applyCanvasStyle(this.upperCanvasEl); + this.contextTop = this.upperCanvasEl.getContext('2d'); + }, + + _createCacheCanvas: function () { + this.cacheCanvasEl = this._createCanvasElement(); + this.cacheCanvasEl.setAttribute('width', this.width); + this.cacheCanvasEl.setAttribute('height', this.height); + this.contextCache = this.cacheCanvasEl.getContext('2d'); + }, + + /** + * @private + * @method _initWrapperElement + * @param {Number} width + * @param {Number} height + */ + _initWrapperElement: function () { + this.wrapperEl = fabric.util.wrapElement(this.lowerCanvasEl, 'div', { + 'class': this.containerClass + }); + fabric.util.setStyle(this.wrapperEl, { + width: this.getWidth() + 'px', + height: this.getHeight() + 'px', + position: 'relative' + }); + fabric.util.makeElementUnselectable(this.wrapperEl); + }, + + /** + * @private + * @method _applyCanvasStyle + * @param {Element} element + */ + _applyCanvasStyle: function (element) { + var width = this.getWidth() || element.width, + height = this.getHeight() || element.height; + + fabric.util.setStyle(element, { + position: 'absolute', + width: width + 'px', + height: height + 'px', + left: 0, + top: 0 + }); + element.width = width; + element.height = height; + fabric.util.makeElementUnselectable(element); + }, + + /** + * Returns context of canvas where object selection is drawn + * @method getSelectionContext + * @return {CanvasRenderingContext2D} + */ + getSelectionContext: function() { + return this.contextTop; + }, + + /** + * Returns <canvas> element on which object selection is drawn + * @method getSelectionElement + * @return {HTMLCanvasElement} + */ + getSelectionElement: function () { + return this.upperCanvasEl; + }, + + /** + * Sets given object as active + * @method setActiveObject + * @param object {fabric.Object} Object to set as an active one + * @return {fabric.Canvas} thisArg + * @chainable + */ + setActiveObject: function (object, e) { + if (this._activeObject) { + this._activeObject.setActive(false); + } + this._activeObject = object; + object.setActive(true); + + this.renderAll(); + + this.fire('object:selected', { target: object, e: e }); + object.fire('selected', { e: e }); + return this; + }, + + /** + * Returns currently active object + * @method getActiveObject + * @return {fabric.Object} active object + */ + getActiveObject: function () { + return this._activeObject; + }, + + /** + * Discards currently active object + * @method discardActiveObject + * @return {fabric.Canvas} thisArg + * @chainable + */ + discardActiveObject: function () { + if (this._activeObject) { + this._activeObject.setActive(false); + } + this._activeObject = null; + return this; + }, + + /** + * Sets active group to a speicified one + * @method setActiveGroup + * @param {fabric.Group} group Group to set as a current one + * @return {fabric.Canvas} thisArg + * @chainable + */ + setActiveGroup: function (group) { + this._activeGroup = group; + group && group.setActive(true); + return this; + }, + + /** + * Returns currently active group + * @method getActiveGroup + * @return {fabric.Group} Current group + */ + getActiveGroup: function () { + return this._activeGroup; + }, + + /** + * Removes currently active group + * @method discardActiveGroup + * @return {fabric.Canvas} thisArg + */ + discardActiveGroup: function () { + var g = this.getActiveGroup(); + if (g) { + g.destroy(); + } + return this.setActiveGroup(null); + }, + + /** + * Deactivates all objects by calling their setActive(false) + * @method deactivateAll + * @return {fabric.Canvas} thisArg + */ + deactivateAll: function () { + var allObjects = this.getObjects(), + i = 0, + len = allObjects.length; + for ( ; i < len; i++) { + allObjects[i].setActive(false); + } + this.discardActiveGroup(); + this.discardActiveObject(); + return this; + }, + + /** + * Deactivates all objects and dispatches appropriate events + * @method deactivateAllWithDispatch + * @return {fabric.Canvas} thisArg + */ + deactivateAllWithDispatch: function () { + var activeObject = this.getActiveGroup() || this.getActiveObject(); + if (activeObject) { + this.fire('before:selection:cleared', { target: activeObject }); + } + this.deactivateAll(); + if (activeObject) { + this.fire('selection:cleared'); + } + return this; + } + }; + + fabric.Canvas.prototype.toString = fabric.StaticCanvas.prototype.toString; + extend(fabric.Canvas.prototype, InteractiveMethods); + + // iterating manually to workaround Opera's bug + // where "prototype" property is enumerable and overrides existing prototype + for (var prop in fabric.StaticCanvas) { + if (prop !== 'prototype') { + fabric.Canvas[prop] = fabric.StaticCanvas[prop]; + } + } + + if (fabric.isTouchSupported) { + fabric.Canvas.prototype._setCursorFromEvent = function() { }; + } + + /** + * @class fabric.Element + * @alias fabric.Canvas + * @deprecated + * @constructor + */ + fabric.Element = fabric.Canvas; +})(); +fabric.util.object.extend(fabric.StaticCanvas.prototype, { + + FX_DURATION: 500, + + /** + * Centers object horizontally with animation. + * @method fxCenterObjectH + * @param {fabric.Object} object Object to center + * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties + * @return {fabric.Canvas} thisArg + * @chainable + */ + fxCenterObjectH: function (object, callbacks) { + callbacks = callbacks || { }; + + var empty = function() { }, + onComplete = callbacks.onComplete || empty, + onChange = callbacks.onChange || empty, + _this = this; + + fabric.util.animate({ + startValue: object.get('left'), + endValue: this.getCenter().left, + duration: this.FX_DURATION, + onChange: function(value) { + object.set('left', value); + _this.renderAll(); + onChange(); + }, + onComplete: function() { + object.setCoords(); + onComplete(); + } + }); + + return this; + }, + + /** + * Centers object vertically with animation. + * @method fxCenterObjectV + * @param {fabric.Object} object Object to center + * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties + * @return {fabric.Canvas} thisArg + * @chainable + */ + fxCenterObjectV: function (object, callbacks) { + callbacks = callbacks || { }; + + var empty = function() { }, + onComplete = callbacks.onComplete || empty, + onChange = callbacks.onChange || empty, + _this = this; + + fabric.util.animate({ + startValue: object.get('top'), + endValue: this.getCenter().top, + duration: this.FX_DURATION, + onChange: function(value) { + object.set('top', value); + _this.renderAll(); + onChange(); + }, + onComplete: function() { + object.setCoords(); + onComplete(); + } + }); + + return this; + }, + + /** + * Same as `fabric.Canvas#remove` but animated + * @method fxRemove + * @param {fabric.Object} object Object to remove + * @param {Function} callback Callback, invoked on effect completion + * @return {fabric.Canvas} thisArg + * @chainable + */ + fxRemove: function (object, callbacks) { + callbacks = callbacks || { }; + + var empty = function() { }, + onComplete = callbacks.onComplete || empty, + onChange = callbacks.onChange || empty, + _this = this; + + fabric.util.animate({ + startValue: object.get('opacity'), + endValue: 0, + duration: this.FX_DURATION, + onStart: function() { + object.setActive(false); + }, + onChange: function(value) { + object.set('opacity', value); + _this.renderAll(); + onChange(); + }, + onComplete: function () { + _this.remove(object); + onComplete(); + } + }); + + return this; + } +}); +fabric.util.object.extend(fabric.StaticCanvas.prototype, { + + /** + * Populates canvas with data from the specified dataless JSON + * JSON format must conform to the one of `fabric.Canvas#toDatalessJSON` + * @method loadFromDatalessJSON + * @param {String} json JSON string + * @param {Function} callback Callback, invoked when json is parsed + * and corresponding objects (e.g: fabric.Image) + * are initialized + * @return {fabric.Canvas} instance + * @chainable + */ + loadFromDatalessJSON: function (json, callback) { + + if (!json) { + return; + } + + // serialize if it wasn't already + var serialized = (typeof json === 'string') + ? JSON.parse(json) + : json; + + if (!serialized || (serialized && !serialized.objects)) return; + + this.clear(); + + // TODO: test this + this.backgroundColor = serialized.background; + this._enlivenDatalessObjects(serialized.objects, callback); + }, + + /** + * @method _enlivenDatalessObjects + * @param {Array} objects + * @param {Function} callback + */ + _enlivenDatalessObjects: function (objects, callback) { + + /** @ignore */ + function onObjectLoaded(object, index) { + _this.insertAt(object, index, true); + object.setCoords(); + if (++numLoadedObjects === numTotalObjects) { + callback && callback(); + } + } + + var _this = this, + numLoadedObjects = 0, + numTotalObjects = objects.length; + + if (numTotalObjects === 0 && callback) { + callback(); + } + + try { + objects.forEach(function (obj, index) { + + var pathProp = obj.paths ? 'paths' : 'path'; + var path = obj[pathProp]; + + delete obj[pathProp]; + + if (typeof path !== 'string') { + switch (obj.type) { + case 'image': + fabric[fabric.util.string.capitalize(obj.type)].fromObject(obj, function (o) { + onObjectLoaded(o, index); + }); + break; + default: + var klass = fabric[fabric.util.string.camelize(fabric.util.string.capitalize(obj.type))]; + if (klass && klass.fromObject) { + // restore path + if (path) { + obj[pathProp] = path; + } + onObjectLoaded(klass.fromObject(obj), index); + } + break; + } + } + else { + if (obj.type === 'image') { + fabric.util.loadImage(path, function (image) { + var oImg = new fabric.Image(image); + + oImg.setSourcePath(path); + + fabric.util.object.extend(oImg, obj); + oImg.setAngle(obj.angle); + + onObjectLoaded(oImg, index); + }); + } + else if (obj.type === 'text') { + + if (obj.useNative) { + onObjectLoaded(fabric.Text.fromObject(obj), index); + } + else { + obj.path = path; + var object = fabric.Text.fromObject(obj); + var onscriptload = function () { + // TODO (kangax): find out why Opera refuses to work without this timeout + if (Object.prototype.toString.call(fabric.window.opera) === '[object Opera]') { + setTimeout(function () { + onObjectLoaded(object, index); + }, 500); + } + else { + onObjectLoaded(object, index); + } + } + + fabric.util.getScript(path, onscriptload); + } + } + else { + fabric.loadSVGFromURL(path, function (elements, options) { + var object = fabric.util.groupSVGElements(elements, obj, path); + + // copy parameters from serialied json to object (left, top, scaleX, scaleY, etc.) + // skip this step if an object is a PathGroup, since we already passed it options object before + if (!(object instanceof fabric.PathGroup)) { + fabric.util.object.extend(object, obj); + if (typeof obj.angle !== 'undefined') { + object.setAngle(obj.angle); + } + } + + onObjectLoaded(object, index); + }); + } + } + }, this); + } + catch(e) { + fabric.log(e.message); + } + }, + + /** + * Populates canvas with data from the specified JSON + * JSON format must conform to the one of `fabric.Canvas#toJSON` + * @method loadFromJSON + * @param {String} json JSON string + * @param {Function} callback Callback, invoked when json is parsed + * and corresponding objects (e.g: fabric.Image) + * are initialized + * @return {fabric.Canvas} instance + * @chainable + */ + loadFromJSON: function (json, callback) { + if (!json) return; + + var serialized = JSON.parse(json); + if (!serialized || (serialized && !serialized.objects)) return; + + this.clear(); + var _this = this; + this._enlivenObjects(serialized.objects, function () { + _this.backgroundColor = serialized.background; + + if (serialized.backgroundImage) { + _this.setBackgroundImage(serialized.backgroundImage, function() { + + _this.backgroundImageOpacity = serialized.backgroundImageOpacity; + _this.backgroundImageStretch = serialized.backgroundImageStretch; + + _this.renderAll(); + + callback && callback(); + }); + } + else { + callback && callback(); + } + }); + + return this; + }, + + /** + * @method _enlivenObjects + * @param {Array} objects + * @param {Function} callback + */ + _enlivenObjects: function (objects, callback) { + var _this = this; + fabric.util.enlivenObjects(objects, function(enlivenedObjects) { + enlivenedObjects.forEach(function(obj, index) { + _this.insertAt(obj, index, true); + }); + callback && callback(); + }); + }, + + /** + * @private + * @method _toDataURL + * @param {String} format + * @param {Function} callback + */ + _toDataURL: function (format, callback) { + this.clone(function (clone) { + callback(clone.toDataURL(format)); + }); + }, + + /** + * @private + * @method _toDataURLWithMultiplier + * @param {String} format + * @param {Number} multiplier + * @param {Function} callback + */ + _toDataURLWithMultiplier: function (format, multiplier, callback) { + this.clone(function (clone) { + callback(clone.toDataURLWithMultiplier(format, multiplier)); + }); + }, + + /** + * Clones canvas instance + * @method clone + * @param {Object} [callback] Receives cloned instance as a first argument + */ + clone: function (callback) { + var data = JSON.stringify(this); + this.cloneWithoutData(function(clone) { + clone.loadFromJSON(data, function() { + callback && callback(clone); + }); + }); + }, + + /** + * Clones canvas instance without cloning existing data. + * This essentially copies canvas dimensions, clipping properties, etc. + * but leaves data empty (so that you can populate it with your own) + * @method cloneWithoutData + * @param {Object} [callback] Receives cloned instance as a first argument + */ + cloneWithoutData: function(callback) { + var el = fabric.document.createElement('canvas'); + + el.width = this.getWidth(); + el.height = this.getHeight(); + + var clone = new fabric.Canvas(el); + clone.clipTo = this.clipTo; + if (this.backgroundImage) { + clone.setBackgroundImage(this.backgroundImage.src, function() { + clone.renderAll(); + callback && callback(clone); + }); + clone.backgroundImageOpacity = this.backgroundImageOpacity; + clone.backgroundImageStretch = this.backgroundImageStretch; + } + else { + callback && callback(clone); + } + } +}); +(function(global) { + + "use strict"; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + clone = fabric.util.object.clone, + toFixed = fabric.util.toFixed, + capitalize = fabric.util.string.capitalize, + getPointer = fabric.util.getPointer, + degreesToRadians = fabric.util.degreesToRadians, + slice = Array.prototype.slice; + + if (fabric.Object) { + return; + } + + /** + * @class Object + * @memberOf fabric + */ + fabric.Object = fabric.util.createClass(/** @scope fabric.Object.prototype */ { + + /** + * Type of an object (rect, circle, path, etc) + * @property + * @type String + */ + type: 'object', + + /** + * @property + * @type Number + */ + top: 0, + + /** + * @property + * @type Number + */ + left: 0, + + /** + * @property + * @type Number + */ + width: 0, + + /** + * @property + * @type Number + */ + height: 0, + + /** + * @property + * @type Number + */ + scaleX: 1, + + /** + * @property + * @type Number + */ + scaleY: 1, + + /** + * @property + * @type Boolean + */ + flipX: false, + + /** + * @property + * @type Boolean + */ + flipY: false, + + /** + * @property + * @type Number + */ + opacity: 1, + + /** + * @property + * @type Number + */ + angle: 0, + + /** + * @property + * @type Number + */ + cornersize: 12, + + /** + * @property + * @type Boolean + */ + transparentCorners: true, + + /** + * @property + * @type Number + */ + padding: 0, + + /** + * @property + * @type String + */ + borderColor: 'rgba(102,153,255,0.75)', + + /** + * @property + * @type String + */ + cornerColor: 'rgba(102,153,255,0.5)', + + /** + * @property + * @type String + */ + fill: 'rgb(0,0,0)', + + /** + * @property + * @type String + */ + fillRule: 'source-over', + + /** + * @property + * @type String + */ + overlayFill: null, + + /** + * @property + * @type String + */ + stroke: null, + + /** + * @property + * @type Number + */ + strokeWidth: 1, + + /** + * @property + * @type Array + */ + strokeDashArray: null, + + /** + * @property + * @type Number + */ + borderOpacityWhenMoving: 0.4, + + /** + * @property + * @type Number + */ + borderScaleFactor: 1, + + /** + * Transform matrix + * @property + * @type Array + */ + transformMatrix: null, + + /** + * When set to `false`, an object can not be selected for modification (using either point-click-based or group-based selection) + * @property + * @type Boolean + */ + selectable: true, + + /** + * When set to `false`, object's controls are not displayed and can not be used to manipulate object + * @property + * @type Boolean + */ + hasControls: true, + + /** + * When set to `false`, object's borders are not rendered + * @property + * @type Boolean + */ + hasBorders: true, + + /** + * When set to `false`, object's rotating point will not be visible or selectable + * @property + * @type Boolean + */ + hasRotatingPoint: false, + + /** + * Offset for object's rotating point (when enabled) + * @property + * @type Number + */ + rotatingPointOffset: 40, + + /** + * @private + * @property + * @type Number + */ + _theta: 0, + + perPixelTargetFind: false, + + includeDefaultValues: true, + + /** + * List of properties to consider when checking if state of an object is changed (fabric.Object#hasStateChanged); + * as well as for history (undo/redo) purposes + * @property + * @type Array + */ + stateProperties: ( + 'top left width height scaleX scaleY flipX flipY ' + + 'theta angle opacity cornersize fill overlayFill ' + + 'stroke strokeWidth strokeDashArray fillRule ' + + 'borderScaleFactor transformMatrix selectable' + ).split(' '), + + /** + * @method callSuper + * @param {String} methodName + */ + callSuper: function(methodName) { + var fn = this.constructor.superclass.prototype[methodName]; + return (arguments.length > 1) + ? fn.apply(this, slice.call(arguments, 1)) + : fn.call(this); + }, + + /** + * Constructor + * @method initialize + * @param {Object} [options] Options object + */ + initialize: function(options) { + if (options) { + this.setOptions(options); + this._initGradient(options); + } + }, + + /** + * @method initGradient + */ + _initGradient: function(options) { + if (options.fill && typeof options.fill == 'object' && !(options.fill instanceof fabric.Gradient)) { + this.set('fill', new fabric.Gradient(options.fill)); + } + }, + + /** + * @method setOptions + * @param {Object} [options] + */ + setOptions: function(options) { + var i = this.stateProperties.length, prop; + while (i--) { + prop = this.stateProperties[i]; + if (prop in options) { + this.set(prop, options[prop]); + } + } + }, + + /** + * @method transform + * @param {CanvasRenderingContext2D} ctx Context + */ + transform: function(ctx) { + ctx.globalAlpha = this.opacity; + ctx.translate(this.left, this.top); + ctx.rotate(this._theta); + ctx.scale( + this.scaleX * (this.flipX ? -1 : 1), + this.scaleY * (this.flipY ? -1 : 1) + ); + }, + + /** + * Returns an object representation of an instance + * @method toObject + * @return {Object} + */ + toObject: function() { + + var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; + + var object = { + type: this.type, + left: toFixed(this.left, NUM_FRACTION_DIGITS), + top: toFixed(this.top, NUM_FRACTION_DIGITS), + width: toFixed(this.width, NUM_FRACTION_DIGITS), + height: toFixed(this.height, NUM_FRACTION_DIGITS), + fill: (this.fill && this.fill.toObject) ? this.fill.toObject() : this.fill, + overlayFill: this.overlayFill, + stroke: this.stroke, + strokeWidth: this.strokeWidth, + strokeDashArray: this.strokeDashArray, + scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS), + scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS), + angle: toFixed(this.getAngle(), NUM_FRACTION_DIGITS), + flipX: this.flipX, + flipY: this.flipY, + opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS), + selectable: this.selectable, + hasControls: this.hasControls, + hasBorders: this.hasBorders, + hasRotatingPoint: this.hasRotatingPoint, + transparentCorners: this.transparentCorners, + perPixelTargetFind: this.perPixelTargetFind + }; + + if (!this.includeDefaultValues) { + object = this._removeDefaultValues(object); + } + + return object; + }, + + /** + * Returns (dataless) object representation of an instance + * @method toDatalessObject + */ + toDatalessObject: function() { + // will be overwritten by subclasses + return this.toObject(); + }, + + /** + * Returns styles-string for svg-export + * @method getSvgStyles + * @return {string} + */ + getSvgStyles: function() { + return [ + "stroke: ", (this.stroke ? this.stroke : 'none'), "; ", + "stroke-width: ", (this.strokeWidth ? this.strokeWidth : '0'), "; ", + "stroke-dasharray: ", (this.strokeDashArray ? this.strokeDashArray.join(' ') : "; "), + "fill: ", (this.fill ? this.fill : 'none'), "; ", + "opacity: ", (this.opacity ? this.opacity : '1'), ";" + ].join(""); + }, + + /** + * Returns transform-string for svg-export + * @method getSvgTransform + * @return {string} + */ + getSvgTransform: function() { + var angle = this.getAngle(); + return [ + "translate(", toFixed(this.left, 2), " ", toFixed(this.top, 2), ")", + angle !== 0 ? (" rotate(" + toFixed(angle, 2) + ")") : '', + (this.scaleX === 1 && this.scaleY === 1) ? '' : (" scale(" + toFixed(this.scaleX, 2) + " " + toFixed(this.scaleY, 2) + ")") + ].join(''); + }, + + /** + * @private + * @method _removeDefaultValues + */ + _removeDefaultValues: function(object) { + var defaultOptions = fabric.Object.prototype.options; + if (defaultOptions) { + this.stateProperties.forEach(function(prop) { + if (object[prop] === defaultOptions[prop]) { + delete object[prop]; + } + }); + } + return object; + }, + + /** + * Returns true if an object is in its active state + * @return {Boolean} true if an object is in its active state + */ + isActive: function() { + return !!this.active; + }, + + /** + * Sets state of an object - `true` makes it active, `false` - inactive + * @param {Boolean} active + * @return {fabric.Object} thisArg + * @chainable + */ + setActive: function(active) { + this.active = !!active; + return this; + }, + + /** + * Returns a string representation of an instance + * @return {String} + */ + toString: function() { + return "#<fabric." + capitalize(this.type) + ">"; + }, + + /** + * Sets property to a given value + * @method set + * @param {String} name + * @param {Object|Function} value + * @return {fabric.Group} thisArg + * @chainable + */ + set: function(key, value) { + if (typeof key === 'object') { + for (var prop in key) { + this._set(prop, key[prop]); + } + } + else { + if (typeof value === 'function') { + this._set(key, value(this.get(key))); + } + else { + this._set(key, value); + } + } + return this; + }, + + _set: function(key, value) { + var shouldConstrainValue = (key === 'scaleX' || key === 'scaleY') && + value < fabric.Object.MIN_SCALE_LIMIT; + + if (shouldConstrainValue) { + value = fabric.Object.MIN_SCALE_LIMIT; + } + if (key === 'angle') { + this.setAngle(value); + } + else { + this[key] = value; + } + }, + + /** + * Toggles specified property from `true` to `false` or from `false` to `true` + * @method toggle + * @param {String} property property to toggle + * @return {fabric.Object} thisArg + * @chainable + */ + toggle: function(property) { + var value = this.get(property); + if (typeof value === 'boolean') { + this.set(property, !value); + } + return this; + }, + + /** + * @method setSourcePath + * @param {String} value + * @return {fabric.Object} thisArg + * @chainable + */ + setSourcePath: function(value) { + this.sourcePath = value; + return this; + }, + + /** + * Basic getter + * @method get + * @param {Any} property + * @return {Any} value of a property + */ + get: function(property) { + return (property === 'angle') + ? this.getAngle() + : this[property]; + }, + + /** + * @method render + * @param {CanvasRenderingContext2D} ctx context to render on + * @param {Boolean} noTransform + */ + render: function(ctx, noTransform) { + + // do not render if width or height are zeros + if (this.width === 0 || this.height === 0) return; + + ctx.save(); + + var m = this.transformMatrix; + if (m && !this.group) { + ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + + if (!noTransform) { + this.transform(ctx); + } + + if (this.stroke || this.strokeDashArray) { + ctx.lineWidth = this.strokeWidth; + ctx.strokeStyle = this.stroke; + } + + if (this.overlayFill) { + ctx.fillStyle = this.overlayFill; + } + else if (this.fill) { + ctx.fillStyle = this.fill.toLiveGradient + ? this.fill.toLiveGradient(ctx) + : this.fill; + } + + if (m && this.group) { + ctx.translate(-this.group.width/2, -this.group.height/2); + ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + + this._render(ctx, noTransform); + + if (this.active && !noTransform) { + this.drawBorders(ctx); + this.drawCorners(ctx); + } + ctx.restore(); + }, + + /** + * Returns width of an object + * @method getWidth + * @return {Number} width value + */ + getWidth: function() { + return this.width * this.scaleX; + }, + + /** + * Returns height of an object + * @method getHeight + * @return {Number} height value + */ + getHeight: function() { + return this.height * this.scaleY; + }, + + /** + * Scales an object (equally by x and y) + * @method scale + * @param value {Number} scale factor + * @return {fabric.Object} thisArg + * @chainable + */ + scale: function(value) { + this.scaleX = value; + this.scaleY = value; + this.setCoords(); + return this; + }, + + /** + * Scales an object to a given width, with respect to bounding box (scaling by x/y equally) + * @method scaleToWidth + * @param value {Number} new width value + * @return {fabric.Object} thisArg + * @chainable + */ + scaleToWidth: function(value) { + // adjust to bounding rect factor so that rotated shapes would fit as well + var boundingRectFactor = this.getBoundingRectWidth() / this.getWidth(); + return this.scale(value / this.width / boundingRectFactor); + }, + + /** + * Scales an object to a given height, with respect to bounding box (scaling by x/y equally) + * @method scaleToHeight + * @param value {Number} new height value + * @return {fabric.Object} thisArg + * @chainable + */ + scaleToHeight: function(value) { + // adjust to bounding rect factor so that rotated shapes would fit as well + var boundingRectFactor = this.getBoundingRectHeight() / this.getHeight(); + return this.scale(value / this.height / boundingRectFactor); + }, + + /** + * Sets object opacity + * @method setOpacity + * @param value {Number} value 0-1 + * @return {fabric.Object} thisArg + * @chainable + */ + setOpacity: function(value) { + this.set('opacity', value); + return this; + }, + + /** + * Returns object's angle value + * @method getAngle + * @return {Number} angle value + */ + getAngle: function() { + return this._theta * 180 / Math.PI; + }, + + /** + * Sets object's angle + * @method setAngle + * @param value {Number} angle value + * @return {Object} thisArg + */ + setAngle: function(value) { + this._theta = value / 180 * Math.PI; + this.angle = value; + return this; + }, + + /** + * Sets corner position coordinates based on current angle, width and height. + * @method setCoords + * return {fabric.Object} thisArg + * @chainable + */ + setCoords: function() { + + var strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0, + padding = this.padding; + + this.currentWidth = (this.width + strokeWidth) * this.scaleX + padding * 2; + this.currentHeight = (this.height + strokeWidth) * this.scaleY + padding * 2; + + this._hypotenuse = Math.sqrt( + Math.pow(this.currentWidth / 2, 2) + + Math.pow(this.currentHeight / 2, 2)); + + this._angle = Math.atan(this.currentHeight / this.currentWidth); + + // offset added for rotate and scale actions + var offsetX = Math.cos(this._angle + this._theta) * this._hypotenuse, + offsetY = Math.sin(this._angle + this._theta) * this._hypotenuse, + theta = this._theta, + sinTh = Math.sin(theta), + cosTh = Math.cos(theta); + + var tl = { + x: this.left - offsetX, + y: this.top - offsetY + }; + var tr = { + x: tl.x + (this.currentWidth * cosTh), + y: tl.y + (this.currentWidth * sinTh) + }; + var br = { + x: tr.x - (this.currentHeight * sinTh), + y: tr.y + (this.currentHeight * cosTh) + }; + var bl = { + x: tl.x - (this.currentHeight * sinTh), + y: tl.y + (this.currentHeight * cosTh) + }; + var ml = { + x: tl.x - (this.currentHeight/2 * sinTh), + y: tl.y + (this.currentHeight/2 * cosTh) + }; + var mt = { + x: tl.x + (this.currentWidth/2 * cosTh), + y: tl.y + (this.currentWidth/2 * sinTh) + }; + var mr = { + x: tr.x - (this.currentHeight/2 * sinTh), + y: tr.y + (this.currentHeight/2 * cosTh) + }; + var mb = { + x: bl.x + (this.currentWidth/2 * cosTh), + y: bl.y + (this.currentWidth/2 * sinTh) + }; + var mtr = { + x: tl.x + (this.currentWidth/2 * cosTh), + y: tl.y + (this.currentWidth/2 * sinTh) + }; + + // debugging + + // setTimeout(function() { + // canvas.contextTop.fillStyle = 'green'; + // canvas.contextTop.fillRect(mb.x, mb.y, 3, 3); + // canvas.contextTop.fillRect(bl.x, bl.y, 3, 3); + // canvas.contextTop.fillRect(br.x, br.y, 3, 3); + // canvas.contextTop.fillRect(tl.x, tl.y, 3, 3); + // canvas.contextTop.fillRect(tr.x, tr.y, 3, 3); + // canvas.contextTop.fillRect(ml.x, ml.y, 3, 3); + // canvas.contextTop.fillRect(mr.x, mr.y, 3, 3); + // canvas.contextTop.fillRect(mt.x, mt.y, 3, 3); + // }, 50); + + // clockwise + this.oCoords = { tl: tl, tr: tr, br: br, bl: bl, ml: ml, mt: mt, mr: mr, mb: mb, mtr: mtr }; + + // set coordinates of the draggable boxes in the corners used to scale/rotate the image + this._setCornerCoords(); + + return this; + }, + + /** + * Returns width of an object's bounding rectangle + * @method getBoundingRectWidth + * @return {Number} width value + */ + getBoundingRectWidth: function() { + this.oCoords || this.setCoords(); + var xCoords = [this.oCoords.tl.x, this.oCoords.tr.x, this.oCoords.br.x, this.oCoords.bl.x]; + var minX = fabric.util.array.min(xCoords); + var maxX = fabric.util.array.max(xCoords); + return Math.abs(minX - maxX); + }, + + /** + * Returns height of an object's bounding rectangle + * @method getBoundingRectHeight + * @return {Number} height value + */ + getBoundingRectHeight: function() { + this.oCoords || this.setCoords(); + var yCoords = [this.oCoords.tl.y, this.oCoords.tr.y, this.oCoords.br.y, this.oCoords.bl.y]; + var minY = fabric.util.array.min(yCoords); + var maxY = fabric.util.array.max(yCoords); + return Math.abs(minY - maxY); + }, + + /** + * Draws borders of an object's bounding box. + * Requires public properties: width, height + * Requires public options: padding, borderColor + * @method drawBorders + * @param {CanvasRenderingContext2D} ctx Context to draw on + * @return {fabric.Object} thisArg + * @chainable + */ + drawBorders: function(ctx) { + if (!this.hasBorders) return; + + var MIN_SCALE_LIMIT = fabric.Object.MIN_SCALE_LIMIT, + padding = this.padding, + padding2 = padding * 2, + strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0; + + ctx.save(); + + ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; + ctx.strokeStyle = this.borderColor; + + var scaleX = 1 / (this.scaleX < MIN_SCALE_LIMIT ? MIN_SCALE_LIMIT : this.scaleX), + scaleY = 1 / (this.scaleY < MIN_SCALE_LIMIT ? MIN_SCALE_LIMIT : this.scaleY); + + ctx.lineWidth = 1 / this.borderScaleFactor; + + ctx.scale(scaleX, scaleY); + + var w = this.getWidth(), + h = this.getHeight(); + + ctx.strokeRect( + ~~(-(w / 2) - padding - strokeWidth / 2 * this.scaleX) + 0.5, // offset needed to make lines look sharper + ~~(-(h / 2) - padding - strokeWidth / 2 * this.scaleY) + 0.5, + ~~(w + padding2 + strokeWidth * this.scaleX), + ~~(h + padding2 + strokeWidth * this.scaleY) + ); + + if (this.hasRotatingPoint && !this.lockRotation && this.hasControls) { + + var rotateHeight = ( + this.flipY + ? h + (strokeWidth * this.scaleY) + (padding * 2) + : -h - (strokeWidth * this.scaleY) - (padding * 2) + ) / 2; + + var rotateWidth = (-w/2); + + ctx.beginPath(); + ctx.moveTo(0, rotateHeight); + ctx.lineTo(0, rotateHeight + (this.flipY ? this.rotatingPointOffset : -this.rotatingPointOffset)); + ctx.closePath(); + ctx.stroke(); + } + + ctx.restore(); + return this; + }, + + _renderDashedStroke: function(ctx) { + + if (1 & this.strokeDashArray.length /* if odd number of items */) { + /* duplicate items */ + this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray); + } + + var i = 0, + x = -this.width/2, y = -this.height/2, + _this = this, + padding = this.padding, + width = this.getWidth(), + height = this.getHeight(), + dashedArrayLength = this.strokeDashArray.length; + + ctx.save(); + ctx.beginPath(); + + function renderSide(xMultiplier, yMultiplier) { + + var lineLength = 0, + sideLength = (yMultiplier ? _this.height : _this.width) + padding * 2; + + while (lineLength < sideLength) { + + var lengthOfSubPath = _this.strokeDashArray[i++]; + lineLength += lengthOfSubPath; + + if (lineLength > sideLength) { + var lengthDiff = lineLength - sideLength; + } + + // track coords + if (xMultiplier) { + x += (lengthOfSubPath * xMultiplier) - (lengthDiff * xMultiplier || 0); + } + else { + y += (lengthOfSubPath * yMultiplier) - (lengthDiff * yMultiplier || 0); + } + + ctx[1 & i /* odd */ ? 'moveTo' : 'lineTo'](x, y); + if (i >= dashedArrayLength) { + i = 0; + } + } + } + + renderSide(1, 0); + renderSide(0, 1); + renderSide(-1, 0); + renderSide(0, -1); + + ctx.stroke(); + ctx.closePath(); + ctx.restore(); + }, + + /** + * Draws corners of an object's bounding box. + * Requires public properties: width, height, scaleX, scaleY + * Requires public options: cornersize, padding + * @method drawCorners + * @param {CanvasRenderingContext2D} ctx Context to draw on + * @return {fabric.Object} thisArg + * @chainable + */ + drawCorners: function(ctx) { + if (!this.hasControls) return; + + var size = this.cornersize, + size2 = size / 2, + strokeWidth2 = this.strokeWidth / 2, + left = -(this.width / 2), + top = -(this.height / 2), + _left, + _top, + sizeX = size / this.scaleX, + sizeY = size / this.scaleY, + paddingX = this.padding / this.scaleX, + paddingY = this.padding / this.scaleY, + scaleOffsetY = size2 / this.scaleY, + scaleOffsetX = size2 / this.scaleX, + scaleOffsetSizeX = (size2 - size) / this.scaleX, + scaleOffsetSizeY = (size2 - size) / this.scaleY, + height = this.height, + width = this.width, + methodName = this.transparentCorners ? 'strokeRect' : 'fillRect'; + + ctx.save(); + + ctx.lineWidth = 1 / Math.max(this.scaleX, this.scaleY); + + ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; + ctx.strokeStyle = ctx.fillStyle = this.cornerColor; + + // top-left + _left = left - scaleOffsetX - strokeWidth2 - paddingX; + _top = top - scaleOffsetY - strokeWidth2 - paddingY; + + ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + + // top-right + _left = left + width - scaleOffsetX + strokeWidth2 + paddingX; + _top = top - scaleOffsetY - strokeWidth2 - paddingY; + + ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + + // bottom-left + _left = left - scaleOffsetX - strokeWidth2 - paddingX; + _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY; + + ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + + // bottom-right + _left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX; + _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY; + + ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + + if (!this.lockUniScaling) { + // middle-top + _left = left + width/2 - scaleOffsetX; + _top = top - scaleOffsetY - strokeWidth2 - paddingY; + + ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + + // middle-bottom + _left = left + width/2 - scaleOffsetX; + _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY; + + ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + + // middle-right + _left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX; + _top = top + height/2 - scaleOffsetY; + + ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + + // middle-left + _left = left - scaleOffsetX - strokeWidth2 - paddingX; + _top = top + height/2 - scaleOffsetY; + + ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + } + + // middle-top-rotate + if (this.hasRotatingPoint) { + + _left = left + width/2 - scaleOffsetX; + + _top = this.flipY ? + (top + height + (this.rotatingPointOffset / this.scaleY) - sizeY/2 + strokeWidth2 + paddingY) + : (top - (this.rotatingPointOffset / this.scaleY) - sizeY/2 - strokeWidth2 - paddingY); + + ctx.clearRect(_left, _top, sizeX, sizeY); + ctx[methodName](_left, _top, sizeX, sizeY); + } + + ctx.restore(); + + return this; + }, + + /** + * Clones an instance + * @method clone + * @param {Object} options object + * @return {fabric.Object} clone of an instance + */ + clone: function(options) { + if (this.constructor.fromObject) { + return this.constructor.fromObject(this.toObject(), options); + } + return new fabric.Object(this.toObject()); + }, + + /** + * Creates an instance of fabric.Image out of an object + * @method cloneAsImage + * @param callback {Function} callback, invoked with an instance as a first argument + * @return {fabric.Object} thisArg + * @chainable + */ + cloneAsImage: function(callback) { + if (fabric.Image) { + var i = new Image(); + + /** @ignore */ + i.onload = function() { + if (callback) { + callback(new fabric.Image(i), orig); + } + i = i.onload = null; + }; + + var orig = { + angle: this.get('angle'), + flipX: this.get('flipX'), + flipY: this.get('flipY') + }; + + // normalize angle + this.set('angle', 0).set('flipX', false).set('flipY', false); + this.toDataURL(function(dataURL) { + i.src = dataURL; + }); + } + return this; + }, + + /** + * Converts an object into a data-url-like string + * @method toDataURL + * @return {String} string of data + */ + toDataURL: function(callback) { + var el = fabric.document.createElement('canvas'); + if (!el.getContext && typeof G_vmlCanvasManager != 'undefined') { + G_vmlCanvasManager.initElement(el); + } + + el.width = this.getBoundingRectWidth(); + el.height = this.getBoundingRectHeight(); + + fabric.util.wrapElement(el, 'div'); + + var canvas = new fabric.Canvas(el); + canvas.backgroundColor = 'transparent'; + canvas.renderAll(); + + if (this.constructor.async) { + this.clone(proceed); + } + else { + proceed(this.clone()); + } + + function proceed(clone) { + clone.left = el.width / 2; + clone.top = el.height / 2; + + clone.setActive(false); + + canvas.add(clone); + var data = canvas.toDataURL('png'); + + canvas.dispose(); + canvas = clone = null; + + callback && callback(data); + } + }, + + /** + * @method hasStateChanged + * @return {Boolean} true if instance' state has changed + */ + hasStateChanged: function() { + return this.stateProperties.some(function(prop) { + return this[prop] !== this.originalState[prop]; + }, this); + }, + + /** + * @method saveState + * @return {fabric.Object} thisArg + * @chainable + */ + saveState: function() { + this.stateProperties.forEach(function(prop) { + this.originalState[prop] = this.get(prop); + }, this); + return this; + }, + + /** + * @method setupState + */ + setupState: function() { + this.originalState = { }; + this.saveState(); + }, + + /** + * Returns true if object intersects with an area formed by 2 points + * @method intersectsWithRect + * @param {Object} selectionTL + * @param {Object} selectionBR + * @return {Boolean} + */ + intersectsWithRect: function(selectionTL, selectionBR) { + var oCoords = this.oCoords, + tl = new fabric.Point(oCoords.tl.x, oCoords.tl.y), + tr = new fabric.Point(oCoords.tr.x, oCoords.tr.y), + bl = new fabric.Point(oCoords.bl.x, oCoords.bl.y), + br = new fabric.Point(oCoords.br.x, oCoords.br.y); + + var intersection = fabric.Intersection.intersectPolygonRectangle( + [tl, tr, br, bl], + selectionTL, + selectionBR + ); + return (intersection.status === 'Intersection'); + }, + + /** + * Returns true if object intersects with another object + * @method intersectsWithObject + * @param {Object} other Object to test + * @return {Boolean} + */ + intersectsWithObject: function(other) { + // extracts coords + function getCoords(oCoords) { + return { + tl: new fabric.Point(oCoords.tl.x, oCoords.tl.y), + tr: new fabric.Point(oCoords.tr.x, oCoords.tr.y), + bl: new fabric.Point(oCoords.bl.x, oCoords.bl.y), + br: new fabric.Point(oCoords.br.x, oCoords.br.y) + } + } + var thisCoords = getCoords(this.oCoords), + otherCoords = getCoords(other.oCoords); + + var intersection = fabric.Intersection.intersectPolygonPolygon( + [thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl], + [otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl] + ); + + return (intersection.status === 'Intersection'); + }, + + /** + * Returns true if object is fully contained within area of another object + * @method isContainedWithinObject + * @param {Object} other Object to test + * @return {Boolean} + */ + isContainedWithinObject: function(other) { + return this.isContainedWithinRect(other.oCoords.tl, other.oCoords.br); + }, + + /** + * Returns true if object is fully contained within area formed by 2 points + * @method isContainedWithinRect + * @param {Object} selectionTL + * @param {Object} selectionBR + * @return {Boolean} + */ + isContainedWithinRect: function(selectionTL, selectionBR) { + var oCoords = this.oCoords, + tl = new fabric.Point(oCoords.tl.x, oCoords.tl.y), + tr = new fabric.Point(oCoords.tr.x, oCoords.tr.y), + bl = new fabric.Point(oCoords.bl.x, oCoords.bl.y), + br = new fabric.Point(oCoords.br.x, oCoords.br.y); + + return tl.x > selectionTL.x + && tr.x < selectionBR.x + && tl.y > selectionTL.y + && bl.y < selectionBR.y; + }, + + /** + * @method isType + * @param type {String} type to check against + * @return {Boolean} true if specified type is identical to the type of instance + */ + isType: function(type) { + return this.type === type; + }, + + /** + * Determines which one of the four corners has been clicked + * @method _findTargetCorner + * @private + * @param e {Event} event object + * @param offset {Object} canvas offset + * @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found + */ + _findTargetCorner: function(e, offset) { + if (!this.hasControls || !this.active) return false; + + var pointer = getPointer(e), + ex = pointer.x - offset.left, + ey = pointer.y - offset.top, + xpoints, + lines; + + for (var i in this.oCoords) { + + if (i === 'mtr' && !this.hasRotatingPoint) { + continue; + } + + if (this.lockUniScaling && (i === 'mt' || i === 'mr' || i === 'mb' || i === 'ml')) { + continue; + } + + lines = this._getImageLines(this.oCoords[i].corner, i); + + // debugging + + // canvas.contextTop.fillRect(lines.bottomline.d.x, lines.bottomline.d.y, 2, 2); + // canvas.contextTop.fillRect(lines.bottomline.o.x, lines.bottomline.o.y, 2, 2); + + // canvas.contextTop.fillRect(lines.leftline.d.x, lines.leftline.d.y, 2, 2); + // canvas.contextTop.fillRect(lines.leftline.o.x, lines.leftline.o.y, 2, 2); + + // canvas.contextTop.fillRect(lines.topline.d.x, lines.topline.d.y, 2, 2); + // canvas.contextTop.fillRect(lines.topline.o.x, lines.topline.o.y, 2, 2); + + // canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2); + // canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2); + + xpoints = this._findCrossPoints(ex, ey, lines); + if (xpoints % 2 == 1 && xpoints != 0) { + this.__corner = i; + return i; + } + } + return false; + }, + + /** + * Helper method to determine how many cross points are between the 4 image edges + * and the horizontal line determined by the position of our mouse when clicked on canvas + * @method _findCrossPoints + * @private + * @param ex {Number} x coordinate of the mouse + * @param ey {Number} y coordinate of the mouse + * @param oCoords {Object} Coordinates of the image being evaluated + */ + _findCrossPoints: function(ex, ey, oCoords) { + var b1, b2, a1, a2, xi, yi, + xcount = 0, + iLine; + + for (var lineKey in oCoords) { + iLine = oCoords[lineKey]; + // optimisation 1: line below dot. no cross + if ((iLine.o.y < ey) && (iLine.d.y < ey)) { + continue; + } + // optimisation 2: line above dot. no cross + if ((iLine.o.y >= ey) && (iLine.d.y >= ey)) { + continue; + } + // optimisation 3: vertical line case + if ((iLine.o.x == iLine.d.x) && (iLine.o.x >= ex)) { + xi = iLine.o.x; + yi = ey; + } + // calculate the intersection point + else { + b1 = 0; + b2 = (iLine.d.y-iLine.o.y)/(iLine.d.x-iLine.o.x); + a1 = ey-b1*ex; + a2 = iLine.o.y-b2*iLine.o.x; + + xi = - (a1-a2)/(b1-b2); + yi = a1+b1*xi; + } + // dont count xi < ex cases + if (xi >= ex) { + xcount += 1; + } + // optimisation 4: specific for square images + if (xcount == 2) { + break; + } + } + return xcount; + }, + + /** + * Method that returns an object with the image lines in it given the coordinates of the corners + * @method _getImageLines + * @private + * @param oCoords {Object} coordinates of the image corners + */ + _getImageLines: function(oCoords, i) { + return { + topline: { + o: oCoords.tl, + d: oCoords.tr + }, + rightline: { + o: oCoords.tr, + d: oCoords.br + }, + bottomline: { + o: oCoords.br, + d: oCoords.bl + }, + leftline: { + o: oCoords.bl, + d: oCoords.tl + } + } + }, + + /** + * Sets the coordinates of the draggable boxes in the corners of + * the image used to scale/rotate it. + * @method _setCornerCoords + * @private + */ + _setCornerCoords: function() { + var coords = this.oCoords, + theta = degreesToRadians(45 - this.getAngle()), + cornerHypotenuse = Math.sqrt(2 * Math.pow(this.cornersize, 2)) / 2, + cosHalfOffset = cornerHypotenuse * Math.cos(theta), + sinHalfOffset = cornerHypotenuse * Math.sin(theta), + sinTh = Math.sin(this._theta), + cosTh = Math.cos(this._theta); + + coords.tl.corner = { + tl: { + x: coords.tl.x - sinHalfOffset, + y: coords.tl.y - cosHalfOffset + }, + tr: { + x: coords.tl.x + cosHalfOffset, + y: coords.tl.y - sinHalfOffset + }, + bl: { + x: coords.tl.x - cosHalfOffset, + y: coords.tl.y + sinHalfOffset + }, + br: { + x: coords.tl.x + sinHalfOffset, + y: coords.tl.y + cosHalfOffset + } + }; + + coords.tr.corner = { + tl: { + x: coords.tr.x - sinHalfOffset, + y: coords.tr.y - cosHalfOffset + }, + tr: { + x: coords.tr.x + cosHalfOffset, + y: coords.tr.y - sinHalfOffset + }, + br: { + x: coords.tr.x + sinHalfOffset, + y: coords.tr.y + cosHalfOffset + }, + bl: { + x: coords.tr.x - cosHalfOffset, + y: coords.tr.y + sinHalfOffset + } + }; + + coords.bl.corner = { + tl: { + x: coords.bl.x - sinHalfOffset, + y: coords.bl.y - cosHalfOffset + }, + bl: { + x: coords.bl.x - cosHalfOffset, + y: coords.bl.y + sinHalfOffset + }, + br: { + x: coords.bl.x + sinHalfOffset, + y: coords.bl.y + cosHalfOffset + }, + tr: { + x: coords.bl.x + cosHalfOffset, + y: coords.bl.y - sinHalfOffset + } + }; + + coords.br.corner = { + tr: { + x: coords.br.x + cosHalfOffset, + y: coords.br.y - sinHalfOffset + }, + bl: { + x: coords.br.x - cosHalfOffset, + y: coords.br.y + sinHalfOffset + }, + br: { + x: coords.br.x + sinHalfOffset, + y: coords.br.y + cosHalfOffset + }, + tl: { + x: coords.br.x - sinHalfOffset, + y: coords.br.y - cosHalfOffset + } + }; + + coords.ml.corner = { + tl: { + x: coords.ml.x - sinHalfOffset, + y: coords.ml.y - cosHalfOffset + }, + tr: { + x: coords.ml.x + cosHalfOffset, + y: coords.ml.y - sinHalfOffset + }, + bl: { + x: coords.ml.x - cosHalfOffset, + y: coords.ml.y + sinHalfOffset + }, + br: { + x: coords.ml.x + sinHalfOffset, + y: coords.ml.y + cosHalfOffset + } + }; + + coords.mt.corner = { + tl: { + x: coords.mt.x - sinHalfOffset, + y: coords.mt.y - cosHalfOffset + }, + tr: { + x: coords.mt.x + cosHalfOffset, + y: coords.mt.y - sinHalfOffset + }, + bl: { + x: coords.mt.x - cosHalfOffset, + y: coords.mt.y + sinHalfOffset + }, + br: { + x: coords.mt.x + sinHalfOffset, + y: coords.mt.y + cosHalfOffset + } + }; + + coords.mr.corner = { + tl: { + x: coords.mr.x - sinHalfOffset, + y: coords.mr.y - cosHalfOffset + }, + tr: { + x: coords.mr.x + cosHalfOffset, + y: coords.mr.y - sinHalfOffset + }, + bl: { + x: coords.mr.x - cosHalfOffset, + y: coords.mr.y + sinHalfOffset + }, + br: { + x: coords.mr.x + sinHalfOffset, + y: coords.mr.y + cosHalfOffset + } + }; + + coords.mb.corner = { + tl: { + x: coords.mb.x - sinHalfOffset, + y: coords.mb.y - cosHalfOffset + }, + tr: { + x: coords.mb.x + cosHalfOffset, + y: coords.mb.y - sinHalfOffset + }, + bl: { + x: coords.mb.x - cosHalfOffset, + y: coords.mb.y + sinHalfOffset + }, + br: { + x: coords.mb.x + sinHalfOffset, + y: coords.mb.y + cosHalfOffset + } + }; + + coords.mtr.corner = { + tl: { + x: coords.mtr.x - sinHalfOffset + (sinTh * this.rotatingPointOffset), + y: coords.mtr.y - cosHalfOffset - (cosTh * this.rotatingPointOffset) + }, + tr: { + x: coords.mtr.x + cosHalfOffset + (sinTh * this.rotatingPointOffset), + y: coords.mtr.y - sinHalfOffset - (cosTh * this.rotatingPointOffset) + }, + bl: { + x: coords.mtr.x - cosHalfOffset + (sinTh * this.rotatingPointOffset), + y: coords.mtr.y + sinHalfOffset - (cosTh * this.rotatingPointOffset) + }, + br: { + x: coords.mtr.x + sinHalfOffset + (sinTh * this.rotatingPointOffset), + y: coords.mtr.y + cosHalfOffset - (cosTh * this.rotatingPointOffset) + } + }; + }, + + /** + * Makes object's color grayscale + * @method toGrayscale + * @return {fabric.Object} thisArg + */ + toGrayscale: function() { + var fillValue = this.get('fill'); + if (fillValue) { + this.set('overlayFill', new fabric.Color(fillValue).toGrayscale().toRgb()); + } + return this; + }, + + /** + * @method complexity + * @return {Number} + */ + complexity: function() { + return 0; + }, + + /** + * Returns a JSON representation of an instance + * @method toJSON + * @return {String} json + */ + toJSON: function() { + // delegate, not alias + return this.toObject(); + }, + + /** + * @method setGradientFill + */ + setGradientFill: function(options) { + this.set('fill', fabric.Gradient.forObject(this, options)); + }, + + /** + * @method animate + * + * As object — multiple properties + * + * object.animate({ left: ..., top: ... }); + * object.animate({ left: ..., top: ... }, { duration: ... }); + * + * As string — one property + * + * object.animate('left', ...); + * object.animate('left', { duration: ... }); + * + */ + animate: function() { + if (arguments[0] && typeof arguments[0] == 'object') { + for (var prop in arguments[0]) { + this._animate(prop, arguments[0][prop], arguments[1]); + } + } + else { + this._animate.apply(this, arguments); + } + return this; + }, + + /** + * @private + * @method _animate + */ + _animate: function(property, to, options) { + var obj = this; + + options || (options = { }); + + if (!('from' in options)) { + options.from = this.get(property); + } + + if (/[+-]/.test((to + '').charAt(0))) { + to = this.get(property) + parseFloat(to); + } + + fabric.util.animate({ + startValue: options.from, + endValue: to, + byValue: options.by, + easing: options.easing, + duration: options.duration, + onChange: function(value) { + obj.set(property, value); + options.onChange && options.onChange(); + }, + onComplete: function() { + obj.setCoords(); + options.onComplete && options.onComplete(); + } + }); + }, + + /** + * Centers object horizontally on canvas to which it was added last + * @method centerH + * @return {fabric.Object} thisArg + */ + centerH: function () { + this.canvas.centerObjectH(this); + return this; + }, + + /** + * Centers object vertically on canvas to which it was added last + * @method centerV + * @return {fabric.Object} thisArg + * @chainable + */ + centerV: function () { + this.canvas.centerObjectV(this); + return this; + }, + + /** + * Centers object vertically and horizontally on canvas to which is was added last + * @method center + * @return {fabric.Object} thisArg + * @chainable + */ + center: function () { + return this.centerH().centerV(); + }, + + /** + * Removes object from canvas to which it was added last + * @method remove + * @return {fabric.Object} thisArg + * @chainable + */ + remove: function() { + return this.canvas.remove(this); + }, + + /** + * Moves an object to the bottom of the stack of drawn objects + * @method sendToBack + * @return {fabric.Object} thisArg + * @chainable + */ + sendToBack: function() { + this.canvas.sendToBack(this); + return this; + }, + + /** + * Moves an object to the top of the stack of drawn objects + * @method bringToFront + * @return {fabric.Object} thisArg + * @chainable + */ + bringToFront: function() { + this.canvas.bringToFront(this); + return this; + }, + + /** + * Moves an object one level down in stack of drawn objects + * @method sendBackwards + * @return {fabric.Object} thisArg + * @chainable + */ + sendBackwards: function() { + this.canvas.sendBackwards(this); + return this; + }, + + /** + * Moves an object one level up in stack of drawn objects + * @method bringForward + * @return {fabric.Object} thisArg + * @chainable + */ + bringForward: function() { + this.canvas.bringForward(this); + return this; + } + }); + + /** + * @alias rotate -> setAngle + */ + fabric.Object.prototype.rotate = fabric.Object.prototype.setAngle; + + var proto = fabric.Object.prototype; + for (var i = proto.stateProperties.length; i--; ) { + + var propName = proto.stateProperties[i], + capitalizedPropName = propName.charAt(0).toUpperCase() + propName.slice(1), + setterName = 'set' + capitalizedPropName, + getterName = 'get' + capitalizedPropName; + + // using `new Function` for better introspection + if (!proto[getterName]) { + proto[getterName] = (function(property) { + return new Function('return this.get("' + property + '")'); + })(propName); + } + if (!proto[setterName]) { + proto[setterName] = (function(property) { + return new Function('value', 'return this.set("' + property + '", value)'); + })(propName); + } + } + + extend(fabric.Object.prototype, fabric.Observable); + + extend(fabric.Object, { + + /** + * @static + * @constant + * @type Number + */ + NUM_FRACTION_DIGITS: 2, + + /** + * @static + * @constant + * @type Number + */ + MIN_SCALE_LIMIT: 0.1 + + }); + +})(typeof exports != 'undefined' ? exports : this); + +(function(global) { + + "use strict"; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + parentSet = fabric.Object.prototype.set, + coordProps = { 'x1': 1, 'x2': 1, 'y1': 1, 'y2': 1 }; + + if (fabric.Line) { + fabric.warn('fabric.Line is already defined'); + return; + } + + /** + * @class Line + * @extends fabric.Object + */ + fabric.Line = fabric.util.createClass(fabric.Object, /** @scope fabric.Line.prototype */ { + + /** + * @property + * @type String + */ + type: 'line', + + /** + * Constructor + * @method initialize + * @param {Array} points Array of points + * @param {Object} [options] Options object + * @return {fabric.Line} thisArg + */ + initialize: function(points, options) { + if (!points) { + points = [0, 0, 0, 0]; + } + + this.callSuper('initialize', options); + + this.set('x1', points[0]); + this.set('y1', points[1]); + this.set('x2', points[2]); + this.set('y2', points[3]); + + this._setWidthHeight(options); + }, + + /** + * @private + * @method _setWidthHeight + * @param {Object} options + */ + _setWidthHeight: function(options) { + options || (options = { }); + + this.set('width', (this.x2 - this.x1) || 1); + this.set('height', (this.y2 - this.y1) || 1); + + this.set('left', 'left' in options ? options.left : (this.x1 + this.width / 2)); + this.set('top', 'top' in options ? options.top : (this.y1 + this.height / 2)); + }, + + /** + * @private + * @method _set + * @param {String} key + * @param {Any} value + */ + _set: function(key, value) { + this[key] = value; + if (key in coordProps) { + this._setWidthHeight(); + } + return this; + }, + + /** + * @private + * @method _render + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + ctx.beginPath(); + + if (this.group) { + ctx.translate(-this.group.width/2 + this.left, -this.group.height / 2 + this.top); + } + + // move from center (of virtual box) to its left/top corner + ctx.moveTo(this.width === 1 ? 0 : (-this.width / 2), this.height === 1 ? 0 : (-this.height / 2)); + ctx.lineTo(this.width === 1 ? 0 : (this.width / 2), this.height === 1 ? 0 : (this.height / 2)); + + ctx.lineWidth = this.strokeWidth; + + // TODO: test this + // make sure setting "fill" changes color of a line + // (by copying fillStyle to strokeStyle, since line is stroked, not filled) + var origStrokeStyle = ctx.strokeStyle; + ctx.strokeStyle = ctx.fillStyle; + ctx.stroke(); + ctx.strokeStyle = origStrokeStyle; + }, + + /** + * Returns complexity of an instance + * @method complexity + * @return {Number} complexity + */ + complexity: function() { + return 1; + }, + + /** + * Returns object representation of an instance + * @methd toObject + * @return {Object} + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + x1: this.get('x1'), + y1: this.get('y1'), + x2: this.get('x2'), + y2: this.get('y2') + }); + }, + + /** + * Returns svg representation of an instance + * @method toSVG + * @return {string} svg representation of an instance + */ + toSVG: function() { + return [ + '<line ', + 'x1="', this.get('x1'), '" ', + 'y1="', this.get('y1'), '" ', + 'x2="', this.get('x2'), '" ', + 'y2="', this.get('y2'), '" ', + 'style="', this.getSvgStyles(), '" ', + '/>' + ].join(''); + } + }); + + /** + * List of attribute names to account for when parsing SVG element (used by `fabric.Line.fromElement`) + * @static + * @see http://www.w3.org/TR/SVG/shapes.html#LineElement + */ + fabric.Line.ATTRIBUTE_NAMES = 'x1 y1 x2 y2 stroke stroke-width transform'.split(' '); + + /** + * Returns fabric.Line instance from an SVG element + * @static + * @method fabric.Line.fromElement + * @param {SVGElement} element Element to parse + * @param {Object} [options] Options object + * @return {fabric.Line} instance of fabric.Line + */ + fabric.Line.fromElement = function(element, options) { + var parsedAttributes = fabric.parseAttributes(element, fabric.Line.ATTRIBUTE_NAMES); + var points = [ + parsedAttributes.x1 || 0, + parsedAttributes.y1 || 0, + parsedAttributes.x2 || 0, + parsedAttributes.y2 || 0 + ]; + return new fabric.Line(points, extend(parsedAttributes, options)); + }; + + /** + * Returns fabric.Line instance from an object representation + * @static + * @method fabric.Line.fromObject + * @param {Object} object Object to create an instance from + * @return {fabric.Line} instance of fabric.Line + */ + fabric.Line.fromObject = function(object) { + var points = [object.x1, object.y1, object.x2, object.y2]; + return new fabric.Line(points, object); + }; + +})(typeof exports != 'undefined' ? exports : this); +(function(global) { + + "use strict"; + + var fabric = global.fabric || (global.fabric = { }), + piBy2 = Math.PI * 2, + extend = fabric.util.object.extend; + + if (fabric.Circle) { + fabric.warn('fabric.Circle is already defined.'); + return; + } + + /** + * @class Circle + * @extends fabric.Object + */ + fabric.Circle = fabric.util.createClass(fabric.Object, /** @scope fabric.Circle.prototype */ { + + /** + * @property + * @type String + */ + type: 'circle', + + /** + * Constructor + * @method initialize + * @param {Object} [options] Options object + * @return {fabric.Circle} thisArg + */ + initialize: function(options) { + options = options || { }; + + this.set('radius', options.radius || 0); + this.callSuper('initialize', options); + + var diameter = this.get('radius') * 2; + this.set('width', diameter).set('height', diameter); + }, + + /** + * Returns object representation of an instance + * @method toObject + * @return {Object} object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + radius: this.get('radius') + }); + }, + + /** + * Returns svg representation of an instance + * @method toSVG + * @return {string} svg representation of an instance + */ + toSVG: function() { + return ('<circle ' + + 'cx="0" cy="0" ' + + 'r="' + this.radius + '" ' + + 'style="' + this.getSvgStyles() + '" ' + + 'transform="' + this.getSvgTransform() + '" ' + + '/>'); + }, + + /** + * @private + * @method _render + * @param ctx {CanvasRenderingContext2D} context to render on + */ + _render: function(ctx, noTransform) { + ctx.beginPath(); + // multiply by currently set alpha (the one that was set by path group where this object is contained, for example) + ctx.globalAlpha *= this.opacity; + ctx.arc(noTransform ? this.left : 0, noTransform ? this.top : 0, this.radius, 0, piBy2, false); + ctx.closePath(); + if (this.fill) { + ctx.fill(); + } + if (this.stroke) { + ctx.stroke(); + } + }, + + /** + * Returns horizontal radius of an object (according to how an object is scaled) + * @method getRadiusX + * @return {Number} + */ + getRadiusX: function() { + return this.get('radius') * this.get('scaleX'); + }, + + /** + * Returns vertical radius of an object (according to how an object is scaled) + * @method getRadiusY + * @return {Number} + */ + getRadiusY: function() { + return this.get('radius') * this.get('scaleY'); + }, + + /** + * Sets radius of an object (and updates width accordingly) + * @method setRadius + * @return {Number} + */ + setRadius: function(value) { + this.radius = value; + this.set('width', value * 2).set('height', value * 2); + }, + + /** + * Returns complexity of an instance + * @method complexity + * @return {Number} complexity of this instance + */ + complexity: function() { + return 1; + } + }); + + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Circle.fromElement}) + * @static + * @see: http://www.w3.org/TR/SVG/shapes.html#CircleElement + */ + fabric.Circle.ATTRIBUTE_NAMES = 'cx cy r fill fill-opacity opacity stroke stroke-width transform'.split(' '); + + /** + * Returns {@link fabric.Circle} instance from an SVG element + * @static + * @method fabric.Circle.fromElement + * @param element {SVGElement} element to parse + * @param options {Object} options object + * @throws {Error} If value of `r` attribute is missing or invalid + * @return {Object} instance of fabric.Circle + */ + fabric.Circle.fromElement = function(element, options) { + options || (options = { }); + var parsedAttributes = fabric.parseAttributes(element, fabric.Circle.ATTRIBUTE_NAMES); + if (!isValidRadius(parsedAttributes)) { + throw Error('value of `r` attribute is required and can not be negative'); + } + if ('left' in parsedAttributes) { + parsedAttributes.left -= (options.width / 2) || 0; + } + if ('top' in parsedAttributes) { + parsedAttributes.top -= (options.height / 2) || 0; + } + return new fabric.Circle(extend(parsedAttributes, options)); + }; + + /** + * @private + */ + function isValidRadius(attributes) { + return (('radius' in attributes) && (attributes.radius > 0)); + } + + /** + * Returns {@link fabric.Circle} instance from an object representation + * @static + * @method fabric.Circle.fromObject + * @param {Object} object Object to create an instance from + * @return {Object} Instance of fabric.Circle + */ + fabric.Circle.fromObject = function(object) { + return new fabric.Circle(object); + }; + +})(typeof exports != 'undefined' ? exports : this); +(function(global) { + + "use strict"; + + var fabric = global.fabric || (global.fabric = { }); + + if (fabric.Triangle) { + fabric.warn('fabric.Triangle is already defined'); + return; + } + + /** + * @class Triangle + * @extends fabric.Object + */ + fabric.Triangle = fabric.util.createClass(fabric.Object, /** @scope fabric.Triangle.prototype */ { + + /** + * @property + * @type String + */ + type: 'triangle', + + /** + * Constructor + * @method initialize + * @param options {Object} options object + * @return {Object} thisArg + */ + initialize: function(options) { + options = options || { }; + + this.callSuper('initialize', options); + + this.set('width', options.width || 100) + .set('height', options.height || 100); + }, + + /** + * @private + * @method _render + * @param ctx {CanvasRenderingContext2D} Context to render on + */ + _render: function(ctx) { + var widthBy2 = this.width / 2, + heightBy2 = this.height / 2; + + ctx.beginPath(); + ctx.moveTo(-widthBy2, heightBy2); + ctx.lineTo(0, -heightBy2); + ctx.lineTo(widthBy2, heightBy2); + ctx.closePath(); + + if (this.fill) { + ctx.fill(); + } + if (this.stroke) { + ctx.stroke(); + } + }, + + /** + * Returns complexity of an instance + * @method complexity + * @return {Number} complexity of this instance + */ + complexity: function() { + return 1; + }, + + /** + * Returns svg representation of an instance + * @method toSVG + * @return {string} svg representation of an instance + */ + toSVG: function() { + + var widthBy2 = this.width / 2, + heightBy2 = this.height / 2; + + var points = [ + -widthBy2 + " " + heightBy2, + "0 " + -heightBy2, + widthBy2 + " " + heightBy2 + ].join(","); + + return '<polygon ' + + 'points="' + points + '" ' + + 'style="' + this.getSvgStyles() + '" ' + + 'transform="' + this.getSvgTransform() + '" ' + + '/>'; + } + }); + + /** + * Returns fabric.Triangle instance from an object representation + * @static + * @method Canvas.Trangle.fromObject + * @param object {Object} object to create an instance from + * @return {Object} instance of Canvas.Triangle + */ + fabric.Triangle.fromObject = function(object) { + return new fabric.Triangle(object); + }; + +})(typeof exports != 'undefined' ? exports : this); +(function(global){ + + "use strict"; + + var fabric = global.fabric || (global.fabric = { }), + piBy2 = Math.PI * 2, + extend = fabric.util.object.extend; + + if (fabric.Ellipse) { + fabric.warn('fabric.Ellipse is already defined.'); + return; + } + + /** + * @class Ellipse + * @extends fabric.Object + */ + fabric.Ellipse = fabric.util.createClass(fabric.Object, /** @scope fabric.Ellipse.prototype */ { + + /** + * @property + * @type String + */ + type: 'ellipse', + + /** + * Constructor + * @method initialize + * @param {Object} [options] Options object + * @return {Object} thisArg + */ + initialize: function(options) { + options = options || { }; + + this.callSuper('initialize', options); + + this.set('rx', options.rx || 0); + this.set('ry', options.ry || 0); + + this.set('width', this.get('rx') * 2); + this.set('height', this.get('ry') * 2); + }, + + /** + * Returns object representation of an instance + * @method toObject + * @return {Object} object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + rx: this.get('rx'), + ry: this.get('ry') + }); + }, + + /** + * Returns svg representation of an instance + * @method toSVG + * @return {string} svg representation of an instance + */ + toSVG: function() { + return [ + '<ellipse ', + 'rx="', this.get('rx'), '" ', + 'ry="', this.get('ry'), '" ', + 'style="', this.getSvgStyles(), '" ', + 'transform="', this.getSvgTransform(), '" ', + '/>' + ].join(''); + }, + + /** + * Renders this instance on a given context + * @method render + * @param ctx {CanvasRenderingContext2D} context to render on + * @param noTransform {Boolean} context is not transformed when set to true + */ + render: function(ctx, noTransform) { + // do not use `get` for perf. reasons + if (this.rx === 0 || this.ry === 0) return; + return this.callSuper('render', ctx, noTransform); + }, + + /** + * @private + * @method _render + * @param ctx {CanvasRenderingContext2D} context to render on + */ + _render: function(ctx, noTransform) { + ctx.beginPath(); + ctx.save(); + ctx.globalAlpha *= this.opacity; + if (this.transformMatrix && this.group) { + ctx.translate(this.cx, this.cy); + } + ctx.transform(1, 0, 0, this.ry/this.rx, 0, 0); + ctx.arc(noTransform ? this.left : 0, noTransform ? this.top : 0, this.rx, 0, piBy2, false); + if (this.stroke) { + ctx.stroke(); + } + if (this.fill) { + ctx.fill(); + } + ctx.restore(); + }, + + /** + * Returns complexity of an instance + * @method complexity + * @return {Number} complexity + */ + complexity: function() { + return 1; + } + }); + + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Ellipse.fromElement}) + * @static + * @see http://www.w3.org/TR/SVG/shapes.html#EllipseElement + */ + fabric.Ellipse.ATTRIBUTE_NAMES = 'cx cy rx ry fill fill-opacity opacity stroke stroke-width transform'.split(' '); + + /** + * Returns {@link fabric.Ellipse} instance from an SVG element + * @static + * @method fabric.Ellipse.fromElement + * @param {SVGElement} element Element to parse + * @param {Object} [options] Options object + * @return {fabric.Ellipse} + */ + fabric.Ellipse.fromElement = function(element, options) { + options || (options = { }); + + var parsedAttributes = fabric.parseAttributes(element, fabric.Ellipse.ATTRIBUTE_NAMES); + var cx = parsedAttributes.left; + var cy = parsedAttributes.top; + + if ('left' in parsedAttributes) { + parsedAttributes.left -= (options.width / 2) || 0; + } + if ('top' in parsedAttributes) { + parsedAttributes.top -= (options.height / 2) || 0; + } + + var ellipse = new fabric.Ellipse(extend(parsedAttributes, options)); + + ellipse.cx = cx || 0; + ellipse.cy = cy || 0; + + return ellipse; + }; + + /** + * Returns fabric.Ellipse instance from an object representation + * @static + * @method fabric.Ellipse.fromObject + * @param {Object} object Object to create an instance from + * @return {fabric.Ellipse} + */ + fabric.Ellipse.fromObject = function(object) { + return new fabric.Ellipse(object); + }; + +})(typeof exports != 'undefined' ? exports : this); +(function(global) { + + "use strict"; + + var fabric = global.fabric || (global.fabric = { }); + + if (fabric.Rect) { + console.warn('fabric.Rect is already defined'); + return; + } + + /** + * @class Rect + * @extends fabric.Object + */ + fabric.Rect = fabric.util.createClass(fabric.Object, /** @scope fabric.Rect.prototype */ { + + /** + * @property + * @type String + */ + type: 'rect', + + /** + * @property + * @type Number + */ + rx: 0, + + /** + * @property + * @type Number + */ + ry: 0, + + /** + * Constructor + * @method initialize + * @param options {Object} options object + * @return {Object} thisArg + */ + initialize: function(options) { + this._initStateProperties(); + this.callSuper('initialize', options); + this._initRxRy(); + }, + + /** + * Creates `stateProperties` list on an instance, and adds `fabric.Rect` -specific ones to it + * (such as "rx", "ry", etc.) + * @private + * @method _initStateProperties + */ + _initStateProperties: function() { + this.stateProperties = this.stateProperties.concat(['rx', 'ry']); + }, + + /** + * @private + * @method _initRxRy + */ + _initRxRy: function() { + if (this.rx && !this.ry) { + this.ry = this.rx; + } + else if (this.ry && !this.rx) { + this.rx = this.ry; + } + }, + + /** + * @private + * @method _render + * @param ctx {CanvasRenderingContext2D} context to render on + */ + _render: function(ctx) { + var rx = this.rx || 0, + ry = this.ry || 0, + x = -this.width / 2, + y = -this.height / 2, + w = this.width, + h = this.height; + + ctx.beginPath(); + ctx.globalAlpha *= this.opacity; + + if (this.transformMatrix && this.group) { + ctx.translate( + this.width / 2 + this.x, + this.height / 2 + this.y); + } + if (!this.transformMatrix && this.group) { + ctx.translate( + -this.group.width / 2 + this.width / 2 + this.x, + -this.group.height / 2 + this.height / 2 + this.y); + } + + ctx.moveTo(x+rx, y); + ctx.lineTo(x+w-rx, y); + ctx.quadraticCurveTo(x+w, y, x+w, y+ry, x+w, y+ry); + ctx.lineTo(x+w, y+h-ry); + ctx.quadraticCurveTo(x+w,y+h,x+w-rx,y+h,x+w-rx,y+h); + ctx.lineTo(x+rx,y+h); + ctx.quadraticCurveTo(x,y+h,x,y+h-ry,x,y+h-ry); + ctx.lineTo(x,y+ry); + ctx.quadraticCurveTo(x,y,x+rx,y,x+rx,y); + ctx.closePath(); + + if (this.fill) { + ctx.fill(); + } + + if (this.strokeDashArray) { + this._renderDashedStroke(ctx); + } + else if (this.stroke) { + ctx.stroke(); + } + }, + + // since our coordinate system differs from that of SVG + _normalizeLeftTopProperties: function(parsedAttributes) { + if (parsedAttributes.left) { + this.set('left', parsedAttributes.left + this.getWidth() / 2); + } + this.set('x', parsedAttributes.left || 0); + if (parsedAttributes.top) { + this.set('top', parsedAttributes.top + this.getHeight() / 2); + } + this.set('y', parsedAttributes.top || 0); + return this; + }, + + /** + * @method complexity + * @return {Number} complexity + */ + complexity: function() { + return 1; + }, + + /** + * Returns object representation of an instance + * @method toObject + * @return {Object} object representation of an instance + */ + toObject: function() { + return fabric.util.object.extend(this.callSuper('toObject'), { + rx: this.get('rx') || 0, + ry: this.get('ry') || 0 + }); + }, + + /** + * Returns svg representation of an instance + * @method toSVG + * @return {string} svg representation of an instance + */ + toSVG: function() { + return '<rect ' + + 'x="' + (-1 * this.width / 2) + '" y="' + (-1 * this.height / 2) + '" ' + + 'rx="' + this.get('rx') + '" ry="' + this.get('ry') + '" ' + + 'width="' + this.width + '" height="' + this.height + '" ' + + 'style="' + this.getSvgStyles() + '" ' + + 'transform="' + this.getSvgTransform() + '" ' + + '/>'; + } + }); + + // TODO (kangax): implement rounded rectangles (both parsing and rendering) + + /** + * List of attribute names to account for when parsing SVG element (used by `fabric.Rect.fromElement`) + * @static + */ + fabric.Rect.ATTRIBUTE_NAMES = 'x y width height rx ry fill fill-opacity opacity stroke stroke-width transform'.split(' '); + + /** + * @private + */ + function _setDefaultLeftTopValues(attributes) { + attributes.left = attributes.left || 0; + attributes.top = attributes.top || 0; + return attributes; + } + + /** + * Returns fabric.Rect instance from an SVG element + * @static + * @method fabric.Rect.fromElement + * @param element {SVGElement} element to parse + * @param options {Object} options object + * @return {fabric.Rect} instance of fabric.Rect + */ + fabric.Rect.fromElement = function(element, options) { + if (!element) { + return null; + } + + var parsedAttributes = fabric.parseAttributes(element, fabric.Rect.ATTRIBUTE_NAMES); + parsedAttributes = _setDefaultLeftTopValues(parsedAttributes); + + var rect = new fabric.Rect(fabric.util.object.extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); + rect._normalizeLeftTopProperties(parsedAttributes); + + return rect; + }; + + /** + * Returns fabric.Rect instance from an object representation + * @static + * @method fabric.Rect.fromObject + * @param object {Object} object to create an instance from + * @return {Object} instance of fabric.Rect + */ + fabric.Rect.fromObject = function(object) { + return new fabric.Rect(object); + }; + +})(typeof exports != 'undefined' ? exports : this); +(function(global) { + + "use strict"; + + var fabric = global.fabric || (global.fabric = { }), + toFixed = fabric.util.toFixed; + + if (fabric.Polyline) { + fabric.warn('fabric.Polyline is already defined'); + return; + } + + /** + * @class Polyline + * @extends fabric.Object + */ + fabric.Polyline = fabric.util.createClass(fabric.Object, /** @scope fabric.Polyline.prototype */ { + + /** + * @property + * @type String + */ + type: 'polyline', + + /** + * Constructor + * @method initialize + * @param {Array} points array of points + * @param {Object} [options] Options object + * @return {Object} thisArg + */ + initialize: function(points, options) { + options = options || { }; + this.set('points', points); + this.callSuper('initialize', options); + this._calcDimensions(); + }, + + /** + * @private + * @method _calcDimensions + */ + _calcDimensions: function() { + return fabric.Polygon.prototype._calcDimensions.call(this); + }, + + /** + * Returns object representation of an instance + * @method toObject + * @return {Object} Object representation of an instance + */ + toObject: function() { + return fabric.Polygon.prototype.toObject.call(this); + }, + + /** + * Returns svg representation of an instance + * @method toSVG + * @return {string} svg representation of an instance + */ + toSVG: function() { + var points = []; + for (var i = 0, len = this.points.length; i < len; i++) { + points.push(toFixed(this.points[i].x, 2), ',', toFixed(this.points[i].y, 2), ' '); + } + + return [ + '<polyline ', + 'points="', points.join(''), '" ', + 'style="', this.getSvgStyles(), '" ', + 'transform="', this.getSvgTransform(), '" ', + '/>' + ].join(''); + }, + + /** + * @private + * @method _render + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + var point; + ctx.beginPath(); + ctx.moveTo(this.points[0].x, this.points[0].y); + for (var i = 0, len = this.points.length; i < len; i++) { + point = this.points[i]; + ctx.lineTo(point.x, point.y); + } + if (this.fill) { + ctx.fill(); + } + if (this.stroke) { + ctx.stroke(); + } + }, + + /** + * Returns complexity of an instance + * @method complexity + * @return {Number} complexity + */ + complexity: function() { + return this.get('points').length; + } + }); + + /** + * List of attribute names to account for when parsing SVG element (used by `fabric.Polyline.fromElement`) + * @static + * @see: http://www.w3.org/TR/SVG/shapes.html#PolylineElement + */ + fabric.Polyline.ATTRIBUTE_NAMES = 'fill fill-opacity opacity stroke stroke-width transform'.split(' '); + + /** + * Returns fabric.Polyline instance from an SVG element + * @static + * @method fabric.Polyline.fromElement + * @param {SVGElement} element Element to parse + * @param {Object} [options] Options object + * @return {Object} instance of fabric.Polyline + */ + fabric.Polyline.fromElement = function(element, options) { + if (!element) { + return null; + } + options || (options = { }); + + var points = fabric.parsePointsAttribute(element.getAttribute('points')), + parsedAttributes = fabric.parseAttributes(element, fabric.Polyline.ATTRIBUTE_NAMES); + + for (var i = 0, len = points.length; i < len; i++) { + // normalize coordinates, according to containing box (dimensions of which are passed via `options`) + points[i].x -= (options.width / 2) || 0; + points[i].y -= (options.height / 2) || 0; + } + + return new fabric.Polyline(points, fabric.util.object.extend(parsedAttributes, options)); + }; + + /** + * Returns fabric.Polyline instance from an object representation + * @static + * @method fabric.Polyline.fromObject + * @param {Object} [object] Object to create an instance from + * @return {fabric.Polyline} + */ + fabric.Polyline.fromObject = function(object) { + var points = object.points; + return new fabric.Polyline(points, object); + }; + +})(typeof exports != 'undefined' ? exports : this); +(function(global) { + + "use strict"; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + min = fabric.util.array.min, + max = fabric.util.array.max, + toFixed = fabric.util.toFixed; + + if (fabric.Polygon) { + fabric.warn('fabric.Polygon is already defined'); + return; + } + + function byX(p) { return p.x; } + function byY(p) { return p.y; } + + /** + * @class Polygon + * @extends fabric.Object + */ + fabric.Polygon = fabric.util.createClass(fabric.Object, /** @scope fabric.Polygon.prototype */ { + + /** + * @property + * @type String + */ + type: 'polygon', + + /** + * Constructor + * @method initialize + * @param {Array} points Array of points + * @param {Object} options Options object + * @return {fabric.Polygon} thisArg + */ + initialize: function(points, options) { + options = options || { }; + this.points = points; + this.callSuper('initialize', options); + this._calcDimensions(); + }, + + /** + * @private + * @method _calcDimensions + */ + _calcDimensions: function() { + + var points = this.points, + minX = min(points, 'x'), + minY = min(points, 'y'), + maxX = max(points, 'x'), + maxY = max(points, 'y'); + + this.width = (maxX - minX) || 1; + this.height = (maxY - minY) || 1; + + this.minX = minX; + this.minY = minY; + }, + + /** + * Returns object representation of an instance + * @method toObject + * @return {Object} object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + points: this.points.concat() + }); + }, + + /** + * Returns svg representation of an instance + * @method toSVG + * @return {string} svg representation of an instance + */ + toSVG: function() { + var points = []; + for (var i = 0, len = this.points.length; i < len; i++) { + points.push(toFixed(this.points[i].x, 2), ',', toFixed(this.points[i].y, 2), ' '); + } + + return [ + '<polygon ', + 'points="', points.join(''), '" ', + 'style="', this.getSvgStyles(), '" ', + 'transform="', this.getSvgTransform(), '" ', + '/>' + ].join(''); + }, + + /** + * @private + * @method _render + * @param ctx {CanvasRenderingContext2D} context to render on + */ + _render: function(ctx) { + var point; + ctx.beginPath(); + ctx.moveTo(this.points[0].x, this.points[0].y); + for (var i = 0, len = this.points.length; i < len; i++) { + point = this.points[i]; + ctx.lineTo(point.x, point.y); + } + if (this.fill) { + ctx.fill(); + } + if (this.stroke) { + ctx.closePath(); + ctx.stroke(); + } + }, + + /** + * Returns complexity of an instance + * @method complexity + * @return {Number} complexity of this instance + */ + complexity: function() { + return this.points.length; + } + }); + + /** + * List of attribute names to account for when parsing SVG element (used by `fabric.Polygon.fromElement`) + * @static + * @see: http://www.w3.org/TR/SVG/shapes.html#PolygonElement + */ + fabric.Polygon.ATTRIBUTE_NAMES = 'fill fill-opacity opacity stroke stroke-width transform'.split(' '); + + /** + * Returns fabric.Polygon instance from an SVG element + * @static + * @method fabric.Polygon.fromElement + * @param {SVGElement} element Element to parse + * @param {Object} options Options object + * @return {fabric.Polygon} + */ + fabric.Polygon.fromElement = function(element, options) { + if (!element) { + return null; + } + options || (options = { }); + + var points = fabric.parsePointsAttribute(element.getAttribute('points')), + parsedAttributes = fabric.parseAttributes(element, fabric.Polygon.ATTRIBUTE_NAMES); + + for (var i = 0, len = points.length; i < len; i++) { + // normalize coordinates, according to containing box (dimensions of which are passed via `options`) + points[i].x -= (options.width / 2) || 0; + points[i].y -= (options.height / 2) || 0; + } + + return new fabric.Polygon(points, extend(parsedAttributes, options)); + }; + + /** + * Returns fabric.Polygon instance from an object representation + * @static + * @method fabric.Polygon.fromObject + * @param {Object} object Object to create an instance from + * @return {fabric.Polygon} + */ + fabric.Polygon.fromObject = function(object) { + return new fabric.Polygon(object.points, object); + }; + +})(typeof exports != 'undefined' ? exports : this); +(function(global) { + + var commandLengths = { + m: 2, + l: 2, + h: 1, + v: 1, + c: 6, + s: 4, + q: 4, + t: 2, + a: 7 + }; + + function drawArc(ctx, x, y, coords) { + var rx = coords[0]; + var ry = coords[1]; + var rot = coords[2]; + var large = coords[3]; + var sweep = coords[4]; + var ex = coords[5]; + var ey = coords[6]; + var segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y); + for (var i=0; i<segs.length; i++) { + var bez = segmentToBezier.apply(this, segs[i]); + ctx.bezierCurveTo.apply(ctx, bez); + } + } + + var arcToSegmentsCache = { }, + segmentToBezierCache = { }, + _join = Array.prototype.join, + argsString; + + // Copied from Inkscape svgtopdf, thanks! + function arcToSegments(x, y, rx, ry, large, sweep, rotateX, ox, oy) { + argsString = _join.call(arguments); + if (arcToSegmentsCache[argsString]) { + return arcToSegmentsCache[argsString]; + } + + var th = rotateX * (Math.PI/180); + var sin_th = Math.sin(th); + var cos_th = Math.cos(th); + rx = Math.abs(rx); + ry = Math.abs(ry); + var px = cos_th * (ox - x) * 0.5 + sin_th * (oy - y) * 0.5; + var py = cos_th * (oy - y) * 0.5 - sin_th * (ox - x) * 0.5; + var pl = (px*px) / (rx*rx) + (py*py) / (ry*ry); + if (pl > 1) { + pl = Math.sqrt(pl); + rx *= pl; + ry *= pl; + } + + var a00 = cos_th / rx; + var a01 = sin_th / rx; + var a10 = (-sin_th) / ry; + var a11 = (cos_th) / ry; + var x0 = a00 * ox + a01 * oy; + var y0 = a10 * ox + a11 * oy; + var x1 = a00 * x + a01 * y; + var y1 = a10 * x + a11 * y; + + var d = (x1-x0) * (x1-x0) + (y1-y0) * (y1-y0); + var sfactor_sq = 1 / d - 0.25; + if (sfactor_sq < 0) sfactor_sq = 0; + var sfactor = Math.sqrt(sfactor_sq); + if (sweep == large) sfactor = -sfactor; + var xc = 0.5 * (x0 + x1) - sfactor * (y1-y0); + var yc = 0.5 * (y0 + y1) + sfactor * (x1-x0); + + var th0 = Math.atan2(y0-yc, x0-xc); + var th1 = Math.atan2(y1-yc, x1-xc); + + var th_arc = th1-th0; + if (th_arc < 0 && sweep == 1){ + th_arc += 2*Math.PI; + } else if (th_arc > 0 && sweep == 0) { + th_arc -= 2 * Math.PI; + } + + var segments = Math.ceil(Math.abs(th_arc / (Math.PI * 0.5 + 0.001))); + var result = []; + for (var i=0; i<segments; i++) { + var th2 = th0 + i * th_arc / segments; + var th3 = th0 + (i+1) * th_arc / segments; + result[i] = [xc, yc, th2, th3, rx, ry, sin_th, cos_th]; + } + + return (arcToSegmentsCache[argsString] = result); + } + + function segmentToBezier(cx, cy, th0, th1, rx, ry, sin_th, cos_th) { + argsString = _join.call(arguments); + if (segmentToBezierCache[argsString]) { + return segmentToBezierCache[argsString]; + } + + var a00 = cos_th * rx; + var a01 = -sin_th * ry; + var a10 = sin_th * rx; + var a11 = cos_th * ry; + + var th_half = 0.5 * (th1 - th0); + var t = (8/3) * Math.sin(th_half * 0.5) * Math.sin(th_half * 0.5) / Math.sin(th_half); + var x1 = cx + Math.cos(th0) - t * Math.sin(th0); + var y1 = cy + Math.sin(th0) + t * Math.cos(th0); + var x3 = cx + Math.cos(th1); + var y3 = cy + Math.sin(th1); + var x2 = x3 + t * Math.sin(th1); + var y2 = y3 - t * Math.cos(th1); + + return (segmentToBezierCache[argsString] = [ + a00 * x1 + a01 * y1, a10 * x1 + a11 * y1, + a00 * x2 + a01 * y2, a10 * x2 + a11 * y2, + a00 * x3 + a01 * y3, a10 * x3 + a11 * y3 + ]); + } + + "use strict"; + + var fabric = global.fabric || (global.fabric = { }), + min = fabric.util.array.min, + max = fabric.util.array.max, + extend = fabric.util.object.extend, + _toString = Object.prototype.toString; + + if (fabric.Path) { + fabric.warn('fabric.Path is already defined'); + return; + } + if (!fabric.Object) { + fabric.warn('fabric.Path requires fabric.Object'); + return; + } + + /** + * @private + */ + function getX(item) { + if (item[0] === 'H') { + return item[1]; + } + return item[item.length - 2]; + } + + /** + * @private + */ + function getY(item) { + if (item[0] === 'V') { + return item[1]; + } + return item[item.length - 1]; + } + + /** + * @class Path + * @extends fabric.Object + */ + fabric.Path = fabric.util.createClass(fabric.Object, /** @scope fabric.Path.prototype */ { + + /** + * @property + * @type String + */ + type: 'path', + + /** + * Constructor + * @method initialize + * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens) + * @param {Object} [options] Options object + */ + initialize: function(path, options) { + options = options || { }; + + this.setOptions(options); + + if (!path) { + throw Error('`path` argument is required'); + } + + var fromArray = _toString.call(path) === '[object Array]'; + + this.path = fromArray + ? path + // one of commands (m,M,l,L,q,Q,c,C,etc.) followed by non-command characters (i.e. command values) + : path.match && path.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi); + + if (!this.path) return; + + if (!fromArray) { + this._initializeFromString(options); + } + + if (options.sourcePath) { + this.setSourcePath(options.sourcePath); + } + }, + + /** + * @private + * @method _initializeFromString + */ + _initializeFromString: function(options) { + var isWidthSet = 'width' in options, + isHeightSet = 'height' in options; + + this.path = this._parsePath(); + + if (!isWidthSet || !isHeightSet) { + extend(this, this._parseDimensions()); + if (isWidthSet) { + this.width = options.width; + } + if (isHeightSet) { + this.height = options.height; + } + } + }, + + /** + * @private + * @method _render + */ + _render: function(ctx) { + var current, // current instruction + previous = null, + x = 0, // current x + y = 0, // current y + controlX = 0, // current control point x + controlY = 0, // current control point y + tempX, + tempY, + tempControlX, + tempControlY, + l = -(this.width / 2), + t = -(this.height / 2); + + for (var i = 0, len = this.path.length; i < len; ++i) { + + current = this.path[i]; + + switch (current[0]) { // first letter + + case 'l': // lineto, relative + x += current[1]; + y += current[2]; + ctx.lineTo(x + l, y + t); + break; + + case 'L': // lineto, absolute + x = current[1]; + y = current[2]; + ctx.lineTo(x + l, y + t); + break; + + case 'h': // horizontal lineto, relative + x += current[1]; + ctx.lineTo(x + l, y + t); + break; + + case 'H': // horizontal lineto, absolute + x = current[1]; + ctx.lineTo(x + l, y + t); + break; + + case 'v': // vertical lineto, relative + y += current[1]; + ctx.lineTo(x + l, y + t); + break; + + case 'V': // verical lineto, absolute + y = current[1]; + ctx.lineTo(x + l, y + t); + break; + + case 'm': // moveTo, relative + x += current[1]; + y += current[2]; + ctx.moveTo(x + l, y + t); + break; + + case 'M': // moveTo, absolute + x = current[1]; + y = current[2]; + ctx.moveTo(x + l, y + t); + break; + + case 'c': // bezierCurveTo, relative + tempX = x + current[5]; + tempY = y + current[6]; + controlX = x + current[3]; + controlY = y + current[4]; + ctx.bezierCurveTo( + x + current[1] + l, // x1 + y + current[2] + t, // y1 + controlX + l, // x2 + controlY + t, // y2 + tempX + l, + tempY + t + ); + x = tempX; + y = tempY; + break; + + case 'C': // bezierCurveTo, absolute + x = current[5]; + y = current[6]; + controlX = current[3]; + controlY = current[4]; + ctx.bezierCurveTo( + current[1] + l, + current[2] + t, + controlX + l, + controlY + t, + x + l, + y + t + ); + break; + + case 's': // shorthand cubic bezierCurveTo, relative + // transform to absolute x,y + tempX = x + current[3]; + tempY = y + current[4]; + // calculate reflection of previous control points + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + ctx.bezierCurveTo( + controlX + l, + controlY + t, + x + current[1] + l, + y + current[2] + t, + tempX + l, + tempY + t + ); + // set control point to 2nd one of this command + // "... the first control point is assumed to be the reflection of the second control point on the previous command relative to the current point." + controlX = x + current[1]; + controlY = y + current[2]; + + x = tempX; + y = tempY; + break; + + case 'S': // shorthand cubic bezierCurveTo, absolute + tempX = current[3]; + tempY = current[4]; + // calculate reflection of previous control points + controlX = 2*x - controlX; + controlY = 2*y - controlY; + ctx.bezierCurveTo( + controlX + l, + controlY + t, + current[1] + l, + current[2] + t, + tempX + l, + tempY + t + ); + x = tempX; + y = tempY; + + // set control point to 2nd one of this command + // "... the first control point is assumed to be the reflection of the second control point on the previous command relative to the current point." + controlX = current[1]; + controlY = current[2]; + + break; + + case 'q': // quadraticCurveTo, relative + // transform to absolute x,y + tempX = x + current[3]; + tempY = y + current[4]; + + controlX = x + current[1]; + controlY = y + current[2]; + + ctx.quadraticCurveTo( + controlX + l, + controlY + t, + tempX + l, + tempY + t + ); + x = tempX; + y = tempY; + break; + + case 'Q': // quadraticCurveTo, absolute + tempX = current[3]; + tempY = current[4]; + + ctx.quadraticCurveTo( + current[1] + l, + current[2] + t, + tempX + l, + tempY + t + ); + x = tempX; + y = tempY; + controlX = current[1]; + controlY = current[2]; + break; + + case 't': // shorthand quadraticCurveTo, relative + + // transform to absolute x,y + tempX = x + current[1]; + tempY = y + current[2]; + + + if (previous[0].match(/[QqTt]/) === null) { + // If there is no previous command or if the previous command was not a Q, q, T or t, + // assume the control point is coincident with the current point + controlX = x; + controlY = y; + } + else if (previous[0] === 't') { + // calculate reflection of previous control points for t + controlX = 2 * x - tempControlX; + controlY = 2 * y - tempControlY; + } + else if (previous[0] === 'q') { + // calculate reflection of previous control points for q + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + } + + tempControlX = controlX; + tempControlY = controlY; + + ctx.quadraticCurveTo( + controlX + l, + controlY + t, + tempX + l, + tempY + t + ); + x = tempX; + y = tempY; + controlX = x + current[1]; + controlY = y + current[2]; + break; + + case 'T': + tempX = current[1]; + tempY = current[2]; + + // calculate reflection of previous control points + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + ctx.quadraticCurveTo( + controlX + l, + controlY + t, + tempX + l, + tempY + t + ); + x = tempX; + y = tempY; + break; + + case 'a': + // TODO: optimize this + drawArc(ctx, x + l, y + t, [ + current[1], + current[2], + current[3], + current[4], + current[5], + current[6] + x + l, + current[7] + y + t + ]); + x += current[6]; + y += current[7]; + break; + + case 'A': + // TODO: optimize this + drawArc(ctx, x + l, y + t, [ + current[1], + current[2], + current[3], + current[4], + current[5], + current[6] + l, + current[7] + t + ]); + x = current[6]; + y = current[7]; + break; + + case 'z': + case 'Z': + ctx.closePath(); + break; + } + previous = current; + } + }, + + /** + * Renders path on a specified context + * @method render + * @param {CanvasRenderingContext2D} ctx context to render path on + * @param {Boolean} noTransform When true, context is not transformed + */ + render: function(ctx, noTransform) { + ctx.save(); + var m = this.transformMatrix; + if (m) { + ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + if (!noTransform) { + this.transform(ctx); + } + // ctx.globalCompositeOperation = this.fillRule; + + if (this.overlayFill) { + ctx.fillStyle = this.overlayFill; + } + else if (this.fill) { + ctx.fillStyle = this.fill.toLiveGradient + ? this.fill.toLiveGradient(ctx) + : this.fill; + } + + if (this.stroke) { + ctx.strokeStyle = this.stroke; + } + ctx.beginPath(); + + this._render(ctx); + + if (this.fill) { + ctx.fill(); + } + if (this.stroke) { + ctx.strokeStyle = this.stroke; + ctx.lineWidth = this.strokeWidth; + ctx.lineCap = ctx.lineJoin = 'round'; + ctx.stroke(); + } + if (!noTransform && this.active) { + this.drawBorders(ctx); + this.hideCorners || this.drawCorners(ctx); + } + ctx.restore(); + }, + + /** + * Returns string representation of an instance + * @method toString + * @return {String} string representation of an instance + */ + toString: function() { + return '#<fabric.Path (' + this.complexity() + + '): { "top": ' + this.top + ', "left": ' + this.left + ' }>'; + }, + + /** + * Returns object representation of an instance + * @method toObject + * @return {Object} + */ + toObject: function() { + var o = extend(this.callSuper('toObject'), { + path: this.path + }); + if (this.sourcePath) { + o.sourcePath = this.sourcePath; + } + if (this.transformMatrix) { + o.transformMatrix = this.transformMatrix; + } + return o; + }, + + /** + * Returns dataless object representation of an instance + * @method toDatalessObject + * @return {Object} + */ + toDatalessObject: function() { + var o = this.toObject(); + if (this.sourcePath) { + o.path = this.sourcePath; + } + delete o.sourcePath; + return o; + }, + + /** + * Returns svg representation of an instance + * @method toSVG + * @return {string} svg representation of an instance + */ + toSVG: function() { + var chunks = []; + for (var i = 0, len = this.path.length; i < len; i++) { + chunks.push(this.path[i].join(' ')); + } + var path = chunks.join(' '); + + return [ + '<g transform="', this.getSvgTransform(), '">', + '<path ', + 'width="', this.width, '" height="', this.height, '" ', + 'd="', path, '" ', + 'style="', this.getSvgStyles(), '" ', + 'transform="translate(', (-this.width / 2), ' ', (-this.height/2), ')" />', + '</g>' + ].join(''); + }, + + /** + * Returns number representation of an instance complexity + * @method complexity + * @return {Number} complexity + */ + complexity: function() { + return this.path.length; + }, + + /** + * @private + * @method _parsePath + */ + _parsePath: function() { + var result = [ ], + currentPath, + chunks, + parsed; + + for (var i = 0, j, chunksParsed, len = this.path.length; i < len; i++) { + currentPath = this.path[i]; + chunks = currentPath.slice(1).trim().replace(/(\d)-/g, '$1###-').split(/\s|,|###/); + chunksParsed = [ currentPath.charAt(0) ]; + + for (var j = 0, jlen = chunks.length; j < jlen; j++) { + parsed = parseFloat(chunks[j]); + if (!isNaN(parsed)) { + chunksParsed.push(parsed); + } + } + + var command = chunksParsed[0].toLowerCase(), + commandLength = commandLengths[command]; + + if (chunksParsed.length - 1 > commandLength) { + for (var k = 1, klen = chunksParsed.length; k < klen; k += commandLength) { + result.push([ chunksParsed[0] ].concat(chunksParsed.slice(k, k + commandLength))); + } + } + else { + result.push(chunksParsed); + } + } + + return result; + }, + + /** + * @method _parseDimensions + */ + _parseDimensions: function() { + var aX = [], + aY = [], + previousX, + previousY, + isLowerCase = false, + x, + y; + + this.path.forEach(function(item, i) { + if (item[0] !== 'H') { + previousX = (i === 0) ? getX(item) : getX(this.path[i-1]); + } + if (item[0] !== 'V') { + previousY = (i === 0) ? getY(item) : getY(this.path[i-1]); + } + + // lowercased letter denotes relative position; + // transform to absolute + if (item[0] === item[0].toLowerCase()) { + isLowerCase = true; + } + + // last 2 items in an array of coordinates are the actualy x/y (except H/V); + // collect them + + // TODO (kangax): support relative h/v commands + + x = isLowerCase + ? previousX + getX(item) + : item[0] === 'V' + ? previousX + : getX(item); + + y = isLowerCase + ? previousY + getY(item) + : item[0] === 'H' + ? previousY + : getY(item); + + var val = parseInt(x, 10); + if (!isNaN(val)) aX.push(val); + + val = parseInt(y, 10); + if (!isNaN(val)) aY.push(val); + + }, this); + + var minX = min(aX), + minY = min(aY), + deltaX = 0, + deltaY = 0; + + var o = { + top: minY - deltaY, + left: minX - deltaX, + bottom: max(aY) - deltaY, + right: max(aX) - deltaX + }; + + o.width = o.right - o.left; + o.height = o.bottom - o.top; + + return o; + } + }); + + /** + * Creates an instance of fabric.Path from an object + * @static + * @method fabric.Path.fromObject + * @return {fabric.Path} Instance of fabric.Path + */ + fabric.Path.fromObject = function(object) { + return new fabric.Path(object.path, object); + }; + + /** + * List of attribute names to account for when parsing SVG element (used by `fabric.Path.fromElement`) + * @static + * @see http://www.w3.org/TR/SVG/paths.html#PathElement + */ + fabric.Path.ATTRIBUTE_NAMES = 'd fill fill-opacity opacity fill-rule stroke stroke-width transform'.split(' '); + + /** + * Creates an instance of fabric.Path from an SVG <path> element + * @static + * @method fabric.Path.fromElement + * @param {SVGElement} element to parse + * @param {Object} options object + * @return {fabric.Path} Instance of fabric.Path + */ + fabric.Path.fromElement = function(element, options) { + var parsedAttributes = fabric.parseAttributes(element, fabric.Path.ATTRIBUTE_NAMES); + return new fabric.Path(parsedAttributes.d, extend(parsedAttributes, options)); + }; + +})(typeof exports != 'undefined' ? exports : this); +(function(global) { + + "use strict"; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + invoke = fabric.util.array.invoke, + parentSet = fabric.Object.prototype.set, + parentToObject = fabric.Object.prototype.toObject, + camelize = fabric.util.string.camelize, + capitalize = fabric.util.string.capitalize; + + if (fabric.PathGroup) { + fabric.warn('fabric.PathGroup is already defined'); + return; + } + + /** + * @class PathGroup + * @extends fabric.Path + */ + fabric.PathGroup = fabric.util.createClass(fabric.Path, /** @scope fabric.PathGroup.prototype */ { + + /** + * @property + * @type String + */ + type: 'path-group', + + /** + * @property + * @type String + */ + fill: '', + + /** + * @property + * @type Boolean + */ + forceFillOverwrite: false, + + /** + * Constructor + * @method initialize + * @param {Array} paths + * @param {Object} [options] Options object + * @return {fabric.PathGroup} thisArg + */ + initialize: function(paths, options) { + + options = options || { }; + this.paths = paths || [ ]; + + for (var i = this.paths.length; i--; ) { + this.paths[i].group = this; + } + + this.setOptions(options); + this.setCoords(); + + if (options.sourcePath) { + this.setSourcePath(options.sourcePath); + } + }, + + /** + * Renders this group on a specified context + * @method render + * @param {CanvasRenderingContext2D} ctx Context to render this instance on + */ + render: function(ctx) { + ctx.save(); + + var m = this.transformMatrix; + if (m) { + ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + + this.transform(ctx); + for (var i = 0, l = this.paths.length; i < l; ++i) { + this.paths[i].render(ctx, true); + } + if (this.active) { + this.drawBorders(ctx); + this.hideCorners || this.drawCorners(ctx); + } + ctx.restore(); + }, + + /** + * Sets certain property to a certain value + * @method _set + * @param {String} prop + * @param {Any} value + * @return {fabric.PathGroup} thisArg + */ + _set: function(prop, value) { + + if ((prop === 'fill' || prop === 'overlayFill') && value && this.isSameColor()) { + var i = this.paths.length; + while (i--) { + this.paths[i]._set(prop, value); + } + } + + return this.callSuper('_set', prop, value); + }, + + /** + * Returns object representation of this path group + * @method toObject + * @return {Object} object representation of an instance + */ + toObject: function() { + return extend(parentToObject.call(this), { + paths: invoke(this.getObjects(), 'clone'), + sourcePath: this.sourcePath + }); + }, + + /** + * Returns dataless object representation of this path group + * @method toDatalessObject + * @return {Object} dataless object representation of an instance + */ + toDatalessObject: function() { + var o = this.toObject(); + if (this.sourcePath) { + o.paths = this.sourcePath; + } + return o; + }, + + /** + * Returns svg representation of an instance + * @method toSVG + * @return {string} svg representation of an instance + */ + toSVG: function() { + var objects = this.getObjects(); + var markup = [ + '<g ', + 'width="', this.width, '" ', + 'height="', this.height, '" ', + 'style="', this.getSvgStyles(), '" ', + 'transform="', this.getSvgTransform(), '" ', + '>' + ]; + + for (var i = 0, len = objects.length; i < len; i++) { + markup.push(objects[i].toSVG()); + } + markup.push('</g>'); + + return markup.join(''); + }, + + /** + * Returns a string representation of this path group + * @method toString + * @return {String} string representation of an object + */ + toString: function() { + return '#<fabric.PathGroup (' + this.complexity() + + '): { top: ' + this.top + ', left: ' + this.left + ' }>'; + }, + + /** + * Returns true if all paths in this group are of same color + * @method isSameColor + * @return {Boolean} true if all paths are of the same color (`fill`) + */ + isSameColor: function() { + var firstPathFill = this.getObjects()[0].get('fill'); + return this.getObjects().every(function(path) { + return path.get('fill') === firstPathFill; + }); + }, + + /** + * Returns number representation of object's complexity + * @method complexity + * @return {Number} complexity + */ + complexity: function() { + return this.paths.reduce(function(total, path) { + return total + ((path && path.complexity) ? path.complexity() : 0); + }, 0); + }, + + /** + * Makes path group grayscale + * @method toGrayscale + * @return {fabric.PathGroup} thisArg + */ + toGrayscale: function() { + var i = this.paths.length; + while (i--) { + this.paths[i].toGrayscale(); + } + return this; + }, + + /** + * Returns all paths in this path group + * @method getObjects + * @return {Array} array of path objects included in this path group + */ + getObjects: function() { + return this.paths; + } + }); + + /** + * @private + * @method instantiatePaths + */ + function instantiatePaths(paths) { + for (var i = 0, len = paths.length; i < len; i++) { + if (!(paths[i] instanceof fabric.Object)) { + var klassName = camelize(capitalize(paths[i].type)); + paths[i] = fabric[klassName].fromObject(paths[i]); + } + } + return paths; + } + + /** + * Creates fabric.Triangle instance from an object representation + * @static + * @method fabric.PathGroup.fromObject + * @param {Object} object + * @return {fabric.PathGroup} + */ + fabric.PathGroup.fromObject = function(object) { + var paths = instantiatePaths(object.paths); + return new fabric.PathGroup(paths, object); + }; + +})(typeof exports != 'undefined' ? exports : this); +(function(global){ + + "use strict"; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + min = fabric.util.array.min, + max = fabric.util.array.max, + invoke = fabric.util.array.invoke, + removeFromArray = fabric.util.removeFromArray; + + if (fabric.Group) { + return; + } + + /** + * @class Group + * @extends fabric.Object + */ + fabric.Group = fabric.util.createClass(fabric.Object, /** @scope fabric.Group.prototype */ { + + /** + * @property + * @type String + */ + type: 'group', + + /** + * Constructor + * @method initialized + * @param {Object} objects Group objects + * @param {Object} [options] Options object + * @return {Object} thisArg + */ + initialize: function(objects, options) { + this.objects = objects || []; + this.originalState = { }; + + this.callSuper('initialize'); + + this._calcBounds(); + this._updateObjectsCoords(); + + if (options) { + extend(this, options); + } + this._setOpacityIfSame(); + + // group is active by default + this.setCoords(true); + this.saveCoords(); + + //this.activateAllObjects(); + }, + + /** + * @private + * @method _updateObjectsCoords + */ + _updateObjectsCoords: function() { + var groupDeltaX = this.left, + groupDeltaY = this.top; + + this.forEachObject(function(object) { + + var objectLeft = object.get('left'), + objectTop = object.get('top'); + + object.set('originalLeft', objectLeft); + object.set('originalTop', objectTop); + + object.set('left', objectLeft - groupDeltaX); + object.set('top', objectTop - groupDeltaY); + + object.setCoords(); + + // do not display corners of objects enclosed in a group + object.hideCorners = true; + }, this); + }, + + /** + * Returns string represenation of a group + * @method toString + * @return {String} + */ + toString: function() { + return '#<fabric.Group: (' + this.complexity() + ')>'; + }, + + /** + * Returns an array of all objects in this group + * @method getObjects + * @return {Array} group objects + */ + getObjects: function() { + return this.objects; + }, + + /** + * Adds an object to a group; Then recalculates group's dimension, position. + * @method addWithUpdate + * @param {Object} object + * @return {fabric.Group} thisArg + * @chainable + */ + addWithUpdate: function(object) { + this._restoreObjectsState(); + this.objects.push(object); + this._calcBounds(); + this._updateObjectsCoords(); + return this; + }, + + /** + * Removes an object from a group; Then recalculates group's dimension, position. + * @method removeWithUpdate + * @param {Object} object + * @return {fabric.Group} thisArg + * @chainable + */ + removeWithUpdate: function(object) { + this._restoreObjectsState(); + removeFromArray(this.objects, object); + object.setActive(false); + this._calcBounds(); + this._updateObjectsCoords(); + return this; + }, + + /** + * Adds an object to a group + * @method add + * @param {Object} object + * @return {fabric.Group} thisArg + * @chainable + */ + add: function(object) { + this.objects.push(object); + return this; + }, + + /** + * Removes an object from a group + * @method remove + * @param {Object} object + * @return {fabric.Group} thisArg + * @chainable + */ + remove: function(object) { + removeFromArray(this.objects, object); + return this; + }, + + /** + * Returns a size of a group (i.e: length of an array containing its objects) + * @return {Number} Group size + */ + size: function() { + return this.getObjects().length; + }, + + /** + * @private + */ + _set: function(key, value) { + if (key === 'fill' || key === 'opacity') { + var i = this.objects.length; + this[key] = value; + while (i--) { + this.objects[i].set(key, value); + } + } + else { + this[key] = value; + } + }, + + /** + * Returns true if a group contains an object + * @method contains + * @param {Object} object Object to check against + * @return {Boolean} `true` if group contains an object + */ + contains: function(object) { + return this.objects.indexOf(object) > -1; + }, + + /** + * Returns object representation of an instance + * @method toObject + * @return {Object} object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + objects: invoke(this.objects, 'toObject') + }); + }, + + /** + * Renders instance on a given context + * @method render + * @param {CanvasRenderingContext2D} ctx context to render instance on + */ + render: function(ctx, noTransform) { + ctx.save(); + this.transform(ctx); + + var groupScaleFactor = Math.max(this.scaleX, this.scaleY); + + for (var i = 0, len = this.objects.length, object; object = this.objects[i]; i++) { + var originalScaleFactor = object.borderScaleFactor; + object.borderScaleFactor = groupScaleFactor; + object.render(ctx); + object.borderScaleFactor = originalScaleFactor; + } + if (!noTransform && this.active) { + this.drawBorders(ctx); + this.hideCorners || this.drawCorners(ctx); + } + ctx.restore(); + this.setCoords(); + }, + + /** + * Returns object from the group at the specified index + * @method item + * @param index {Number} index of item to get + * @return {fabric.Object} + */ + item: function(index) { + return this.getObjects()[index]; + }, + + /** + * Returns complexity of an instance + * @method complexity + * @return {Number} complexity + */ + complexity: function() { + return this.getObjects().reduce(function(total, object) { + total += (typeof object.complexity == 'function') ? object.complexity() : 0; + return total; + }, 0); + }, + + /** + * Retores original state of each of group objects (original state is that which was before group was created). + * @private + * @method _restoreObjectsState + * @return {fabric.Group} thisArg + * @chainable + */ + _restoreObjectsState: function() { + this.objects.forEach(this._restoreObjectState, this); + return this; + }, + + /** + * Restores original state of a specified object in group + * @private + * @method _restoreObjectState + * @param {fabric.Object} object + * @return {fabric.Group} thisArg + */ + _restoreObjectState: function(object) { + + var groupLeft = this.get('left'), + groupTop = this.get('top'), + groupAngle = this.getAngle() * (Math.PI / 180), + objectLeft = object.get('originalLeft'), + objectTop = object.get('originalTop'), + rotatedTop = Math.cos(groupAngle) * object.get('top') + Math.sin(groupAngle) * object.get('left'), + rotatedLeft = -Math.sin(groupAngle) * object.get('top') + Math.cos(groupAngle) * object.get('left'); + + object.setAngle(object.getAngle() + this.getAngle()); + + object.set('left', groupLeft + rotatedLeft * this.get('scaleX')); + object.set('top', groupTop + rotatedTop * this.get('scaleY')); + + object.set('scaleX', object.get('scaleX') * this.get('scaleX')); + object.set('scaleY', object.get('scaleY') * this.get('scaleY')); + + object.setCoords(); + object.hideCorners = false; + object.setActive(false); + object.setCoords(); + + return this; + }, + + /** + * Destroys a group (restoring state of its objects) + * @method destroy + * @return {fabric.Group} thisArg + * @chainable + */ + destroy: function() { + return this._restoreObjectsState(); + }, + + /** + * Saves coordinates of this instance (to be used together with `hasMoved`) + * @saveCoords + * @return {fabric.Group} thisArg + * @chainable + */ + saveCoords: function() { + this._originalLeft = this.get('left'); + this._originalTop = this.get('top'); + return this; + }, + + /** + * Checks whether this group was moved (since `saveCoords` was called last) + * @method hasMoved + * @return {Boolean} true if an object was moved (since fabric.Group#saveCoords was called) + */ + hasMoved: function() { + return this._originalLeft !== this.get('left') || + this._originalTop !== this.get('top'); + }, + + /** + * Sets coordinates of all group objects + * @method setObjectsCoords + * @return {fabric.Group} thisArg + * @chainable + */ + setObjectsCoords: function() { + this.forEachObject(function(object) { + object.setCoords(); + }); + return this; + }, + + /** + * Activates (makes active) all group objects + * @method activateAllObjects + * @return {fabric.Group} thisArg + * @chainable + */ + activateAllObjects: function() { + this.forEachObject(function(object) { + object.setActive(); + }); + return this; + }, + + /** + * Executes given function for each object in this group + * @method forEachObject + * @param {Function} callback + * Callback invoked with current object as first argument, + * index - as second and an array of all objects - as third. + * Iteration happens in reverse order (for performance reasons). + * Callback is invoked in a context of Global Object (e.g. `window`) + * when no `context` argument is given + * + * @param {Object} context Context (aka thisObject) + * + * @return {fabric.Group} thisArg + * @chainable + */ + forEachObject: fabric.StaticCanvas.prototype.forEachObject, + + /** + * @private + * @method _setOpacityIfSame + */ + _setOpacityIfSame: function() { + var objects = this.getObjects(), + firstValue = objects[0] ? objects[0].get('opacity') : 1; + + var isSameOpacity = objects.every(function(o) { + return o.get('opacity') === firstValue; + }); + + if (isSameOpacity) { + this.opacity = firstValue; + } + }, + + /** + * @private + * @method _calcBounds + */ + _calcBounds: function() { + var aX = [], + aY = [], + minX, minY, maxX, maxY, o, width, height, + i = 0, + len = this.objects.length; + + for (; i < len; ++i) { + o = this.objects[i]; + o.setCoords(); + for (var prop in o.oCoords) { + aX.push(o.oCoords[prop].x); + aY.push(o.oCoords[prop].y); + } + }; + + minX = min(aX); + maxX = max(aX); + minY = min(aY); + maxY = max(aY); + + width = (maxX - minX) || 0; + height = (maxY - minY) || 0; + + this.width = width; + this.height = height; + + this.left = (minX + width / 2) || 0; + this.top = (minY + height / 2) || 0; + }, + + /** + * Checks if point is contained within the group + * @method containsPoint + * @param {fabric.Point} point point with `x` and `y` properties + * @return {Boolean} true if point is contained within group + */ + containsPoint: function(point) { + + var halfWidth = this.get('width') / 2, + halfHeight = this.get('height') / 2, + centerX = this.get('left'), + centerY = this.get('top'); + + return centerX - halfWidth < point.x && + centerX + halfWidth > point.x && + centerY - halfHeight < point.y && + centerY + halfHeight > point.y; + }, + + /** + * Makes all of this group's objects grayscale (i.e. calling `toGrayscale` on them) + * @method toGrayscale + */ + toGrayscale: function() { + var i = this.objects.length; + while (i--) { + this.objects[i].toGrayscale(); + } + }, + + /** + * Returns svg representation of an instance + * @method toSVG + * @return {string} svg representation of an instance + */ + toSVG: function() { + var objectsMarkup = [ ]; + for (var i = 0, len = this.objects.length; i < len; i++) { + objectsMarkup.push(this.objects[i].toSVG()); + } + + return ( + '<g transform="' + this.getSvgTransform() + '">' + + objectsMarkup.join('') + + '</g>'); + } + }); + + /** + * Returns fabric.Group instance from an object representation + * @static + * @method fabric.Group.fromObject + * @param object {Object} object to create a group from + * @param options {Object} options object + * @return {fabric.Group} an instance of fabric.Group + */ + fabric.Group.fromObject = function(object, callback) { + fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) { + delete object.objects; + callback && callback(new fabric.Group(enlivenedObjects, object)); + }); + }; + + fabric.Group.async = true; + +})(typeof exports != 'undefined' ? exports : this); +(function(global) { + + "use strict"; + + var extend = fabric.util.object.extend; + + if (!global.fabric) { + global.fabric = { }; + } + + if (global.fabric.Image) { + fabric.warn('fabric.Image is already defined.'); + return; + }; + + if (!fabric.Object) { + fabric.warn('fabric.Object is required for fabric.Image initialization'); + return; + } + + /** + * @class Image + * @extends fabric.Object + */ + fabric.Image = fabric.util.createClass(fabric.Object, /** @scope fabric.Image.prototype */ { + + /** + * @property + * @type Boolean + */ + active: false, + + /** + * @property + * @type Boolean + */ + bordervisibility: false, + + /** + * @property + * @type Boolean + */ + cornervisibility: false, + + /** + * @property + * @type String + */ + type: 'image', + + /** + * Constructor + * @param {HTMLImageElement | String} element Image element + * @param {Object} options optional + */ + initialize: function(element, options) { + options || (options = { }); + + this.callSuper('initialize', options); + this._initElement(element); + this._originalImage = this.getElement(); + this._initConfig(options); + + this.filters = [ ]; + + if (options.filters) { + this.filters = options.filters; + this.applyFilters(); + } + }, + + /** + * Returns image element which this instance if based on + * @method getElement + * @return {HTMLImageElement} image element + */ + getElement: function() { + return this._element; + }, + + /** + * Sets image element for this instance to a specified one + * @method setElement + * @param {HTMLImageElement} element + * @return {fabric.Image} thisArg + * @chainable + */ + setElement: function(element) { + this._element = element; + this._initConfig(); + return this; + }, + + /** + * Returns original size of an image + * @method getOriginalSize + * @return {Object} object with "width" and "height" properties + */ + getOriginalSize: function() { + var element = this.getElement(); + return { + width: element.width, + height: element.height + }; + }, + + /** + * Sets border visibility + * @method setBorderVisibility + * @param {Boolean} visible When true, border is set to be visible + */ + setBorderVisibility: function(visible) { + this._resetWidthHeight(); + this._adjustWidthHeightToBorders(showBorder); + this.setCoords(); + }, + + /** + * Sets corner visibility + * @method setCornersVisibility + * @param {Boolean} visible When true, corners are set to be visible + */ + setCornersVisibility: function(visible) { + this.cornervisibility = !!visible; + }, + + /** + * Renders image on a specified context + * @method render + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + render: function(ctx, noTransform) { + ctx.save(); + var m = this.transformMatrix; + this._resetWidthHeight(); + if (this.group) { + ctx.translate(-this.group.width/2 + this.width/2, -this.group.height/2 + this.height/2); + } + if (m) { + ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + if (!noTransform) { + this.transform(ctx); + } + this._render(ctx); + if (this.active && !noTransform) { + this.drawBorders(ctx); + this.hideCorners || this.drawCorners(ctx); + } + ctx.restore(); + }, + + /** + * Returns object representation of an instance + * @method toObject + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + src: this._originalImage.src || this._originalImage._src, + filters: this.filters.concat() + }); + }, + + /** + * Returns svg representation of an instance + * @method toSVG + * @return {string} svg representation of an instance + */ + toSVG: function() { + return '<g transform="' + this.getSvgTransform() + '">'+ + '<image xlink:href="' + this.getSvgSrc() + '" '+ + 'style="' + this.getSvgStyles() + '" ' + + // we're essentially moving origin of transformation from top/left corner to the center of the shape + // by wrapping it in container <g> element with actual transformation, then offsetting object to the top/left + // so that object's center aligns with container's left/top + 'transform="translate('+ (-this.width/2) + ' ' + (-this.height/2) + ')" ' + + 'width="' + this.width + '" ' + + 'height="' + this.height + '"' + '/>'+ + '</g>'; + }, + + /** + * Returns source of an image + * @method getSrc + * @return {String} Source of an image + */ + getSrc: function() { + return this.getElement().src || this.getElement()._src; + }, + + /** + * Returns string representation of an instance + * @method toString + * @return {String} String representation of an instance + */ + toString: function() { + return '#<fabric.Image: { src: "' + this.getSrc() + '" }>'; + }, + + /** + * Returns a clone of an instance + * @mthod clone + * @param {Function} callback Callback is invoked with a clone as a first argument + */ + clone: function(callback) { + this.constructor.fromObject(this.toObject(), callback); + }, + + /** + * Applies filters assigned to this image (from "filters" array) + * @mthod applyFilters + * @param {Function} callback Callback is invoked when all filters have been applied and new image is generated + */ + applyFilters: function(callback) { + + if (this.filters.length === 0) { + this.setElement(this._originalImage); + callback && callback(); + return; + } + + var isLikelyNode = typeof Buffer !== 'undefined' && typeof window === 'undefined', + imgEl = this._originalImage, + canvasEl = fabric.document.createElement('canvas'), + replacement = isLikelyNode ? new (require('canvas').Image) : fabric.document.createElement('img'), + _this = this; + + if (!canvasEl.getContext && typeof G_vmlCanvasManager != 'undefined') { + G_vmlCanvasManager.initElement(canvasEl); + } + + canvasEl.width = imgEl.width; + canvasEl.height = imgEl.height; + + canvasEl.getContext('2d').drawImage(imgEl, 0, 0, imgEl.width, imgEl.height); + + this.filters.forEach(function(filter) { + filter && filter.applyTo(canvasEl); + }); + + /** @ignore */ + replacement.onload = function() { + _this._element = replacement; + callback && callback(); + replacement.onload = canvasEl = imgEl = null; + }; + replacement.width = imgEl.width; + replacement.height = imgEl.height; + + if (isLikelyNode) { + var base64str = canvasEl.toDataURL('image/png').replace(/data:image\/png;base64,/, ''); + replacement.src = new Buffer(base64str, 'base64'); + _this._element = replacement; + + // onload doesn't fire in node, so we invoke callback manually + callback && callback(); + } + else { + replacement.src = canvasEl.toDataURL('image/png'); + } + + return this; + }, + + /** + * @private + */ + _render: function(ctx) { + ctx.drawImage( + this.getElement(), + - this.width / 2, + -this.height / 2, + this.width, + this.height + ); + }, + + /** + * @private + */ + _adjustWidthHeightToBorders: function(showBorder) { + if (showBorder) { + this.currentBorder = this.borderwidth; + this.width += (2 * this.currentBorder); + this.height += (2 * this.currentBorder); + } + else { + this.currentBorder = 0; + } + }, + + /** + * @private + */ + _resetWidthHeight: function() { + var element = this.getElement(); + + this.set('width', element.width); + this.set('height', element.height); + }, + + /** + * The Image class's initialization method. This method is automatically + * called by the constructor. + * @method _initElement + * @param {HTMLImageElement|String} el The element representing the image + */ + _initElement: function(element) { + this.setElement(fabric.util.getById(element)); + fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS); + }, + + /** + * @method _initConfig + * @param {Object} options Options object + */ + _initConfig: function(options) { + options || (options = { }); + this.setOptions(options); + this._setBorder(); + this._setWidthHeight(options); + }, + + /** + * @method _initFilters + * @param {Object} object Object with filters property + */ + _initFilters: function(object) { + if (object.filters && object.filters.length) { + this.filters = object.filters.map(function(filterObj) { + return filterObj && fabric.Image.filters[filterObj.type].fromObject(filterObj); + }); + } + }, + + /** + * @private + */ + _setBorder: function() { + if (this.bordervisibility) { + this.currentBorder = this.borderwidth; + } + else { + this.currentBorder = 0; + } + }, + + /** + * @private + */ + _setWidthHeight: function(options) { + var sidesBorderWidth = 2 * this.currentBorder; + + this.width = 'width' in options + ? options.width + : ((this.getElement().width || 0) + sidesBorderWidth); + + this.height = 'height' in options + ? options.height + : ((this.getElement().height || 0) + sidesBorderWidth); + }, + + /** + * Returns complexity of an instance + * @method complexity + * @return {Number} complexity + */ + complexity: function() { + return 1; + } + }); + + /** + * Default CSS class name for canvas + * @static + * @type String + */ + fabric.Image.CSS_CANVAS = "canvas-img"; + + fabric.Image.prototype.getSvgSrc = fabric.Image.prototype.getSrc; + + /** + * Creates an instance of fabric.Image from its object representation + * @static + * @method fromObject + * @param object {Object} + * @param callback {Function} optional + */ + fabric.Image.fromObject = function(object, callback) { + var img = fabric.document.createElement('img'), + src = object.src; + + if (object.width) { + img.width = object.width; + } + if (object.height) { + img.height = object.height; + } + + /** @ignore */ + img.onload = function() { + fabric.Image.prototype._initFilters.call(object, object); + + var instance = new fabric.Image(img, object); + callback && callback(instance); + img = img.onload = null; + }; + img.src = src; + }; + + /** + * Creates an instance of fabric.Image from an URL string + * @static + * @method fromURL + * @param {String} url URL to create an image from + * @param {Function} [callback] Callback to invoke when image is created (newly created image is passed as a first argument) + * @param {Object} [imgOptions] Options object + */ + fabric.Image.fromURL = function(url, callback, imgOptions) { + var img = fabric.document.createElement('img'); + + /** @ignore */ + img.onload = function() { + if (callback) { + callback(new fabric.Image(img, imgOptions)); + } + img = img.onload = null; + }; + img.src = url; + }; + + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Image.fromElement}) + * @static + * @see http://www.w3.org/TR/SVG/struct.html#ImageElement + */ + fabric.Image.ATTRIBUTE_NAMES = 'x y width height fill fill-opacity opacity stroke stroke-width transform xlink:href'.split(' '); + + /** + * Returns {@link fabric.Image} instance from an SVG element + * @static + * @method fabric.Image.fromElement + * @param {SVGElement} element Element to parse + * @param {Function} callback Callback to execute when fabric.Image object is created + * @param {Object} [options] Options object + * @return {fabric.Image} + */ + fabric.Image.fromElement = function(element, callback, options) { + options || (options = { }); + + var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES); + + fabric.Image.fromURL(parsedAttributes['xlink:href'], callback, extend(parsedAttributes, options)); + }; + + fabric.Image.async = true; + +})(typeof exports != 'undefined' ? exports : this); + +fabric.util.object.extend(fabric.Object.prototype, { + + /** + * @method _getAngleValueForStraighten + * @return {Number} angle value + * @private + */ + _getAngleValueForStraighten: function() { + var angle = this.get('angle'); + + // TODO (kangax): can this be simplified? + + if (angle > -225 && angle <= -135) { return -180; } + else if (angle > -135 && angle <= -45) { return -90; } + else if (angle > -45 && angle <= 45) { return 0; } + else if (angle > 45 && angle <= 135) { return 90; } + else if (angle > 135 && angle <= 225 ) { return 180; } + else if (angle > 225 && angle <= 315) { return 270; } + else if (angle > 315) { return 360; } + + return 0; + }, + + /** + * @method straighten + * @return {fabric.Object} thisArg + * @chainable + */ + straighten: function() { + var angle = this._getAngleValueForStraighten(); + this.setAngle(angle); + return this; + }, + + /** + * @method fxStraighten + * @param {Object} callbacks + * - onComplete: invoked on completion + * - onChange: invoked on every step of animation + * + * @return {fabric.Object} thisArg + * @chainable + */ + fxStraighten: function(callbacks) { + callbacks = callbacks || { }; + + var empty = function() { }, + onComplete = callbacks.onComplete || empty, + onChange = callbacks.onChange || empty, + _this = this; + + fabric.util.animate({ + startValue: this.get('angle'), + endValue: this._getAngleValueForStraighten(), + duration: this.FX_DURATION, + onChange: function(value) { + _this.setAngle(value); + onChange(); + }, + onComplete: function() { + _this.setCoords(); + onComplete(); + }, + onStart: function() { + _this.setActive(false); + } + }); + + return this; + } +}); + +fabric.util.object.extend(fabric.StaticCanvas.prototype, { + + /** + * Straightens object, then rerenders canvas + * @method straightenObject + * @param {fabric.Object} object Object to straighten + * @return {fabric.Canvas} thisArg + * @chainable + */ + straightenObject: function (object) { + object.straighten(); + this.renderAll(); + return this; + }, + + /** + * Same as `fabric.Canvas#straightenObject`, but animated + * @method fxStraightenObject + * @param {fabric.Object} object Object to straighten + * @return {fabric.Canvas} thisArg + * @chainable + */ + fxStraightenObject: function (object) { + object.fxStraighten({ + onChange: this.renderAll.bind(this) + }); + return this; + } +}); +/** + * @namespace + */ +fabric.Image.filters = { }; + +/** + * @class fabric.Image.filters.Grayscale + * @memberOf fabric.Image.filters + */ +fabric.Image.filters.Grayscale = fabric.util.createClass( /** @scope fabric.Image.filters.Grayscale.prototype */ { + + /** + * @param {String} type + */ + type: "Grayscale", + + /** + * @method applyTo + * @memberOf fabric.Image.filters.Grayscale.prototype + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + iLen = imageData.width, + jLen = imageData.height, + index, average, i, j; + + for (i = 0; i < iLen; i++) { + for (j = 0; j < jLen; j++) { + + index = (i * 4) * jLen + (j * 4); + average = (data[index] + data[index + 1] + data[index + 2]) / 3; + + data[index] = average; + data[index + 1] = average; + data[index + 2] = average; + } + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * @method toJSON + * @return {String} json representation of filter + */ + toJSON: function() { + return { type: this.type }; + } +}); + +fabric.Image.filters.Grayscale.fromObject = function() { + return new fabric.Image.filters.Grayscale(); +}; + +/** + * @class fabric.Image.filters.RemoveWhite + * @memberOf fabric.Image.filters + */ +fabric.Image.filters.RemoveWhite = fabric.util.createClass( /** @scope fabric.Image.filters.RemoveWhite.prototype */ { + + /** + * @param {String} type + */ + type: "RemoveWhite", + + /** + * @memberOf fabric.Image.filters.RemoveWhite.prototype + * @param {Object} [options] Options object + */ + initialize: function(options) { + options || (options = { }); + this.threshold = options.threshold || 30; + this.distance = options.distance || 20; + }, + + /** + * @method applyTo + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + threshold = this.threshold, + distance = this.distance, + limit = 255 - threshold, + abs = Math.abs, + r, g, b; + + for (var i = 0, len = data.length; i < len; i += 4) { + + r = data[i]; + g = data[i+1]; + b = data[i+2]; + + if (r > limit && + g > limit && + b > limit && + abs(r-g) < distance && + abs(r-b) < distance && + abs(g-b) < distance) { + + data[i+3] = 1; + } + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * @method toJSON + * @return {String} json representation of filter + */ + toJSON: function() { + return { + type: this.type, + threshold: this.threshold, + distance: this.distance + }; + } +}); + +fabric.Image.filters.RemoveWhite.fromObject = function(object) { + return new fabric.Image.filters.RemoveWhite(object); +}; + +/** + * @class fabric.Image.filters.Invert + * @memberOf fabric.Image.filters + */ +fabric.Image.filters.Invert = fabric.util.createClass( /** @scope fabric.Image.filters.Invert.prototype */ { + + /** + * @param {String} type + */ + type: "Invert", + + /** + * @method applyTo + * @memberOf fabric.Image.filters.Invert.prototype + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + iLen = data.length, i; + + for (i = 0; i < iLen; i+=4) { + data[i] = 255 - data[i]; + data[i + 1] = 255 - data[i + 1]; + data[i + 2] = 255 - data[i + 2]; + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * @method toJSON + * @return {String} json representation of filter + */ + toJSON: function() { + return { type: this.type }; + } +}); + +fabric.Image.filters.Invert.fromObject = function() { + return new fabric.Image.filters.Invert(); +}; + +/** + * @class fabric.Image.filters.Sepia + * @memberOf fabric.Image.filters + */ +fabric.Image.filters.Sepia = fabric.util.createClass( /** @scope fabric.Image.filters.Sepia.prototype */ { + + /** + * @param {String} type + */ + type: "Sepia", + + /** + * @method applyTo + * @memberOf fabric.Image.filters.Sepia.prototype + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + iLen = data.length, i, avg; + + for (i = 0; i < iLen; i+=4) { + avg = 0.3 * data[i] + 0.59 * data[i + 1] + 0.11 * data[i + 2]; + data[i] = avg + 100; + data[i + 1] = avg + 50; + data[i + 2] = avg + 255; + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * @method toJSON + * @return {String} json representation of filter + */ + toJSON: function() { + return { type: this.type }; + } +}); + +fabric.Image.filters.Sepia.fromObject = function() { + return new fabric.Image.filters.Sepia(); +}; + +/** + * @class fabric.Image.filters.Sepia2 + * @memberOf fabric.Image.filters + */ +fabric.Image.filters.Sepia2 = fabric.util.createClass( /** @scope fabric.Image.filters.Sepia2.prototype */ { + + /** + * @param {String} type + */ + type: "Sepia2", + + /** + * @method applyTo + * @memberOf fabric.Image.filters.Sepia.prototype + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + iLen = data.length, i, r, g, b; + + for (i = 0; i < iLen; i+=4) { + + r = data[i]; + g = data[i + 1]; + b = data[i + 2]; + + data[i] = (r * 0.393 + g * 0.769 + b * 0.189 ) / 1.351; + data[i + 1] = (r * 0.349 + g * 0.686 + b * 0.168 ) / 1.203; + data[i + 2] = (r * 0.272 + g * 0.534 + b * 0.131 ) / 2.140; + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * @method toJSON + * @return {String} json representation of filter + */ + toJSON: function() { + return { type: this.type }; + } +}); + +fabric.Image.filters.Sepia2.fromObject = function() { + return new fabric.Image.filters.Sepia2(); +}; + +/** + * @class fabric.Image.filters.Brightness + * @memberOf fabric.Image.filters + */ +fabric.Image.filters.Brightness = fabric.util.createClass( /** @scope fabric.Image.filters.Brightness.prototype */ { + + /** + * @param {String} type + */ + type: "Brightness", + + /** + * @memberOf fabric.Image.filters.Brightness.prototype + * @param {Object} [options] Options object + */ + initialize: function(options) { + options || (options = { }); + this.brightness = options.brightness || 100; + }, + + /** + * @method applyTo + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + brightness = this.brightness; + + for (var i = 0, len = data.length; i < len; i += 4) { + data[i] += brightness; + data[i + 1] += brightness; + data[i + 2] += brightness; + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * @method toJSON + * @return {String} json representation of filter + */ + toJSON: function() { + return { + type: this.type, + brightness: this.brightness + }; + } +}); + +fabric.Image.filters.Brightness.fromObject = function(object) { + return new fabric.Image.filters.Brightness(object); +}; + +/** + * @class fabric.Image.filters.Brightness + * @memberOf fabric.Image.filters + */ +fabric.Image.filters.Noise = fabric.util.createClass( /** @scope fabric.Image.filters.Noise.prototype */ { + + /** + * @param {String} type + */ + type: "Noise", + + /** + * @memberOf fabric.Image.filters.Brightness.prototype + * @param {Object} [options] Options object + */ + initialize: function(options) { + options || (options = { }); + this.noise = options.noise || 100; + }, + + /** + * @method applyTo + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + noise = this.noise, rand; + + for (var i = 0, len = data.length; i < len; i += 4) { + + rand = (0.5 - Math.random()) * noise; + + data[i] += rand; + data[i + 1] += rand; + data[i + 2] += rand; + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * @method toJSON + * @return {String} json representation of filter + */ + toJSON: function() { + return { + type: this.type, + noise: this.noise + }; + } +}); + +fabric.Image.filters.Noise.fromObject = function(object) { + return new fabric.Image.filters.Noise(object); +}; + +/** + * @class fabric.Image.filters.Brightness + * @memberOf fabric.Image.filters + */ +fabric.Image.filters.GradientTransparency = fabric.util.createClass( /** @scope fabric.Image.filters.GradientTransparency.prototype */ { + + /** + * @param {String} type + */ + type: "GradientTransparency", + + /** + * @memberOf fabric.Image.filters.GradientTransparency.prototype + * @param {Object} [options] Options object + */ + initialize: function(options) { + options || (options = { }); + this.threshold = options.threshold || 100; + }, + + /** + * @method applyTo + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + threshold = this.threshold, + total = data.length; + + for (var i = 0, len = data.length; i < len; i += 4) { + data[i + 3] = threshold + 255 * (total - i) / total; + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * @method toJSON + * @return {String} json representation of filter + */ + toJSON: function() { + return { + type: this.type, + threshold: this.threshold + }; + } +}); + +fabric.Image.filters.GradientTransparency.fromObject = function(object) { + return new fabric.Image.filters.GradientTransparency(object); +}; + +/** + * @class fabric.Image.filters.Tint + * @memberOf fabric.Image.filters + */ +fabric.Image.filters.Tint = fabric.util.createClass( /** @scope fabric.Image.filters.Tint.prototype */ { + + /** + * @param {String} type + */ + type: "Tint", + + /** + * @memberOf fabric.Image.filters.RemoveWhite.prototype + * @param {Object} [options] Options object + */ + initialize: function(options) { + options || (options = { }); + this.color = options.color || 0; + }, + + /** + * @method applyTo + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + iLen = data.length, i, + r, g, b, a; + + var rgb = parseInt(this.color).toString(16); + var cr = parseInt('0x'+rgb.substr(0, 2)); + var cg = parseInt('0x'+rgb.substr(2, 2)); + var cb = parseInt('0x'+rgb.substr(4, 2)); + + for (i = 0; i < iLen; i+=4) { + + a = data[i+3]; + + if (a > 0){ + data[i] = cr; + data[i+1] = cg; + data[i+2] = cb; + } + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * @method toJSON + * @return {String} json representation of filter + */ + toJSON: function() { + return { + type: this.type, + color: this.color + }; + } +}); + +fabric.Image.filters.Tint.fromObject = function(object) { + return new fabric.Image.filters.Tint(object); +}; +(function(global) { + + "use strict"; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + clone = fabric.util.object.clone, + toFixed = fabric.util.toFixed; + + if (fabric.Text) { + fabric.warn('fabric.Text is already defined'); + return; + } + if (!fabric.Object) { + fabric.warn('fabric.Text requires fabric.Object'); + return; + } + + /** + * @class Text + * @extends fabric.Object + */ + fabric.Text = fabric.util.createClass(fabric.Object, /** @scope fabric.Text.prototype */ { + + /** + * @property + * @type Number + */ + fontSize: 40, + + /** + * @property + * @type Number + */ + fontWeight: 100, + + /** + * @property + * @type String + */ + fontFamily: 'Times New Roman', + + /** + * @property + * @type String + */ + textDecoration: '', + + /** + * @property + * @type String | null + */ + textShadow: '', + + /** + * Determines text alignment. Possible values: "left", "center", or "right". + * @property + * @type String + */ + textAlign: 'left', + + /** + * @property + * @type String + */ + fontStyle: '', + + /** + * @property + * @type Number + */ + lineHeight: 1.3, + + /** + * @property + * @type String + */ + strokeStyle: '', + + /** + * @property + * @type Number + */ + strokeWidth: 1, + + /** + * @property + * @type String + */ + backgroundColor: '', + + + /** + * @property + * @type String | null + */ + path: null, + + /** + * @property + * @type String + */ + type: 'text', + + /** + * Indicates whether canvas native text methods should be used to render text (otherwise, Cufon is used) + * @property + * @type Boolean + */ + useNative: true, + + /** + * Constructor + * @method initialize + * @param {String} text + * @param {Object} [options] + * @return {fabric.Text} thisArg + */ + initialize: function(text, options) { + this._initStateProperties(); + this.text = text; + this.setOptions(options || { }); + this._theta = this.angle * Math.PI / 180; + this._initDimensions(); + this.setCoords(); + }, + + /** + * Renders text object on offscreen canvas, so that it would get dimensions + * @private + * @method _initDimensions + */ + _initDimensions: function() { + var canvasEl = fabric.document.createElement('canvas'); + + if (!canvasEl.getContext && typeof G_vmlCanvasManager != 'undefined') { + G_vmlCanvasManager.initElement(canvasEl); + } + + this._render(canvasEl.getContext('2d')); + }, + + /** + * Creates `stateProperties` list on an instance, and adds `fabric.Text` -specific ones to it + * (such as "fontFamily", "fontWeight", etc.) + * @private + * @method _initStateProperties + */ + _initStateProperties: function() { + this.stateProperties = this.stateProperties.concat(); + this.stateProperties.push( + 'fontFamily', + 'fontWeight', + 'fontSize', + 'path', + 'text', + 'textDecoration', + 'textShadow', + 'textAlign', + 'fontStyle', + 'lineHeight', + 'strokeStyle', + 'strokeWidth', + 'backgroundColor', + 'useNative' + ); + fabric.util.removeFromArray(this.stateProperties, 'width'); + }, + + /** + * Returns string representation of an instance + * @method toString + * @return {String} String representation of text object + */ + toString: function() { + return '#<fabric.Text (' + this.complexity() + + '): { "text": "' + this.text + '", "fontFamily": "' + this.fontFamily + '" }>'; + }, + + /** + * @private + * @method _render + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + if (typeof Cufon === 'undefined' || this.useNative === true) { + this._renderViaNative(ctx); + } + else { + this._renderViaCufon(ctx); + } + }, + + /** + * @private + * @method _renderViaCufon + */ + _renderViaCufon: function(ctx) { + var o = Cufon.textOptions || (Cufon.textOptions = { }); + + // export options to be used by cufon.js + o.left = this.left; + o.top = this.top; + o.context = ctx; + o.color = this.fill; + + var el = this._initDummyElementForCufon(); + + // set "cursor" to top/left corner + this.transform(ctx); + + // draw text + Cufon.replaceElement(el, { + engine: 'canvas', + separate: 'none', + fontFamily: this.fontFamily, + fontWeight: this.fontWeight, + textDecoration: this.textDecoration, + textShadow: this.textShadow, + textAlign: this.textAlign, + fontStyle: this.fontStyle, + lineHeight: this.lineHeight, + strokeStyle: this.strokeStyle, + strokeWidth: this.strokeWidth, + backgroundColor: this.backgroundColor + }); + + // update width, height + this.width = o.width; + this.height = o.height; + + this._totalLineHeight = o.totalLineHeight; + this._fontAscent = o.fontAscent; + this._boundaries = o.boundaries; + this._shadowOffsets = o.shadowOffsets; + this._shadows = o.shadows || [ ]; + + el = null; + + // need to set coords _after_ the width/height was retreived from Cufon + this.setCoords(); + }, + + /** + * @private + * @method _render_native + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderViaNative: function(ctx) { + + this.transform(ctx); + this._setTextStyles(ctx); + + var textLines = this.text.split(/\r?\n/); + + this.width = this._getTextWidth(ctx, textLines); + this.height = this._getTextHeight(ctx, textLines); + + this._renderTextBackground(ctx, textLines); + + if (this.textAlign !== 'left') { + ctx.save(); + ctx.translate(this.textAlign === 'center' ? (this.width / 2) : this.width, 0); + } + + this._setTextShadow(ctx); + this._renderTextFill(ctx, textLines); + this.textShadow && ctx.restore(); + + this._renderTextStroke(ctx, textLines); + if (this.textAlign !== 'left') { + ctx.restore(); + } + + this._renderTextDecoration(ctx, textLines); + this._setBoundaries(ctx, textLines); + this._totalLineHeight = 0; + + this.setCoords(); + }, + + /** + * @private + * @method _setBoundaries + */ + _setBoundaries: function(ctx, textLines) { + this._boundaries = [ ]; + + for (var i = 0, len = textLines.length; i < len; i++) { + + var lineWidth = ctx.measureText(textLines[i]).width; + var lineLeftOffset = this._getLineLeftOffset(lineWidth); + + this._boundaries.push({ + height: this.fontSize, + width: lineWidth, + left: lineLeftOffset + }); + } + }, + + /** + * @private + * @method _setTextStyles + */ + _setTextStyles: function(ctx) { + ctx.fillStyle = this.fill; + ctx.strokeStyle = this.strokeStyle; + ctx.lineWidth = this.strokeWidth; + ctx.textBaseline = 'top'; + ctx.textAlign = this.textAlign; + ctx.font = this._getFontDeclaration(); + }, + + /** + * @private + * @method _getTextHeight + */ + _getTextHeight: function(ctx, textLines) { + return this.fontSize * textLines.length * this.lineHeight; + }, + + /** + * @private + * @method _getTextWidth + */ + _getTextWidth: function(ctx, textLines) { + var maxWidth = ctx.measureText(textLines[0]).width; + + for (var i = 1, len = textLines.length; i < len; i++) { + var currentLineWidth = ctx.measureText(textLines[i]).width; + if (currentLineWidth > maxWidth) { + maxWidth = currentLineWidth; + } + } + return maxWidth; + }, + + /** + * @private + * @method _setTextShadow + */ + _setTextShadow: function(ctx) { + if (this.textShadow) { + + // "rgba(0,0,0,0.2) 2px 2px 10px" + // "rgb(0, 100, 0) 0 0 5px" + // "red 2px 2px 1px" + // "#f55 123 345 567" + var reOffsetsAndBlur = /\s+(-?\d+)(?:px)?\s+(-?\d+)(?:px)?\s+(\d+)(?:px)?\s*/; + + var shadowDeclaration = this.textShadow; + var offsetsAndBlur = reOffsetsAndBlur.exec(this.textShadow); + var shadowColor = shadowDeclaration.replace(reOffsetsAndBlur, ''); + + ctx.save(); + ctx.shadowColor = shadowColor; + ctx.shadowOffsetX = parseInt(offsetsAndBlur[1], 10); + ctx.shadowOffsetY = parseInt(offsetsAndBlur[2], 10); + ctx.shadowBlur = parseInt(offsetsAndBlur[3], 10); + + this._shadows = [{ + blur: ctx.shadowBlur, + color: ctx.shadowColor, + offX: ctx.shadowOffsetX, + offY: ctx.shadowOffsetY + }]; + + this._shadowOffsets = [[ + parseInt(ctx.shadowOffsetX, 10), parseInt(ctx.shadowOffsetY, 10) + ]]; + } + }, + + _renderTextFill: function(ctx, textLines) { + this._boundaries = [ ]; + for (var i = 0, len = textLines.length; i < len; i++) { + ctx.fillText( + textLines[i], + -this.width / 2, + (-this.height / 2) + (i * this.fontSize * this.lineHeight) + ); + } + }, + + /** + * @private + * @method _renderTextStroke + */ + _renderTextStroke: function(ctx, textLines) { + if (this.strokeStyle) { + for (var i = 0, len = textLines.length; i < len; i++) { + ctx.strokeText( + textLines[i], + -this.width / 2, + (-this.height / 2) + (i * this.fontSize * this.lineHeight) + ); + } + } + }, + + /** + * @private + * @_renderTextBackground + */ + _renderTextBackground: function(ctx, textLines) { + if (this.backgroundColor) { + ctx.save(); + ctx.fillStyle = this.backgroundColor; + + for (var i = 0, len = textLines.length; i < len; i++) { + + var lineWidth = ctx.measureText(textLines[i]).width; + var lineLeftOffset = this._getLineLeftOffset(lineWidth); + + ctx.fillRect( + (-this.width / 2) + lineLeftOffset, + (-this.height / 2) + (i * this.fontSize * this.lineHeight), + lineWidth, + this.fontSize + ); + } + ctx.restore(); + } + }, + + /** + * @private + * @method _getLineLeftOffset + */ + _getLineLeftOffset: function(lineWidth) { + if (this.textAlign === 'center') { + return (this.width - lineWidth) / 2; + } + if (this.textAlign === 'right') { + return this.width - lineWidth; + } + return 0; + }, + + /** + * @private + * @method _renderTextDecoration + */ + _renderTextDecoration: function(ctx, textLines) { + + var halfOfVerticalBox = this._getTextHeight(ctx, textLines) / 2; + + function renderLinesAtOffset(offset) { + for (var i = 0, len = textLines.length; i < len; i++) { + + var lineWidth = ctx.measureText(textLines[i]).width; + var lineLeftOffset = this._getLineLeftOffset(lineWidth); + + ctx.fillRect( + (-this.width / 2) + lineLeftOffset, + (offset + (i * this.fontSize * this.lineHeight)) - halfOfVerticalBox, + lineWidth, + 1); + } + } + + if (this.textDecoration.indexOf('underline') > -1) { + renderLinesAtOffset.call(this, this.fontSize); + } + if (this.textDecoration.indexOf('line-through') > -1) { + renderLinesAtOffset.call(this, this.fontSize / 2); + } + if (this.textDecoration.indexOf('overline') > -1) { + renderLinesAtOffset.call(this, 0); + } + }, + + /** + * @private + * @method _getFontDeclaration + */ + _getFontDeclaration: function() { + return [ + this.fontStyle, + this.fontWeight, + this.fontSize + 'px', + (fabric.isLikelyNode ? ('"' + this.fontFamily + '"') : this.fontFamily) + ].join(' '); + }, + + /** + * @private + * @method _initDummyElement + */ + _initDummyElementForCufon: function() { + var el = fabric.document.createElement('pre'), + container = fabric.document.createElement('div'); + + // Cufon doesn't play nice with textDecoration=underline if element doesn't have a parent + container.appendChild(el); + + if (typeof G_vmlCanvasManager == 'undefined') { + el.innerHTML = this.text; + } + else { + // IE 7 & 8 drop newlines and white space on text nodes + // see: http://web.student.tuwien.ac.at/~e0226430/innerHtmlQuirk.html + // see: http://www.w3schools.com/dom/dom_mozilla_vs_ie.asp + el.innerText = this.text.replace(/\r?\n/gi, '\r'); + } + + el.style.fontSize = this.fontSize + 'px'; + el.style.letterSpacing = 'normal'; + + return el; + }, + + /** + * Renders text instance on a specified context + * @method render + * @param ctx {CanvasRenderingContext2D} context to render on + */ + render: function(ctx, noTransform) { + ctx.save(); + this._render(ctx); + if (!noTransform && this.active) { + this.drawBorders(ctx); + this.hideCorners || this.drawCorners(ctx); + } + ctx.restore(); + }, + + /** + * Returns object representation of an instance + * @method toObject + * @return {Object} Object representation of text object + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + text: this.text, + fontSize: this.fontSize, + fontWeight: this.fontWeight, + fontFamily: this.fontFamily, + fontStyle: this.fontStyle, + lineHeight: this.lineHeight, + textDecoration: this.textDecoration, + textShadow: this.textShadow, + textAlign: this.textAlign, + path: this.path, + strokeStyle: this.strokeStyle, + strokeWidth: this.strokeWidth, + backgroundColor: this.backgroundColor, + useNative: this.useNative + }); + }, + + /** + * Returns svg representation of an instance + * @method toSVG + * @return {string} svg representation of an instance + */ + toSVG: function() { + + var textLines = this.text.split(/\r?\n/), + lineTopOffset = this.useNative + ? this.fontSize * this.lineHeight + : (-this._fontAscent - ((this._fontAscent / 5) * this.lineHeight)), + + textLeftOffset = -(this.width/2), + textTopOffset = this.useNative + ? this.fontSize - 1 + : (this.height/2) - (textLines.length * this.fontSize) - this._totalLineHeight, + + textAndBg = this._getSVGTextAndBg(lineTopOffset, textLeftOffset, textLines), + shadowSpans = this._getSVGShadows(lineTopOffset, textLines); + + // move top offset by an ascent + textTopOffset += (this._fontAscent ? ((this._fontAscent / 5) * this.lineHeight) : 0); + + return [ + '<g transform="', this.getSvgTransform(), '">', + textAndBg.textBgRects.join(''), + '<text ', + (this.fontFamily ? 'font-family="\'' + this.fontFamily + '\'" ': ''), + (this.fontSize ? 'font-size="' + this.fontSize + '" ': ''), + (this.fontStyle ? 'font-style="' + this.fontStyle + '" ': ''), + (this.fontWeight ? 'font-weight="' + this.fontWeight + '" ': ''), + (this.textDecoration ? 'text-decoration="' + this.textDecoration + '" ': ''), + 'style="', this.getSvgStyles(), '" ', + /* svg starts from left/bottom corner so we normalize height */ + 'transform="translate(', toFixed(textLeftOffset, 2), ' ', toFixed(textTopOffset, 2), ')">', + shadowSpans.join(''), + textAndBg.textSpans.join(''), + '</text>', + '</g>' + ].join(''); + }, + + _getSVGShadows: function(lineTopOffset, textLines) { + var shadowSpans = [], j, i, jlen, ilen, lineTopOffsetMultiplier = 1; + + if (!this._shadows || !this._boundaries) { + return shadowSpans; + } + + for (j = 0, jlen = this._shadows.length; j < jlen; j++) { + for (i = 0, ilen = textLines.length; i < ilen; i++) { + if (textLines[i] !== '') { + var lineLeftOffset = (this._boundaries && this._boundaries[i]) ? this._boundaries[i].left : 0; + shadowSpans.push( + '<tspan x="', + toFixed((lineLeftOffset + lineTopOffsetMultiplier) + this._shadowOffsets[j][0], 2), + ((i === 0 || this.useNative) ? '" y' : '" dy'), '="', + toFixed(this.useNative + ? ((lineTopOffset * i) - this.height / 2 + this._shadowOffsets[j][1]) + : (lineTopOffset + (i === 0 ? this._shadowOffsets[j][1] : 0)), 2), + '" ', + this._getFillAttributes(this._shadows[j].color), '>', + fabric.util.string.escapeXml(textLines[i]), + '</tspan>'); + lineTopOffsetMultiplier = 1; + } else { + // in some environments (e.g. IE 7 & 8) empty tspans are completely ignored, using a lineTopOffsetMultiplier + // prevents empty tspans + lineTopOffsetMultiplier++; + } + } + } + return shadowSpans; + }, + + _getSVGTextAndBg: function(lineTopOffset, textLeftOffset, textLines) { + var textSpans = [ ], textBgRects = [ ], i, lineLeftOffset, len, lineTopOffsetMultiplier = 1; + + // text and background + for (i = 0, len = textLines.length; i < len; i++) { + if (textLines[i] !== '') { + lineLeftOffset = (this._boundaries && this._boundaries[i]) ? toFixed(this._boundaries[i].left, 2) : 0; + textSpans.push( + '<tspan x="', + lineLeftOffset, '" ', + (i === 0 || this.useNative ? 'y' : 'dy'), '="', + toFixed(this.useNative ? ((lineTopOffset * i) - this.height / 2) : (lineTopOffset * lineTopOffsetMultiplier), 2) , '" ', + // doing this on <tspan> elements since setting opacity on containing <text> one doesn't work in Illustrator + this._getFillAttributes(this.fill), '>', + fabric.util.string.escapeXml(textLines[i]), + '</tspan>' + ); + lineTopOffsetMultiplier = 1; + } else { + // in some environments (e.g. IE 7 & 8) empty tspans are completely ignored, using a lineTopOffsetMultiplier + // prevents empty tspans + lineTopOffsetMultiplier++; + } + + if (!this.backgroundColor || !this._boundaries) continue; + + textBgRects.push( + '<rect ', + this._getFillAttributes(this.backgroundColor), + ' x="', + toFixed(textLeftOffset + this._boundaries[i].left, 2), + '" y="', + /* an offset that seems to straighten things out */ + toFixed((lineTopOffset * i) - this.height / 2, 2), + '" width="', + toFixed(this._boundaries[i].width, 2), + '" height="', + toFixed(this._boundaries[i].height, 2), + '"></rect>'); + } + return { + textSpans: textSpans, + textBgRects: textBgRects + }; + }, + + // Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values + // we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 + _getFillAttributes: function(value) { + var fillColor = value ? new fabric.Color(value) : ''; + if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) { + return 'fill="' + value + '"'; + } + return 'opacity="' + fillColor.getAlpha() + '" fill="' + fillColor.setAlpha(1).toRgb() + '"'; + }, + + /** + * Sets "color" of an instance (alias of `set('fill', …)`) + * @method setColor + * @param {String} value + * @return {fabric.Text} thisArg + * @chainable + */ + setColor: function(value) { + this.set('fill', value); + return this; + }, + + /** + * Sets fontSize of an instance and updates its coordinates + * @method setFontsize + * @param {Number} value + * @return {fabric.Text} thisArg + * @chainable + */ + setFontsize: function(value) { + this.set('fontSize', value); + this._initDimensions(); + this.setCoords(); + return this; + }, + + /** + * Returns actual text value of an instance + * @method getText + * @return {String} + */ + getText: function() { + return this.text; + }, + + /** + * Sets text of an instance, and updates its coordinates + * @method setText + * @param {String} value + * @return {fabric.Text} thisArg + * @chainable + */ + setText: function(value) { + this.set('text', value); + this._initDimensions(); + this.setCoords(); + return this; + }, + + /** + * Sets specified property to a specified value + * @method set + * @param {String} name + * @param {Any} value + * @return {fabric.Text} thisArg + * @chainable + */ + _set: function(name, value) { + if (name === 'fontFamily' && this.path) { + this.path = this.path.replace(/(.*?)([^\/]*)(\.font\.js)/, '$1' + value + '$3'); + } + this.callSuper('_set', name, value); + } + }); + + /** + * List of attribute names to account for when parsing SVG element (used by `fabric.Text.fromElement`) + * @static + */ + fabric.Text.ATTRIBUTE_NAMES = + ('x y fill fill-opacity opacity stroke stroke-width transform ' + + 'font-family font-style font-weight font-size text-decoration').split(' '); + + /** + * Returns fabric.Text instance from an object representation + * @static + * @method fromObject + * @param {Object} object to create an instance from + * @return {fabric.Text} an instance + */ + fabric.Text.fromObject = function(object) { + return new fabric.Text(object.text, clone(object)); + }; + + /** + * Returns fabric.Text instance from an SVG element (<b>not yet implemented</b>) + * @static + * @method fabric.Text.fromElement + * @param element + * @param options + * @return {fabric.Text} an instance + */ + fabric.Text.fromElement = function(element, options) { + if (!element) { + return null; + } + + var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES); + var options = fabric.util.object.extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes); + var text = new fabric.Text(element.textContent, options); + + return text; + }; + +})(typeof exports != 'undefined' ? exports : this); +(function() { + + if (typeof document != 'undefined' && typeof window != 'undefined') { + return; + } + + var DOMParser = new require('xmldom').DOMParser, + URL = require('url'), + HTTP = require('http'), + + Canvas = require('canvas'), + Image = require('canvas').Image; + + function request(url, encoding, callback) { + var oURL = URL.parse(url), + client = HTTP.createClient(oURL.port, oURL.hostname), + request = client.request('GET', oURL.pathname, { 'host': oURL.hostname }); + + client.addListener('error', function(err) { + if (err.errno === process.ECONNREFUSED) { + fabric.log('ECONNREFUSED: connection refused to ' + client.host + ':' + client.port); + } + else { + fabric.log(err.message); + } + }); + + request.end(); + request.on('response', function (response) { + var body = ""; + if (encoding) { + response.setEncoding(encoding); + } + response.on('end', function () { + callback(body); + }); + response.on('data', function (chunk) { + if (response.statusCode == 200) { + body += chunk; + } + }); + }); + } + + fabric.util.loadImage = function(url, callback) { + request(url, 'binary', function(body) { + var img = new Image(); + img.src = new Buffer(body, 'binary'); + // preserving original url, which seems to be lost in node-canvas + img._src = url; + callback(img); + }); + }; + + fabric.loadSVGFromURL = function(url, callback) { + url = url.replace(/^\n\s*/, '').replace(/\?.*$/, '').trim(); + request(url, '', function(body) { + fabric.loadSVGFromString(body, callback); + }); + }; + + fabric.loadSVGFromString = function(string, callback) { + var doc = new DOMParser().parseFromString(string); + fabric.parseSVGDocument(doc.documentElement, function(results, options) { + callback(results, options); + }); + }; + + fabric.util.getScript = function(url, callback) { + request(url, '', function(body) { + eval(body); + callback && callback(); + }); + }; + + fabric.Image.fromObject = function(object, callback) { + fabric.util.loadImage(object.src, function(img) { + var oImg = new fabric.Image(img); + + oImg._initConfig(object); + oImg._initFilters(object); + callback(oImg); + }); + }; + + /** + * Only available when running fabric on node.js + * @method createCanvasForNode + * @param width Canvas width + * @param height Canvas height + * @return {Object} wrapped canvas instance + */ + fabric.createCanvasForNode = function(width, height) { + + var canvasEl = fabric.document.createElement('canvas'), + nodeCanvas = new Canvas(width || 600, height || 600); + + // jsdom doesn't create style on canvas element, so here be temp. workaround + canvasEl.style = { }; + + canvasEl.width = nodeCanvas.width; + canvasEl.height = nodeCanvas.height; + + var canvas = fabric.Canvas || fabric.StaticCanvas; + var fabricCanvas = new canvas(canvasEl); + fabricCanvas.contextContainer = nodeCanvas.getContext('2d'); + fabricCanvas.nodeCanvas = nodeCanvas; + + return fabricCanvas; + }; + + fabric.StaticCanvas.prototype.createPNGStream = function() { + return this.nodeCanvas.createPNGStream(); + }; + if (fabric.Canvas) { + fabric.Canvas.prototype.createPNGStream + } + + var origSetWidth = fabric.StaticCanvas.prototype.setWidth; + fabric.StaticCanvas.prototype.setWidth = function(width) { + origSetWidth.call(this); + this.nodeCanvas.width = width; + return this; + }; + if (fabric.Canvas) { + fabric.Canvas.prototype.setWidth = fabric.StaticCanvas.prototype.setWidth; + } + + var origSetHeight = fabric.StaticCanvas.prototype.setHeight; + fabric.StaticCanvas.prototype.setHeight = function(height) { + origSetHeight.call(this); + this.nodeCanvas.height = height; + return this; + }; + if (fabric.Canvas) { + fabric.Canvas.prototype.setHeight = fabric.StaticCanvas.prototype.setHeight; + } + +})(); diff --git a/js/jquery.miniColors.min.js b/js/jquery.miniColors.min.js new file mode 100644 index 0000000..1d33464 --- /dev/null +++ b/js/jquery.miniColors.min.js @@ -0,0 +1,9 @@ +/* + * jQuery miniColors: A small color selector + * + * Copyright 2012 Cory LaViska for A Beautiful Site, LLC. (http://www.abeautifulsite.net/) + * + * Dual licensed under the MIT or GPL Version 2 licenses + * +*/ +if(jQuery)(function($){$.extend($.fn,{miniColors:function(o,data){var create=function(input,o,data){var color=expandHex(input.val())||'ffffff',hsb=hex2hsb(color),rgb=hsb2rgb(hsb),alpha=parseFloat(input.attr('data-opacity')).toFixed(2);if(alpha>1)alpha=1;if(alpha<0)alpha=0;var trigger=$('<a class="miniColors-trigger" style="background-color: #'+color+'" href="#"></a>');trigger.insertAfter(input);trigger.wrap('<span class="miniColors-triggerWrap"></span>');if(o.opacity){trigger.css('backgroundColor','rgba('+rgb.r+', '+rgb.g+', '+rgb.b+', '+alpha+')')}input.addClass('miniColors').data('original-maxlength',input.attr('maxlength')||null).data('original-autocomplete',input.attr('autocomplete')||null).data('letterCase',o.letterCase==='uppercase'?'uppercase':'lowercase').data('opacity',o.opacity?true:false).data('alpha',alpha).data('trigger',trigger).data('hsb',hsb).data('change',o.change?o.change:null).data('close',o.close?o.close:null).data('open',o.open?o.open:null).attr('maxlength',7).attr('autocomplete','off').val('#'+convertCase(color,o.letterCase));if(o.readonly||input.prop('readonly'))input.prop('readonly',true);if(o.disabled||input.prop('disabled'))disable(input);trigger.on('click.miniColors',function(event){event.preventDefault();if(input.val()==='')input.val('#');show(input)});input.on('focus.miniColors',function(event){if(input.val()==='')input.val('#');show(input)});input.on('blur.miniColors',function(event){var hex=expandHex(hsb2hex(input.data('hsb')));input.val(hex?'#'+convertCase(hex,input.data('letterCase')):'')});input.on('keydown.miniColors',function(event){if(event.keyCode===9)hide(input)});input.on('keyup.miniColors',function(event){setColorFromInput(input)});input.on('paste.miniColors',function(event){setTimeout(function(){setColorFromInput(input)},5)})};var destroy=function(input){hide();input=$(input);input.data('trigger').parent().remove();input.attr('autocomplete',input.data('original-autocomplete')).attr('maxlength',input.data('original-maxlength')).removeData().removeClass('miniColors').off('.miniColors');$(document).off('.miniColors')};var enable=function(input){input.prop('disabled',false).data('trigger').parent().removeClass('disabled')};var disable=function(input){hide(input);input.prop('disabled',true).data('trigger').parent().addClass('disabled')};var show=function(input){if(input.prop('disabled'))return false;hide();var selector=$('<div class="miniColors-selector"></div>');selector.append('<div class="miniColors-hues"><div class="miniColors-huePicker"></div></div>').append('<div class="miniColors-colors" style="background-color: #FFF;"><div class="miniColors-colorPicker"><div class="miniColors-colorPicker-inner"></div></div>').css('display','none').addClass(input.attr('class'));if(input.data('opacity')){selector.addClass('opacity').prepend('<div class="miniColors-opacity"><div class="miniColors-opacityPicker"></div></div>')}var hsb=input.data('hsb');selector.find('.miniColors-colors').css('backgroundColor','#'+hsb2hex({h:hsb.h,s:100,b:100})).end().find('.miniColors-opacity').css('backgroundColor','#'+hsb2hex({h:hsb.h,s:hsb.s,b:hsb.b})).end();var colorPosition=input.data('colorPosition');if(!colorPosition)colorPosition=getColorPositionFromHSB(hsb);selector.find('.miniColors-colorPicker').css('top',colorPosition.y+'px').css('left',colorPosition.x+'px');var huePosition=input.data('huePosition');if(!huePosition)huePosition=getHuePositionFromHSB(hsb);selector.find('.miniColors-huePicker').css('top',huePosition+'px');var opacityPosition=input.data('opacityPosition');if(!opacityPosition)opacityPosition=getOpacityPositionFromAlpha(input.attr('data-opacity'));selector.find('.miniColors-opacityPicker').css('top',opacityPosition+'px');input.data('selector',selector).data('huePicker',selector.find('.miniColors-huePicker')).data('opacityPicker',selector.find('.miniColors-opacityPicker')).data('colorPicker',selector.find('.miniColors-colorPicker')).data('mousebutton',0);$('BODY').append(selector);var trigger=input.data('trigger'),hidden=!input.is(':visible'),top=hidden?trigger.offset().top+trigger.outerHeight():input.offset().top+input.outerHeight(),left=hidden?trigger.offset().left:input.offset().left,selectorWidth=selector.outerWidth(),selectorHeight=selector.outerHeight(),triggerWidth=trigger.outerWidth(),triggerHeight=trigger.outerHeight(),windowHeight=$(window).height(),windowWidth=$(window).width(),scrollTop=$(window).scrollTop(),scrollLeft=$(window).scrollLeft();if((top+selectorHeight)>windowHeight+scrollTop)top=top-selectorHeight-triggerHeight;if((left+selectorWidth)>windowWidth+scrollLeft)left=left-selectorWidth+triggerWidth;selector.css({top:top,left:left}).fadeIn(100);selector.on('selectstart',function(){return false});if(!$.browser.msie||($.browser.msie&&$.browser.version>=9)){$(window).on('resize.miniColors',function(event){hide(input)})}$(document).on('mousedown.miniColors touchstart.miniColors',function(event){input.data('mousebutton',1);var testSubject=$(event.target).parents().andSelf();if(testSubject.hasClass('miniColors-colors')){event.preventDefault();input.data('moving','colors');moveColor(input,event)}if(testSubject.hasClass('miniColors-hues')){event.preventDefault();input.data('moving','hues');moveHue(input,event)}if(testSubject.hasClass('miniColors-opacity')){event.preventDefault();input.data('moving','opacity');moveOpacity(input,event)}if(testSubject.hasClass('miniColors-selector')){event.preventDefault();return}if(testSubject.hasClass('miniColors'))return;hide(input)}).on('mouseup.miniColors touchend.miniColors',function(event){event.preventDefault();input.data('mousebutton',0).removeData('moving')}).on('mousemove.miniColors touchmove.miniColors',function(event){event.preventDefault();if(input.data('mousebutton')===1){if(input.data('moving')==='colors')moveColor(input,event);if(input.data('moving')==='hues')moveHue(input,event);if(input.data('moving')==='opacity')moveOpacity(input,event)}});if(input.data('open')){input.data('open').call(input.get(0),'#'+hsb2hex(hsb),$.extend(hsb2rgb(hsb),{a:parseFloat(input.attr('data-opacity'))}))}};var hide=function(input){if(!input)input=$('.miniColors');input.each(function(){var selector=$(this).data('selector');$(this).removeData('selector');$(selector).fadeOut(100,function(){if(input.data('close')){var hsb=input.data('hsb'),hex=hsb2hex(hsb);input.data('close').call(input.get(0),'#'+hex,$.extend(hsb2rgb(hsb),{a:parseFloat(input.attr('data-opacity'))}))}$(this).remove()})});$(document).off('.miniColors')};var moveColor=function(input,event){var colorPicker=input.data('colorPicker');colorPicker.hide();var position={x:event.pageX,y:event.pageY};if(event.originalEvent.changedTouches){position.x=event.originalEvent.changedTouches[0].pageX;position.y=event.originalEvent.changedTouches[0].pageY}position.x=position.x-input.data('selector').find('.miniColors-colors').offset().left-6;position.y=position.y-input.data('selector').find('.miniColors-colors').offset().top-6;if(position.x<=-5)position.x=-5;if(position.x>=144)position.x=144;if(position.y<=-5)position.y=-5;if(position.y>=144)position.y=144;input.data('colorPosition',position);colorPicker.css('left',position.x).css('top',position.y).show();var s=Math.round((position.x+5)*0.67);if(s<0)s=0;if(s>100)s=100;var b=100-Math.round((position.y+5)*0.67);if(b<0)b=0;if(b>100)b=100;var hsb=input.data('hsb');hsb.s=s;hsb.b=b;setColor(input,hsb,true)};var moveHue=function(input,event){var huePicker=input.data('huePicker');huePicker.hide();var position=event.pageY;if(event.originalEvent.changedTouches){position=event.originalEvent.changedTouches[0].pageY}position=position-input.data('selector').find('.miniColors-colors').offset().top-1;if(position<=-1)position=-1;if(position>=149)position=149;input.data('huePosition',position);huePicker.css('top',position).show();var h=Math.round((150-position-1)*2.4);if(h<0)h=0;if(h>360)h=360;var hsb=input.data('hsb');hsb.h=h;setColor(input,hsb,true)};var moveOpacity=function(input,event){var opacityPicker=input.data('opacityPicker');opacityPicker.hide();var position=event.pageY;if(event.originalEvent.changedTouches){position=event.originalEvent.changedTouches[0].pageY}position=position-input.data('selector').find('.miniColors-colors').offset().top-1;if(position<=-1)position=-1;if(position>=149)position=149;input.data('opacityPosition',position);opacityPicker.css('top',position).show();var alpha=parseFloat((150-position-1)/150).toFixed(2);if(alpha<0)alpha=0;if(alpha>1)alpha=1;input.data('alpha',alpha).attr('data-opacity',alpha);setColor(input,input.data('hsb'),true)};var setColor=function(input,hsb,updateInput){input.data('hsb',hsb);var hex=hsb2hex(hsb),selector=$(input.data('selector'));if(updateInput)input.val('#'+convertCase(hex,input.data('letterCase')));selector.find('.miniColors-colors').css('backgroundColor','#'+hsb2hex({h:hsb.h,s:100,b:100})).end().find('.miniColors-opacity').css('backgroundColor','#'+hex).end();var rgb=hsb2rgb(hsb);input.data('trigger').css('backgroundColor','#'+hex);if(input.data('opacity')){input.data('trigger').css('backgroundColor','rgba('+rgb.r+', '+rgb.g+', '+rgb.b+', '+input.attr('data-opacity')+')')}if(input.data('change')){if((hex+','+input.attr('data-opacity'))===input.data('lastChange'))return;input.data('change').call(input.get(0),'#'+hex,$.extend(hsb2rgb(hsb),{a:parseFloat(input.attr('data-opacity'))}));input.data('lastChange',hex+','+input.attr('data-opacity'))}};var setColorFromInput=function(input){input.val('#'+cleanHex(input.val()));var hex=expandHex(input.val());if(!hex)return false;var hsb=hex2hsb(hex);var colorPosition=getColorPositionFromHSB(hsb);var colorPicker=$(input.data('colorPicker'));colorPicker.css('top',colorPosition.y+'px').css('left',colorPosition.x+'px');input.data('colorPosition',colorPosition);var huePosition=getHuePositionFromHSB(hsb);var huePicker=$(input.data('huePicker'));huePicker.css('top',huePosition+'px');input.data('huePosition',huePosition);var opacityPosition=getOpacityPositionFromAlpha(input.attr('data-opacity'));var opacityPicker=$(input.data('opacityPicker'));opacityPicker.css('top',opacityPosition+'px');input.data('opacityPosition',opacityPosition);setColor(input,hsb);return true};var convertCase=function(string,letterCase){if(letterCase==='uppercase'){return string.toUpperCase()}else{return string.toLowerCase()}};var getColorPositionFromHSB=function(hsb){var x=Math.ceil(hsb.s/0.67);if(x<0)x=0;if(x>150)x=150;var y=150-Math.ceil(hsb.b/0.67);if(y<0)y=0;if(y>150)y=150;return{x:x-5,y:y-5}};var getHuePositionFromHSB=function(hsb){var y=150-(hsb.h/2.4);if(y<0)h=0;if(y>150)h=150;return y};var getOpacityPositionFromAlpha=function(alpha){var y=150*alpha;if(y<0)y=0;if(y>150)y=150;return 150-y};var cleanHex=function(hex){return hex.replace(/[^A-F0-9]/ig,'')};var expandHex=function(hex){hex=cleanHex(hex);if(!hex)return null;if(hex.length===3)hex=hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];return hex.length===6?hex:null};var hsb2rgb=function(hsb){var rgb={};var h=Math.round(hsb.h);var s=Math.round(hsb.s*255/100);var v=Math.round(hsb.b*255/100);if(s===0){rgb.r=rgb.g=rgb.b=v}else{var t1=v;var t2=(255-s)*v/255;var t3=(t1-t2)*(h%60)/60;if(h===360)h=0;if(h<60){rgb.r=t1;rgb.b=t2;rgb.g=t2+t3}else if(h<120){rgb.g=t1;rgb.b=t2;rgb.r=t1-t3}else if(h<180){rgb.g=t1;rgb.r=t2;rgb.b=t2+t3}else if(h<240){rgb.b=t1;rgb.r=t2;rgb.g=t1-t3}else if(h<300){rgb.b=t1;rgb.g=t2;rgb.r=t2+t3}else if(h<360){rgb.r=t1;rgb.g=t2;rgb.b=t1-t3}else{rgb.r=0;rgb.g=0;rgb.b=0}}return{r:Math.round(rgb.r),g:Math.round(rgb.g),b:Math.round(rgb.b)}};var rgb2hex=function(rgb){var hex=[rgb.r.toString(16),rgb.g.toString(16),rgb.b.toString(16)];$.each(hex,function(nr,val){if(val.length===1)hex[nr]='0'+val});return hex.join('')};var hex2rgb=function(hex){hex=parseInt(((hex.indexOf('#')>-1)?hex.substring(1):hex),16);return{r:hex>>16,g:(hex&0x00FF00)>>8,b:(hex&0x0000FF)}};var rgb2hsb=function(rgb){var hsb={h:0,s:0,b:0};var min=Math.min(rgb.r,rgb.g,rgb.b);var max=Math.max(rgb.r,rgb.g,rgb.b);var delta=max-min;hsb.b=max;hsb.s=max!==0?255*delta/max:0;if(hsb.s!==0){if(rgb.r===max){hsb.h=(rgb.g-rgb.b)/delta}else if(rgb.g===max){hsb.h=2+(rgb.b-rgb.r)/delta}else{hsb.h=4+(rgb.r-rgb.g)/delta}}else{hsb.h=-1}hsb.h*=60;if(hsb.h<0){hsb.h+=360}hsb.s*=100/255;hsb.b*=100/255;return hsb};var hex2hsb=function(hex){var hsb=rgb2hsb(hex2rgb(hex));if(hsb.s===0)hsb.h=360;return hsb};var hsb2hex=function(hsb){return rgb2hex(hsb2rgb(hsb))};switch(o){case'readonly':$(this).each(function(){if(!$(this).hasClass('miniColors'))return;$(this).prop('readonly',data)});return $(this);case'disabled':$(this).each(function(){if(!$(this).hasClass('miniColors'))return;if(data){disable($(this))}else{enable($(this))}});return $(this);case'value':if(data===undefined){if(!$(this).hasClass('miniColors'))return;var input=$(this),hex=expandHex(input.val());return hex?'#'+convertCase(hex,input.data('letterCase')):null}$(this).each(function(){if(!$(this).hasClass('miniColors'))return;$(this).val(data);setColorFromInput($(this))});return $(this);case'opacity':if(data===undefined){if(!$(this).hasClass('miniColors'))return;if($(this).data('opacity')){return parseFloat($(this).attr('data-opacity'))}else{return null}}$(this).each(function(){if(!$(this).hasClass('miniColors'))return;if(data<0)data=0;if(data>1)data=1;$(this).attr('data-opacity',data).data('alpha',data);setColorFromInput($(this))});return $(this);case'destroy':$(this).each(function(){if(!$(this).hasClass('miniColors'))return;destroy($(this))});return $(this);default:if(!o)o={};$(this).each(function(){if($(this)[0].tagName.toLowerCase()!=='input')return;if($(this).data('trigger'))return;create($(this),o,data)});return $(this)}}})})(jQuery);
\ No newline at end of file diff --git a/js/tshirtEditor.js b/js/tshirtEditor.js new file mode 100644 index 0000000..1b3f8c0 --- /dev/null +++ b/js/tshirtEditor.js @@ -0,0 +1,355 @@ +var canvas; +var tshirts = new Array(); //prototype: [{style:'x',color:'white',front:'a',back:'b',price:{tshirt:'12.95',frontPrint:'4.99',backPrint:'4.99',total:'22.47'}}] +var a; +var b; +var line1; +var line2; +var line3; +var line4; + $(document).ready(function() { + //setup front side canvas + canvas = new fabric.Canvas('tcanvas', { + hoverCursor: 'pointer', + selection: true, + selectionBorderColor:'blue' + }); + canvas.on({ + 'object:moving': function(e) { + e.target.opacity = 0.5; + }, + 'object:modified': function(e) { + e.target.opacity = 1; + }, + 'object:selected':onObjectSelected, + 'selection:cleared':onSelectedCleared + }); + // piggyback on `canvas.findTarget`, to fire "object:over" and "object:out" events + canvas.findTarget = (function(originalFn) { + return function() { + var target = originalFn.apply(this, arguments); + if (target) { + if (this._hoveredTarget !== target) { + canvas.fire('object:over', { target: target }); + if (this._hoveredTarget) { + canvas.fire('object:out', { target: this._hoveredTarget }); + } + this._hoveredTarget = target; + } + } + else if (this._hoveredTarget) { + canvas.fire('object:out', { target: this._hoveredTarget }); + this._hoveredTarget = null; + } + return target; + }; + })(canvas.findTarget); + + canvas.on('object:over', function(e) { + //e.target.setFill('red'); + //canvas.renderAll(); + }); + + canvas.on('object:out', function(e) { + //e.target.setFill('green'); + //canvas.renderAll(); + }); + + document.getElementById('add-text').onclick = function() { + var text = $("#text-string").val(); + var textSample = new fabric.Text(text, { + left: fabric.util.getRandomInt(0, 200), + top: fabric.util.getRandomInt(0, 400), + fontFamily: 'helvetica', + angle: 0, + fill: '#000000', + scaleX: 0.5, + scaleY: 0.5, + fontWeight: '', + hasRotatingPoint:true + }); + canvas.add(textSample); + canvas.item(canvas.item.length-1).hasRotatingPoint = true; + $("#texteditor").css('display', 'block'); + $("#imageeditor").css('display', 'block'); + }; + $("#text-string").keyup(function(){ + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.text = this.value; + canvas.renderAll(); + } + }); + $(".img-polaroid").click(function(e){ + var el = e.target; + /*temp code*/ + var offset = 50; + var left = fabric.util.getRandomInt(0 + offset, 200 - offset); + var top = fabric.util.getRandomInt(0 + offset, 400 - offset); + var angle = fabric.util.getRandomInt(-20, 40); + var width = fabric.util.getRandomInt(30, 50); + var opacity = (function(min, max){ return Math.random() * (max - min) + min; })(0.5, 1); + + fabric.Image.fromURL(el.src, function(image) { + image.set({ + left: left, + top: top, + angle: 0, + padding: 10, + cornersize: 10, + hasRotatingPoint:true + }); + //image.scale(getRandomNum(0.1, 0.25)).setCoords(); + canvas.add(image); + }); + }); + document.getElementById('remove-selected').onclick = function() { + var activeObject = canvas.getActiveObject(), + activeGroup = canvas.getActiveGroup(); + if (activeObject) { + canvas.remove(activeObject); + $("#text-string").val(""); + } + else if (activeGroup) { + var objectsInGroup = activeGroup.getObjects(); + canvas.discardActiveGroup(); + objectsInGroup.forEach(function(object) { + canvas.remove(object); + }); + } + }; + document.getElementById('bring-to-front').onclick = function() { + var activeObject = canvas.getActiveObject(), + activeGroup = canvas.getActiveGroup(); + if (activeObject) { + activeObject.bringToFront(); + } + else if (activeGroup) { + var objectsInGroup = activeGroup.getObjects(); + canvas.discardActiveGroup(); + objectsInGroup.forEach(function(object) { + object.bringToFront(); + }); + } + }; + document.getElementById('send-to-back').onclick = function() { + var activeObject = canvas.getActiveObject(), + activeGroup = canvas.getActiveGroup(); + if (activeObject) { + activeObject.sendToBack(); + } + else if (activeGroup) { + var objectsInGroup = activeGroup.getObjects(); + canvas.discardActiveGroup(); + objectsInGroup.forEach(function(object) { + object.sendToBack(); + }); + } + }; + $("#text-bold").click(function() { + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.fontWeight = (activeObject.fontWeight == 'bold' ? '' : 'bold'); + canvas.renderAll(); + } + }); + $("#text-italic").click(function() { + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.fontStyle = (activeObject.fontStyle == 'italic' ? '' : 'italic'); + canvas.renderAll(); + } + }); + $("#text-strike").click(function() { + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.textDecoration = (activeObject.textDecoration == 'line-through' ? '' : 'line-through'); + canvas.renderAll(); + } + }); + $("#text-underline").click(function() { + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.textDecoration = (activeObject.textDecoration == 'underline' ? '' : 'underline'); + canvas.renderAll(); + } + }); + $("#text-left").click(function() { + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.textAlign = 'left'; + canvas.renderAll(); + } + }); + $("#text-center").click(function() { + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.textAlign = 'center'; + canvas.renderAll(); + } + }); + $("#text-right").click(function() { + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.textAlign = 'right'; + canvas.renderAll(); + } + }); + $("#font-family").change(function() { + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.fontFamily = this.value; + canvas.renderAll(); + } + }); + $('#text-bgcolor').miniColors({ + change: function(hex, rgb) { + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.backgroundColor = this.value; + canvas.renderAll(); + } + }, + open: function(hex, rgb) { + // + }, + close: function(hex, rgb) { + // + } + }); + $('#text-fontcolor').miniColors({ + change: function(hex, rgb) { + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.fill = this.value; + canvas.renderAll(); + } + }, + open: function(hex, rgb) { + // + }, + close: function(hex, rgb) { + // + } + }); + + $('#text-strokecolor').miniColors({ + change: function(hex, rgb) { + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.strokeStyle = this.value; + canvas.renderAll(); + } + }, + open: function(hex, rgb) { + // + }, + close: function(hex, rgb) { + // + } + }); + + //canvas.add(new fabric.fabric.Object({hasBorders:true,hasControls:false,hasRotatingPoint:false,selectable:false,type:'rect'})); + $("#drawingArea").hover( + function() { + canvas.add(line1); + canvas.add(line2); + canvas.add(line3); + canvas.add(line4); + canvas.renderAll(); + }, + function() { + canvas.remove(line1); + canvas.remove(line2); + canvas.remove(line3); + canvas.remove(line4); + canvas.renderAll(); + } + ); + + $('.color-preview').click(function(){ + var color = $(this).css("background-color"); + document.getElementById("shirtDiv").style.backgroundColor = color; + }); + + $('#flip').click( + function() { + if ($(this).attr("data-original-title") == "Show Back View") { + $(this).attr('data-original-title', 'Show Front View'); + $("#tshirtFacing").attr("src","img/crew_back.png"); + a = JSON.stringify(canvas); + canvas.clear(); + try + { + var json = JSON.parse(b); + canvas.loadFromJSON(b); + } + catch(e) + {} + + } else { + $(this).attr('data-original-title', 'Show Back View'); + $("#tshirtFacing").attr("src","img/crew_front.png"); + b = JSON.stringify(canvas); + canvas.clear(); + try + { + var json = JSON.parse(a); + canvas.loadFromJSON(a); + } + catch(e) + {} + } + canvas.renderAll(); + setTimeout(function() { + canvas.calcOffset(); + },200); + }); + $(".clearfix button,a").tooltip(); + line1 = new fabric.Line([0,0,200,0], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false}); + line2 = new fabric.Line([199,0,200,399], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false}); + line3 = new fabric.Line([0,0,0,400], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false}); + line4 = new fabric.Line([0,400,200,399], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false}); + });//doc ready + + + function getRandomNum(min, max) { + return Math.random() * (max - min) + min; + } + + function onObjectSelected(e) { + var selectedObject = e.target; + $("#text-string").val(""); + selectedObject.hasRotatingPoint = true + if (selectedObject && selectedObject.type === 'text') { + //display text editor + $("#texteditor").css('display', 'block'); + $("#text-string").val(selectedObject.getText()); + $('#text-fontcolor').miniColors('value',selectedObject.fill); + $('#text-strokecolor').miniColors('value',selectedObject.strokeStyle); + $("#imageeditor").css('display', 'block'); + } + else if (selectedObject && selectedObject.type === 'image'){ + //display image editor + $("#texteditor").css('display', 'none'); + $("#imageeditor").css('display', 'block'); + } + } + function onSelectedCleared(e){ + $("#texteditor").css('display', 'none'); + $("#text-string").val(""); + $("#imageeditor").css('display', 'none'); + } + function setFont(font){ + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'text') { + activeObject.fontFamily = font; + canvas.renderAll(); + } + } + function removeWhite(){ + var activeObject = canvas.getActiveObject(); + if (activeObject && activeObject.type === 'image') { + activeObject.filters[2] = new fabric.Image.filters.RemoveWhite({hreshold: 100, distance: 10});//0-255, 0-255 + activeObject.applyFilters(canvas.renderAll.bind(canvas)); + } + }
\ No newline at end of file diff --git a/template.html b/template.html new file mode 100644 index 0000000..d67340f --- /dev/null +++ b/template.html @@ -0,0 +1,113 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <title>Title</title> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content=""> + <meta name="author" content=""> + + <!-- Le HTML5 shim, for IE6-8 support of HTML elements --> + <!--[if lt IE 9]> + <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script> + <![endif]--> + + <!-- Le styles --> + <link href="css/bootstrap.min.css" rel="stylesheet"> + <link href="css/bootstrap-responsive.min.css" rel="stylesheet"> + <script type="text/javascript"> + </script> + <style type="text/css"> + .footer { + padding: 70px 0; + margin-top: 70px; + border-top: 1px solid #E5E5E5; + background-color: whiteSmoke; + } + body { + padding-top: 60px; + padding-bottom: 40px; + } + </style> + </head> + + <body class="preview" data-spy="scroll" data-target=".subnav" data-offset="80"> + + <!-- Navbar + ================================================== --> + <div class="navbar navbar-fixed-top"> + <div class="navbar-inner"> + <div class="container"> + <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + </a> + <a class="brand" href="#">Custom T-Shirt</a> + <div class="nav-collapse" id="main-menu"> + + </div> + </div> + </div> + </div> + + <div class="container"> + <section id="typography"> + <div class="page-header"> + <h1>Typography</h1> + </div> + + <!-- Headings & Paragraph Copy --> + <div class="row"> + + <div class="span4"> + <div class="well"> + <h1>h1. Heading 1</h1> + <h2>h2. Heading 2</h2> + <h3>h3. Heading 3</h3> + <h4>h4. Heading 4</h4> + <h5>h5. Heading 5</h5> + <h6>h6. Heading 6</h6> + </div> + </div> + + <div class="span4"> + <h3>Tee Styles</h3> + <p>Nullam quis risus eget urna mollis ornare vel eu leo. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p> + <p>Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Donec sed odio dui.</p> + </div> + + <div class="span4"> + <h3>Example addresses</h3> + <address> + <strong>Twitter, Inc.</strong><br> + 795 Folsom Ave, Suite 600<br> + San Francisco, CA 94107<br> + <abbr title="Phone">P:</abbr> (123) 456-7890 + </address> + <address> + <strong>Full Name</strong><br> + <a href="mailto:#">[email protected]</a> + </address> + </div> + + </div> + + </section> + </div><!-- /container --> + +<!-- Footer ================================================== --> + <footer class="footer navbar navbar-fixed-bottom"> + <div class="container"> + <p class="pull-right"><a href="#">Back to top</a></p> + <p></p> + </div> + </footer> + + <!-- Le javascript + ================================================== --> + <!-- Placed at the end of the document so the pages load faster --> + <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> + <script src="js/bootstrap.min.js"></script> + </body> +</html>
\ No newline at end of file diff --git a/test.html b/test.html new file mode 100644 index 0000000..72133f8 --- /dev/null +++ b/test.html @@ -0,0 +1,15 @@ +<html> +<body style="background-color:#000000"> +<ul> +<li><img style="cursor:pointer;" class="img-polaroid" src="http://www.gravatar.com/avatar/3f779b4f4996f9ccba9bc7dde7be7398?s=100&f=y&d=mm"></li> +<li><img style="cursor:pointer;" class="img-polaroid" src="http://www.gravatar.com/avatar/3f779b4f4996f9ccba9bc7dde7be7398?s=100&f=y&d=identicon"></li> +<li><img style="cursor:pointer;" class="img-polaroid" src="http://www.gravatar.com/avatar/3f779b4f4996f9ccba9bc7dde7be7398?s=100&f=y&d=monsterid"></li> +<li><img style="cursor:pointer;" class="img-polaroid" src="http://www.gravatar.com/avatar/3f779b4f4996f9ccba9bc7dde7be7398?s=100&f=y&d=wavatar"></li> +<li><img style="cursor:pointer;" class="img-polaroid" src="http://www.gravatar.com/avatar/3f779b4f4996f9ccba9bc7dde7be7398?s=100&f=y&d=retro"></li> +</ul> +</body> +</html> + + + + |
