├── LICENSE ├── README.md ├── app.css ├── app.html ├── embed.css ├── embed.html ├── embed.js ├── favicon.ico ├── fonts.js ├── icons.svg ├── index.css ├── index.html ├── lib ├── StackBlur.js ├── canvg.js └── rgbcolor.js ├── phosphorus.js ├── phosphorus.sublime-project ├── player.css └── player.js /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2017 Nathan Dinsmore 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [phosphorus.github.io](https://phosphorus.github.io) 2 | -------------------------------------------------------------------------------- /app.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #000; 3 | margin: 0; 4 | overflow: hidden; 5 | } 6 | .player { 7 | position: absolute; 8 | } 9 | .splash, 10 | .error { 11 | position: absolute; 12 | top: 0; 13 | left: 0; 14 | width: 100%; 15 | height: 100%; 16 | background: #000; 17 | display: table; 18 | color: #fff; 19 | cursor: default; 20 | } 21 | .error { 22 | display: none; 23 | } 24 | .splash > div, 25 | .error > div { 26 | display: table-cell; 27 | height: 100%; 28 | text-align: center; 29 | vertical-align: middle; 30 | } 31 | .progress { 32 | width: 80%; 33 | height: 16px; 34 | border: 1px solid #fff; 35 | margin: 0 auto; 36 | } 37 | .progress-bar { 38 | background: #fff; 39 | width: 10%; 40 | height: 100%; 41 | } 42 | h1 { 43 | font: 300 72px Helvetica Neue, Helvetica, Arial, sans-serif; 44 | margin: 0 0 16px; 45 | } 46 | p { 47 | font: 300 24px/1.5 Helvetica Neue, Helvetica, Arial, sans-serif; 48 | margin: 0; 49 | color: rgba(255, 255, 255, .6); 50 | } 51 | .error a { 52 | color: #fff; 53 | } 54 | -------------------------------------------------------------------------------- /app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | phosphorus 7 | 8 |
9 |
10 |
11 |

phosphorus

12 |
13 |
14 |
15 |
16 |
17 |

phosphorus

18 |

An error has occurred. Click here to file a bug report on GitHub.

19 |
20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 117 | -------------------------------------------------------------------------------- /embed.css: -------------------------------------------------------------------------------- 1 | body { 2 | width: 480px; 3 | margin: 0 auto; 4 | } 5 | 6 | .progress-bar { 7 | padding: 0; 8 | } 9 | 10 | .internal-error { 11 | position: absolute; 12 | left: 1px; 13 | bottom: 1px; 14 | right: 1px; 15 | background: #fff; 16 | box-shadow: 0 -1px rgba(0, 0, 0, .4); 17 | } 18 | 19 | .controls { 20 | display: none; 21 | } 22 | 23 | .has-ui .controls { 24 | display: block; 25 | } 26 | 27 | .hide-ui .controls { 28 | position: absolute; 29 | width: 100%; 30 | } 31 | .hide-ui .controls span { 32 | display: none; 33 | } 34 | .hide-ui .player { 35 | box-shadow: none; 36 | } 37 | -------------------------------------------------------------------------------- /embed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | phosphorus 5 | 6 | 7 |
8 |
9 | 10 | 11 | 12 |
Turbo Mode
13 | 14 |
15 |
16 |
17 | An internal error occurred. Click here to file a bug report. 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 70 | -------------------------------------------------------------------------------- /embed.js: -------------------------------------------------------------------------------- 1 | (function(global) { 2 | 'use strict'; 3 | 4 | var script = document.currentScript || (function(scripts) { 5 | return scripts[scripts.length - 1]; 6 | })(document.getElementsByTagName('script')); 7 | 8 | var hasUI = true; 9 | var params = script.src.split('?')[1].split('&'); 10 | params.forEach(function(p) { 11 | var parts = p.split('='); 12 | if (parts.length > 1 && parts[0] === 'ui') { 13 | hasUI = parts[1] !== 'false'; 14 | } 15 | }); 16 | 17 | var iframe = document.createElement('iframe'); 18 | iframe.setAttribute('allowfullscreen', true); 19 | iframe.setAttribute('allowtransparency', true); 20 | iframe.src = script.src.replace(/^http:/, 'https:').replace(/embed\.js/, 'embed.html'); 21 | iframe.width = hasUI ? 482 : 480; 22 | iframe.height = hasUI ? 393 : 360; 23 | iframe.style.border = '0'; 24 | iframe.className = 'phosphorus'; 25 | 26 | script.parentNode.replaceChild(iframe, script); 27 | 28 | }(this)); 29 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trumank/phosphorus/915ecc1185654e7bb3279faa3ec3457852064088/favicon.ico -------------------------------------------------------------------------------- /fonts.js: -------------------------------------------------------------------------------- 1 | WebFontConfig = { 2 | google: { families: [ 'Donegal+One::latin', 'Gloria+Hallelujah::latin', 'Permanent+Marker::latin', 'Mystery+Quest::latin' ] } 3 | }; 4 | (function() { 5 | var wf = document.createElement('script'); 6 | wf.src = ('https:' == document.location.protocol ? 'https' : 'http') + 7 | '://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js'; 8 | wf.type = 'text/javascript'; 9 | wf.async = 'true'; 10 | var s = document.getElementsByTagName('script')[0]; 11 | s.parentNode.insertBefore(wf, s); 12 | })(); 13 | -------------------------------------------------------------------------------- /icons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 39 | 46 | 57 | 58 | 60 | 61 | 63 | image/svg+xml 64 | 66 | 67 | 68 | 69 | 70 | 75 | 81 | 87 | 93 | 99 | 105 | 111 | 116 | 122 | 128 | 134 | 140 | 146 | 152 | 158 | 164 | 170 | 176 | 182 | 188 | 194 | 200 | 206 | 216 | 222 | 228 | 234 | 240 | 246 | 256 | 266 | 276 | 286 | 296 | 306 | 312 | 318 | 324 | 330 | 331 | 332 | -------------------------------------------------------------------------------- /index.css: -------------------------------------------------------------------------------- 1 | body { 2 | width: 480px; 3 | margin: 24px auto 0; 4 | padding-bottom: 24px; 5 | } 6 | .title { 7 | margin: 16px 0 0; 8 | position: relative; 9 | } 10 | .url { 11 | position: relative; 12 | background: 0; 13 | border: 0; 14 | margin: 0; 15 | padding: 0 0 0 32px; 16 | outline: 0; 17 | font: 300 23px/32px Helvetica Neue, Helvetica, Arial, sans-serif; 18 | display: block; 19 | width: 100%; 20 | -moz-box-sizing: border-box; 21 | box-sizing: border-box; 22 | color: rgba(0, 0, 0, .4); 23 | } 24 | .url:focus { 25 | color: #000; 26 | } 27 | .project-link { 28 | position: absolute; 29 | top: 0; 30 | left: 0; 31 | background-position: -160px 0; 32 | } 33 | 34 | a { 35 | color: #25d; 36 | text-decoration: underline; 37 | } 38 | a:visited { 39 | color: #73c; 40 | } 41 | a:active { 42 | color: #03a; 43 | } 44 | .dropdown { 45 | display: inline-block; 46 | position: relative; 47 | } 48 | .dropdown > select { 49 | -webkit-appearance: none; 50 | font: inherit; 51 | position: absolute; 52 | opacity: 0; 53 | cursor: pointer; 54 | top: 0; 55 | left: 0; 56 | width: 100%; 57 | height: 100%; 58 | } 59 | 60 | .area { 61 | overflow: hidden; 62 | margin: 0 -24px; 63 | padding: 0 24px; 64 | } 65 | #player-area, #project-area { 66 | height: 0; 67 | } 68 | #player-area { 69 | border-bottom: 1px solid transparent; 70 | margin-bottom: -1px; 71 | } 72 | .fs #player-area { 73 | overflow: visible; 74 | } 75 | 76 | section { 77 | margin: 24px 0; 78 | } 79 | h1, 80 | p { 81 | font: 300 16px/1.5 Helvetica Neue, Helvetica, Arial, sans-serif; 82 | margin: 0 0 16px; 83 | } 84 | h1 { 85 | font: 300 24px/32px Helvetica Neue, Helvetica, Arial, sans-serif; 86 | margin: 16px 0 0; 87 | } 88 | h1.title { 89 | font: 300 54px/72px Helvetica Neue, Helvetica, Arial, sans-serif; 90 | margin: 0; 91 | } 92 | code { 93 | font: 12px Menlo, Monaco, Consolas, Courier New, monospace; 94 | background: #f5f5f5; 95 | border-radius: 3px; 96 | padding: 3px; 97 | } 98 | .package a { 99 | padding: 2px 8px; 100 | background: linear-gradient(#fafafa, #e8e8e8); 101 | box-shadow: 102 | 0 0 0 1px rgba(0, 0, 0, .35), 103 | 0 1px 4px rgba(0, 0, 0, .2); 104 | border-radius: 3px; 105 | cursor: pointer; 106 | color: #000; 107 | text-decoration: none; 108 | -webkit-tap-highlight-color: transparent; 109 | } 110 | .package a:hover { 111 | background: linear-gradient(#fff, #eaeaea); 112 | box-shadow: 113 | 0 0 0 1px rgba(0, 0, 0, .4), 114 | 0 1px 4px rgba(0, 0, 0, .3); 115 | } 116 | .package a:active { 117 | background: linear-gradient(#ddd, #eaeaea); 118 | box-shadow: 119 | 0 0 0 1px rgba(0, 0, 0, .5), 120 | inset 0 2px 5px rgba(0, 0, 0, .15); 121 | } 122 | .package label, 123 | .package input, 124 | .package a, 125 | .package span { 126 | display: inline-block; 127 | vertical-align: middle; 128 | } 129 | .package input[type=checkbox] { 130 | margin: 0 0 0 16px; 131 | } 132 | 133 | #embed-code { 134 | background: 0; 135 | border: 1px solid rgba(0, 0, 0, .4); 136 | margin: 0; 137 | padding: 4px; 138 | outline: 0; 139 | width: 80px; 140 | font: 12px/16px Menlo, Monaco, Consolas, Courier New, monospace; 141 | -moz-box-sizing: border-box; 142 | box-sizing: border-box; 143 | color: rgba(0, 0, 0, .7); 144 | } 145 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | phosphorus 5 | 6 | 7 | 8 |
9 |

phosphorus

10 |

phosphorus runs your Scratch projects really fast by compiling them to JavaScript. Try it out by pasting a URL or project ID into the field below or choosing an example.

22 |
23 | 24 |
25 |
26 | 27 | 28 | 29 |
Turbo Mode
30 | 31 |
32 |
33 |
34 | An internal error occurred. Click here to file a bug report. 35 |
36 |
37 | 38 |
39 |
40 | 41 | 42 |
43 | 44 |
45 |
46 |

Package this project

47 |

Get a link to a web page that automatically runs your project.

48 |

49 | Package 50 | 51 | 52 | 53 | 54 |

55 |
56 |
57 |

Embed this project

58 |

Include the phosphorus player in your web site.

59 |

60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 |

68 |
69 |
70 |

Report a problem

71 |

phosphorus is still in development. Click here to report a problem with this project.

72 |
73 |
74 |
75 |

Credits

76 |

phosphorus was created by Nathan. Its CPS-style compilation and overall design was inspired by Rhys's sb2.js. It would have more bugs if not for Truman and Tim. It uses the JSZip library, created by Stuart Knightley, David Duponchel, Franz Buchinger, and António Afonso, to read .sb2 files and compressed projects, and the canvg library, created by Gabe Lerner, to render SVGs in <canvas> elements. 77 |

Code

78 |

The source code for phosphorus is available on GitHub.

79 |
80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 273 | -------------------------------------------------------------------------------- /lib/StackBlur.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | StackBlur - a fast almost Gaussian Blur For Canvas 4 | 5 | Version: 0.5 6 | Author: Mario Klingemann 7 | Contact: mario@quasimondo.com 8 | Website: http://www.quasimondo.com/StackBlurForCanvas 9 | Twitter: @quasimondo 10 | 11 | In case you find this class useful - especially in commercial projects - 12 | I am not totally unhappy for a small donation to my PayPal account 13 | mario@quasimondo.de 14 | 15 | Or support me on flattr: 16 | https://flattr.com/thing/72791/StackBlur-a-fast-almost-Gaussian-Blur-Effect-for-CanvasJavascript 17 | 18 | Copyright (c) 2010 Mario Klingemann 19 | 20 | Permission is hereby granted, free of charge, to any person 21 | obtaining a copy of this software and associated documentation 22 | files (the "Software"), to deal in the Software without 23 | restriction, including without limitation the rights to use, 24 | copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the 26 | Software is furnished to do so, subject to the following 27 | conditions: 28 | 29 | The above copyright notice and this permission notice shall be 30 | included in all copies or substantial portions of the Software. 31 | 32 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 33 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 34 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 35 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 36 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 37 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 38 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 39 | OTHER DEALINGS IN THE SOFTWARE. 40 | */ 41 | 42 | (function ( global ) { 43 | 44 | var mul_table = [ 45 | 512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512, 46 | 454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512, 47 | 482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456, 48 | 437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512, 49 | 497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328, 50 | 320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456, 51 | 446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335, 52 | 329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512, 53 | 505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405, 54 | 399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328, 55 | 324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271, 56 | 268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456, 57 | 451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388, 58 | 385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335, 59 | 332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292, 60 | 289,287,285,282,280,278,275,273,271,269,267,265,263,261,259]; 61 | 62 | 63 | var shg_table = [ 64 | 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 65 | 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 66 | 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 67 | 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 68 | 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 69 | 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 70 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 71 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 72 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 73 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 74 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 75 | 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 76 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 77 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 78 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 79 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24 ]; 80 | 81 | function premultiplyAlpha(imageData) 82 | { 83 | var pixels = imageData.data; 84 | var size = imageData.width * imageData.height * 4; 85 | 86 | for (var i=0; i> shg_sum; 249 | pixels[yi+1] = (g_sum * mul_sum) >> shg_sum; 250 | pixels[yi+2] = (b_sum * mul_sum) >> shg_sum; 251 | pixels[yi+3] = (a_sum * mul_sum) >> shg_sum; 252 | 253 | r_sum -= r_out_sum; 254 | g_sum -= g_out_sum; 255 | b_sum -= b_out_sum; 256 | a_sum -= a_out_sum; 257 | 258 | r_out_sum -= stackIn.r; 259 | g_out_sum -= stackIn.g; 260 | b_out_sum -= stackIn.b; 261 | a_out_sum -= stackIn.a; 262 | 263 | p = ( yw + ( ( p = x + radius + 1 ) < widthMinus1 ? p : widthMinus1 ) ) << 2; 264 | 265 | r_in_sum += ( stackIn.r = pixels[p]); 266 | g_in_sum += ( stackIn.g = pixels[p+1]); 267 | b_in_sum += ( stackIn.b = pixels[p+2]); 268 | a_in_sum += ( stackIn.a = pixels[p+3]); 269 | 270 | r_sum += r_in_sum; 271 | g_sum += g_in_sum; 272 | b_sum += b_in_sum; 273 | a_sum += a_in_sum; 274 | 275 | stackIn = stackIn.next; 276 | 277 | r_out_sum += ( pr = stackOut.r ); 278 | g_out_sum += ( pg = stackOut.g ); 279 | b_out_sum += ( pb = stackOut.b ); 280 | a_out_sum += ( pa = stackOut.a ); 281 | 282 | r_in_sum -= pr; 283 | g_in_sum -= pg; 284 | b_in_sum -= pb; 285 | a_in_sum -= pa; 286 | 287 | stackOut = stackOut.next; 288 | 289 | yi += 4; 290 | } 291 | yw += width; 292 | } 293 | 294 | 295 | for ( x = 0; x < width; x++ ) 296 | { 297 | g_in_sum = b_in_sum = a_in_sum = r_in_sum = g_sum = b_sum = a_sum = r_sum = 0; 298 | 299 | yi = x << 2; 300 | r_out_sum = radiusPlus1 * ( pr = pixels[yi]); 301 | g_out_sum = radiusPlus1 * ( pg = pixels[yi+1]); 302 | b_out_sum = radiusPlus1 * ( pb = pixels[yi+2]); 303 | a_out_sum = radiusPlus1 * ( pa = pixels[yi+3]); 304 | 305 | r_sum += sumFactor * pr; 306 | g_sum += sumFactor * pg; 307 | b_sum += sumFactor * pb; 308 | a_sum += sumFactor * pa; 309 | 310 | stack = stackStart; 311 | 312 | for( i = 0; i < radiusPlus1; i++ ) 313 | { 314 | stack.r = pr; 315 | stack.g = pg; 316 | stack.b = pb; 317 | stack.a = pa; 318 | stack = stack.next; 319 | } 320 | 321 | yp = width; 322 | 323 | for( i = 1; i <= radius; i++ ) 324 | { 325 | yi = ( yp + x ) << 2; 326 | 327 | r_sum += ( stack.r = ( pr = pixels[yi])) * ( rbs = radiusPlus1 - i ); 328 | g_sum += ( stack.g = ( pg = pixels[yi+1])) * rbs; 329 | b_sum += ( stack.b = ( pb = pixels[yi+2])) * rbs; 330 | a_sum += ( stack.a = ( pa = pixels[yi+3])) * rbs; 331 | 332 | r_in_sum += pr; 333 | g_in_sum += pg; 334 | b_in_sum += pb; 335 | a_in_sum += pa; 336 | 337 | stack = stack.next; 338 | 339 | if( i < heightMinus1 ) 340 | { 341 | yp += width; 342 | } 343 | } 344 | 345 | yi = x; 346 | stackIn = stackStart; 347 | stackOut = stackEnd; 348 | for ( y = 0; y < height; y++ ) 349 | { 350 | p = yi << 2; 351 | pixels[p] = (r_sum * mul_sum) >> shg_sum; 352 | pixels[p+1] = (g_sum * mul_sum) >> shg_sum; 353 | pixels[p+2] = (b_sum * mul_sum) >> shg_sum; 354 | pixels[p+3] = (a_sum * mul_sum) >> shg_sum; 355 | 356 | r_sum -= r_out_sum; 357 | g_sum -= g_out_sum; 358 | b_sum -= b_out_sum; 359 | a_sum -= a_out_sum; 360 | 361 | r_out_sum -= stackIn.r; 362 | g_out_sum -= stackIn.g; 363 | b_out_sum -= stackIn.b; 364 | a_out_sum -= stackIn.a; 365 | 366 | p = ( x + (( ( p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1 ) * width )) << 2; 367 | 368 | r_sum += ( r_in_sum += ( stackIn.r = pixels[p])); 369 | g_sum += ( g_in_sum += ( stackIn.g = pixels[p+1])); 370 | b_sum += ( b_in_sum += ( stackIn.b = pixels[p+2])); 371 | a_sum += ( a_in_sum += ( stackIn.a = pixels[p+3])); 372 | 373 | stackIn = stackIn.next; 374 | 375 | r_out_sum += ( pr = stackOut.r ); 376 | g_out_sum += ( pg = stackOut.g ); 377 | b_out_sum += ( pb = stackOut.b ); 378 | a_out_sum += ( pa = stackOut.a ); 379 | 380 | r_in_sum -= pr; 381 | g_in_sum -= pg; 382 | b_in_sum -= pb; 383 | a_in_sum -= pa; 384 | 385 | stackOut = stackOut.next; 386 | 387 | yi += width; 388 | } 389 | } 390 | 391 | unpremultiplyAlpha(imageData); 392 | 393 | context.putImageData( imageData, top_x, top_y ); 394 | } 395 | 396 | 397 | function stackBlurCanvasRGB( id, top_x, top_y, width, height, radius ) 398 | { 399 | if ( isNaN(radius) || radius < 1 ) return; 400 | radius |= 0; 401 | 402 | var canvas = document.getElementById( id ); 403 | var context = canvas.getContext("2d"); 404 | var imageData; 405 | 406 | try { 407 | try { 408 | imageData = context.getImageData( top_x, top_y, width, height ); 409 | } catch(e) { 410 | 411 | // NOTE: this part is supposedly only needed if you want to work with local files 412 | // so it might be okay to remove the whole try/catch block and just use 413 | // imageData = context.getImageData( top_x, top_y, width, height ); 414 | try { 415 | netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); 416 | imageData = context.getImageData( top_x, top_y, width, height ); 417 | } catch(e) { 418 | alert("Cannot access local image"); 419 | throw new Error("unable to access local image data: " + e); 420 | return; 421 | } 422 | } 423 | } catch(e) { 424 | alert("Cannot access image"); 425 | throw new Error("unable to access image data: " + e); 426 | } 427 | 428 | var pixels = imageData.data; 429 | 430 | var x, y, i, p, yp, yi, yw, r_sum, g_sum, b_sum, 431 | r_out_sum, g_out_sum, b_out_sum, 432 | r_in_sum, g_in_sum, b_in_sum, 433 | pr, pg, pb, rbs; 434 | 435 | var div = radius + radius + 1; 436 | var w4 = width << 2; 437 | var widthMinus1 = width - 1; 438 | var heightMinus1 = height - 1; 439 | var radiusPlus1 = radius + 1; 440 | var sumFactor = radiusPlus1 * ( radiusPlus1 + 1 ) / 2; 441 | 442 | var stackStart = new BlurStack(); 443 | var stack = stackStart; 444 | for ( i = 1; i < div; i++ ) 445 | { 446 | stack = stack.next = new BlurStack(); 447 | if ( i == radiusPlus1 ) var stackEnd = stack; 448 | } 449 | stack.next = stackStart; 450 | var stackIn = null; 451 | var stackOut = null; 452 | 453 | yw = yi = 0; 454 | 455 | var mul_sum = mul_table[radius]; 456 | var shg_sum = shg_table[radius]; 457 | 458 | for ( y = 0; y < height; y++ ) 459 | { 460 | r_in_sum = g_in_sum = b_in_sum = r_sum = g_sum = b_sum = 0; 461 | 462 | r_out_sum = radiusPlus1 * ( pr = pixels[yi] ); 463 | g_out_sum = radiusPlus1 * ( pg = pixels[yi+1] ); 464 | b_out_sum = radiusPlus1 * ( pb = pixels[yi+2] ); 465 | 466 | r_sum += sumFactor * pr; 467 | g_sum += sumFactor * pg; 468 | b_sum += sumFactor * pb; 469 | 470 | stack = stackStart; 471 | 472 | for( i = 0; i < radiusPlus1; i++ ) 473 | { 474 | stack.r = pr; 475 | stack.g = pg; 476 | stack.b = pb; 477 | stack = stack.next; 478 | } 479 | 480 | for( i = 1; i < radiusPlus1; i++ ) 481 | { 482 | p = yi + (( widthMinus1 < i ? widthMinus1 : i ) << 2 ); 483 | r_sum += ( stack.r = ( pr = pixels[p])) * ( rbs = radiusPlus1 - i ); 484 | g_sum += ( stack.g = ( pg = pixels[p+1])) * rbs; 485 | b_sum += ( stack.b = ( pb = pixels[p+2])) * rbs; 486 | 487 | r_in_sum += pr; 488 | g_in_sum += pg; 489 | b_in_sum += pb; 490 | 491 | stack = stack.next; 492 | } 493 | 494 | 495 | stackIn = stackStart; 496 | stackOut = stackEnd; 497 | for ( x = 0; x < width; x++ ) 498 | { 499 | pixels[yi] = (r_sum * mul_sum) >> shg_sum; 500 | pixels[yi+1] = (g_sum * mul_sum) >> shg_sum; 501 | pixels[yi+2] = (b_sum * mul_sum) >> shg_sum; 502 | 503 | r_sum -= r_out_sum; 504 | g_sum -= g_out_sum; 505 | b_sum -= b_out_sum; 506 | 507 | r_out_sum -= stackIn.r; 508 | g_out_sum -= stackIn.g; 509 | b_out_sum -= stackIn.b; 510 | 511 | p = ( yw + ( ( p = x + radius + 1 ) < widthMinus1 ? p : widthMinus1 ) ) << 2; 512 | 513 | r_in_sum += ( stackIn.r = pixels[p]); 514 | g_in_sum += ( stackIn.g = pixels[p+1]); 515 | b_in_sum += ( stackIn.b = pixels[p+2]); 516 | 517 | r_sum += r_in_sum; 518 | g_sum += g_in_sum; 519 | b_sum += b_in_sum; 520 | 521 | stackIn = stackIn.next; 522 | 523 | r_out_sum += ( pr = stackOut.r ); 524 | g_out_sum += ( pg = stackOut.g ); 525 | b_out_sum += ( pb = stackOut.b ); 526 | 527 | r_in_sum -= pr; 528 | g_in_sum -= pg; 529 | b_in_sum -= pb; 530 | 531 | stackOut = stackOut.next; 532 | 533 | yi += 4; 534 | } 535 | yw += width; 536 | } 537 | 538 | 539 | for ( x = 0; x < width; x++ ) 540 | { 541 | g_in_sum = b_in_sum = r_in_sum = g_sum = b_sum = r_sum = 0; 542 | 543 | yi = x << 2; 544 | r_out_sum = radiusPlus1 * ( pr = pixels[yi]); 545 | g_out_sum = radiusPlus1 * ( pg = pixels[yi+1]); 546 | b_out_sum = radiusPlus1 * ( pb = pixels[yi+2]); 547 | 548 | r_sum += sumFactor * pr; 549 | g_sum += sumFactor * pg; 550 | b_sum += sumFactor * pb; 551 | 552 | stack = stackStart; 553 | 554 | for( i = 0; i < radiusPlus1; i++ ) 555 | { 556 | stack.r = pr; 557 | stack.g = pg; 558 | stack.b = pb; 559 | stack = stack.next; 560 | } 561 | 562 | yp = width; 563 | 564 | for( i = 1; i <= radius; i++ ) 565 | { 566 | yi = ( yp + x ) << 2; 567 | 568 | r_sum += ( stack.r = ( pr = pixels[yi])) * ( rbs = radiusPlus1 - i ); 569 | g_sum += ( stack.g = ( pg = pixels[yi+1])) * rbs; 570 | b_sum += ( stack.b = ( pb = pixels[yi+2])) * rbs; 571 | 572 | r_in_sum += pr; 573 | g_in_sum += pg; 574 | b_in_sum += pb; 575 | 576 | stack = stack.next; 577 | 578 | if( i < heightMinus1 ) 579 | { 580 | yp += width; 581 | } 582 | } 583 | 584 | yi = x; 585 | stackIn = stackStart; 586 | stackOut = stackEnd; 587 | for ( y = 0; y < height; y++ ) 588 | { 589 | p = yi << 2; 590 | pixels[p] = (r_sum * mul_sum) >> shg_sum; 591 | pixels[p+1] = (g_sum * mul_sum) >> shg_sum; 592 | pixels[p+2] = (b_sum * mul_sum) >> shg_sum; 593 | 594 | r_sum -= r_out_sum; 595 | g_sum -= g_out_sum; 596 | b_sum -= b_out_sum; 597 | 598 | r_out_sum -= stackIn.r; 599 | g_out_sum -= stackIn.g; 600 | b_out_sum -= stackIn.b; 601 | 602 | p = ( x + (( ( p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1 ) * width )) << 2; 603 | 604 | r_sum += ( r_in_sum += ( stackIn.r = pixels[p])); 605 | g_sum += ( g_in_sum += ( stackIn.g = pixels[p+1])); 606 | b_sum += ( b_in_sum += ( stackIn.b = pixels[p+2])); 607 | 608 | stackIn = stackIn.next; 609 | 610 | r_out_sum += ( pr = stackOut.r ); 611 | g_out_sum += ( pg = stackOut.g ); 612 | b_out_sum += ( pb = stackOut.b ); 613 | 614 | r_in_sum -= pr; 615 | g_in_sum -= pg; 616 | b_in_sum -= pb; 617 | 618 | stackOut = stackOut.next; 619 | 620 | yi += width; 621 | } 622 | } 623 | 624 | context.putImageData( imageData, top_x, top_y ); 625 | 626 | } 627 | 628 | function BlurStack() 629 | { 630 | this.r = 0; 631 | this.g = 0; 632 | this.b = 0; 633 | this.a = 0; 634 | this.next = null; 635 | } 636 | 637 | var stackBlur = { 638 | image: stackBlurImage, 639 | canvasRGBA: stackBlurCanvasRGBA, 640 | canvasRGB: stackBlurCanvasRGB 641 | }; 642 | 643 | // export as AMD... 644 | if ( typeof define !== 'undefined' && define.amd ) { 645 | define( function () { return stackBlur; }); 646 | } 647 | 648 | // ...or as browserify 649 | else if ( typeof module !== 'undefined' && module.exports ) { 650 | module.exports = stackBlur; 651 | } 652 | 653 | global.stackBlur = stackBlur; 654 | 655 | }( typeof window !== 'undefined' ? window : this )); 656 | -------------------------------------------------------------------------------- /lib/canvg.js: -------------------------------------------------------------------------------- 1 | /* 2 | * canvg.js - Javascript SVG parser and renderer on Canvas 3 | * MIT Licensed 4 | * Gabe Lerner (gabelerner@gmail.com) 5 | * http://code.google.com/p/canvg/ 6 | * 7 | * Requires: rgbcolor.js - http://www.phpied.com/rgb-color-parser-in-javascript/ 8 | */ 9 | (function ( global, factory ) { 10 | 11 | 'use strict'; 12 | 13 | // export as AMD... 14 | if ( typeof define !== 'undefined' && define.amd ) { 15 | define('canvgModule', [ 'rgbcolor', 'stackblur' ], factory ); 16 | } 17 | 18 | // ...or as browserify 19 | else if ( typeof module !== 'undefined' && module.exports ) { 20 | module.exports = factory( require( 'rgbcolor' ), require( 'stackblur' ) ); 21 | } 22 | 23 | global.canvg = factory( global.RGBColor, global.stackBlur ); 24 | 25 | }( typeof window !== 'undefined' ? window : this, function ( RGBColor, stackBlur ) { 26 | 27 | // canvg(target, s) 28 | // empty parameters: replace all 'svg' elements on page with 'canvas' elements 29 | // target: canvas element or the id of a canvas element 30 | // s: svg string, url to svg file, or xml document 31 | // opts: optional hash of options 32 | // ignoreMouse: true => ignore mouse events 33 | // ignoreAnimation: true => ignore animations 34 | // ignoreDimensions: true => does not try to resize canvas 35 | // ignoreClear: true => does not clear canvas 36 | // offsetX: int => draws at a x offset 37 | // offsetY: int => draws at a y offset 38 | // scaleWidth: int => scales horizontally to width 39 | // scaleHeight: int => scales vertically to height 40 | // renderCallback: function => will call the function after the first render is completed 41 | // forceRedraw: function => will call the function on every frame, if it returns true, will redraw 42 | var canvg = function (target, s, opts) { 43 | // no parameters 44 | if (target == null && s == null && opts == null) { 45 | var svgTags = document.querySelectorAll('svg'); 46 | for (var i=0; i~\.\[:]+)/g; 127 | var classRegex = /(\.[^\s\+>~\.\[:]+)/g; 128 | var pseudoElementRegex = /(::[^\s\+>~\.\[:]+|:first-line|:first-letter|:before|:after)/gi; 129 | var pseudoClassWithBracketsRegex = /(:[\w-]+\([^\)]*\))/gi; 130 | var pseudoClassRegex = /(:[^\s\+>~\.\[:]+)/g; 131 | var elementRegex = /([^\s\+>~\.\[:]+)/g; 132 | function getSelectorSpecificity(selector) { 133 | var typeCount = [0, 0, 0]; 134 | var findMatch = function(regex, type) { 135 | var matches = selector.match(regex); 136 | if (matches == null) { 137 | return; 138 | } 139 | typeCount[type] += matches.length; 140 | selector = selector.replace(regex, ' '); 141 | }; 142 | 143 | selector = selector.replace(/:not\(([^\)]*)\)/g, ' $1 '); 144 | selector = selector.replace(/{[^]*/gm, ' '); 145 | findMatch(attributeRegex, 1); 146 | findMatch(idRegex, 0); 147 | findMatch(classRegex, 1); 148 | findMatch(pseudoElementRegex, 2); 149 | findMatch(pseudoClassWithBracketsRegex, 1); 150 | findMatch(pseudoClassRegex, 1); 151 | selector = selector.replace(/[\*\s\+>~]/g, ' '); 152 | selector = selector.replace(/[#\.]/g, ' '); 153 | findMatch(elementRegex, 2); 154 | return typeCount.join(''); 155 | } 156 | 157 | function build(opts) { 158 | var svg = { opts: opts }; 159 | 160 | svg.FRAMERATE = 30; 161 | svg.MAX_VIRTUAL_PIXELS = 30000; 162 | 163 | svg.log = function(msg) {}; 164 | if (svg.opts['log'] == true && typeof(console) != 'undefined') { 165 | svg.log = function(msg) { console.log(msg); }; 166 | }; 167 | 168 | // globals 169 | svg.init = function(ctx) { 170 | var uniqueId = 0; 171 | svg.UniqueId = function () { uniqueId++; return 'canvg' + uniqueId; }; 172 | svg.Definitions = {}; 173 | svg.Styles = {}; 174 | svg.StylesSpecificity = {}; 175 | svg.Animations = []; 176 | svg.Images = []; 177 | svg.ctx = ctx; 178 | svg.ViewPort = new (function () { 179 | this.viewPorts = []; 180 | this.Clear = function() { this.viewPorts = []; } 181 | this.SetCurrent = function(width, height) { this.viewPorts.push({ width: width, height: height }); } 182 | this.RemoveCurrent = function() { this.viewPorts.pop(); } 183 | this.Current = function() { return this.viewPorts[this.viewPorts.length - 1]; } 184 | this.width = function() { return this.Current().width; } 185 | this.height = function() { return this.Current().height; } 186 | this.ComputeSize = function(d) { 187 | if (d != null && typeof(d) == 'number') return d; 188 | if (d == 'x') return this.width(); 189 | if (d == 'y') return this.height(); 190 | return Math.sqrt(Math.pow(this.width(), 2) + Math.pow(this.height(), 2)) / Math.sqrt(2); 191 | } 192 | }); 193 | } 194 | svg.init(); 195 | 196 | // images loaded 197 | svg.ImagesLoaded = function() { 198 | for (var i=0; i]*>/, ''); 240 | var xmlDoc = new ActiveXObject('Microsoft.XMLDOM'); 241 | xmlDoc.async = 'false'; 242 | xmlDoc.loadXML(xml); 243 | return xmlDoc; 244 | } 245 | } 246 | 247 | svg.Property = function(name, value) { 248 | this.name = name; 249 | this.value = value; 250 | } 251 | svg.Property.prototype.getValue = function() { 252 | return this.value; 253 | } 254 | 255 | svg.Property.prototype.hasValue = function() { 256 | return (this.value != null && this.value !== ''); 257 | } 258 | 259 | // return the numerical value of the property 260 | svg.Property.prototype.numValue = function() { 261 | if (!this.hasValue()) return 0; 262 | 263 | var n = parseFloat(this.value); 264 | if ((this.value + '').match(/%$/)) { 265 | n = n / 100.0; 266 | } 267 | return n; 268 | } 269 | 270 | svg.Property.prototype.valueOrDefault = function(def) { 271 | if (this.hasValue()) return this.value; 272 | return def; 273 | } 274 | 275 | svg.Property.prototype.numValueOrDefault = function(def) { 276 | if (this.hasValue()) return this.numValue(); 277 | return def; 278 | } 279 | 280 | // color extensions 281 | // augment the current color value with the opacity 282 | svg.Property.prototype.addOpacity = function(opacityProp) { 283 | var newValue = this.value; 284 | if (opacityProp.value != null && opacityProp.value != '' && typeof(this.value)=='string') { // can only add opacity to colors, not patterns 285 | var color = new RGBColor(this.value); 286 | if (color.ok) { 287 | newValue = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + opacityProp.numValue() + ')'; 288 | } 289 | } 290 | return new svg.Property(this.name, newValue); 291 | } 292 | 293 | // definition extensions 294 | // get the definition from the definitions table 295 | svg.Property.prototype.getDefinition = function() { 296 | var name = this.value.match(/#([^\)'"]+)/); 297 | if (name) { name = name[1]; } 298 | if (!name) { name = this.value; } 299 | return svg.Definitions[name]; 300 | } 301 | 302 | svg.Property.prototype.isUrlDefinition = function() { 303 | return this.value.indexOf('url(') == 0 304 | } 305 | 306 | svg.Property.prototype.getFillStyleDefinition = function(e, opacityProp) { 307 | var def = this.getDefinition(); 308 | 309 | // gradient 310 | if (def != null && def.createGradient) { 311 | return def.createGradient(svg.ctx, e, opacityProp); 312 | } 313 | 314 | // pattern 315 | if (def != null && def.createPattern) { 316 | if (def.getHrefAttribute().hasValue()) { 317 | var pt = def.attribute('patternTransform'); 318 | def = def.getHrefAttribute().getDefinition(); 319 | if (pt.hasValue()) { def.attribute('patternTransform', true).value = pt.value; } 320 | } 321 | return def.createPattern(svg.ctx, e); 322 | } 323 | 324 | return null; 325 | } 326 | 327 | // length extensions 328 | svg.Property.prototype.getDPI = function(viewPort) { 329 | return 96.0; // TODO: compute? 330 | } 331 | 332 | svg.Property.prototype.getEM = function(viewPort) { 333 | var em = 12; 334 | 335 | var fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize); 336 | if (fontSize.hasValue()) em = fontSize.toPixels(viewPort); 337 | 338 | return em; 339 | } 340 | 341 | svg.Property.prototype.getUnits = function() { 342 | var s = this.value+''; 343 | return s.replace(/[0-9\.\-]/g,''); 344 | } 345 | 346 | // get the length as pixels 347 | svg.Property.prototype.toPixels = function(viewPort, processPercent) { 348 | if (!this.hasValue()) return 0; 349 | var s = this.value+''; 350 | if (s.match(/em$/)) return this.numValue() * this.getEM(viewPort); 351 | if (s.match(/ex$/)) return this.numValue() * this.getEM(viewPort) / 2.0; 352 | if (s.match(/px$/)) return this.numValue(); 353 | if (s.match(/pt$/)) return this.numValue() * this.getDPI(viewPort) * (1.0 / 72.0); 354 | if (s.match(/pc$/)) return this.numValue() * 15; 355 | if (s.match(/cm$/)) return this.numValue() * this.getDPI(viewPort) / 2.54; 356 | if (s.match(/mm$/)) return this.numValue() * this.getDPI(viewPort) / 25.4; 357 | if (s.match(/in$/)) return this.numValue() * this.getDPI(viewPort); 358 | if (s.match(/%$/)) return this.numValue() * svg.ViewPort.ComputeSize(viewPort); 359 | var n = this.numValue(); 360 | if (processPercent && n < 1.0) return n * svg.ViewPort.ComputeSize(viewPort); 361 | return n; 362 | } 363 | 364 | // time extensions 365 | // get the time as milliseconds 366 | svg.Property.prototype.toMilliseconds = function() { 367 | if (!this.hasValue()) return 0; 368 | var s = this.value+''; 369 | if (s.match(/s$/)) return this.numValue() * 1000; 370 | if (s.match(/ms$/)) return this.numValue(); 371 | return this.numValue(); 372 | } 373 | 374 | // angle extensions 375 | // get the angle as radians 376 | svg.Property.prototype.toRadians = function() { 377 | if (!this.hasValue()) return 0; 378 | var s = this.value+''; 379 | if (s.match(/deg$/)) return this.numValue() * (Math.PI / 180.0); 380 | if (s.match(/grad$/)) return this.numValue() * (Math.PI / 200.0); 381 | if (s.match(/rad$/)) return this.numValue(); 382 | return this.numValue() * (Math.PI / 180.0); 383 | } 384 | 385 | // text extensions 386 | // get the text baseline 387 | var textBaselineMapping = { 388 | 'baseline': 'alphabetic', 389 | 'before-edge': 'top', 390 | 'text-before-edge': 'top', 391 | 'middle': 'middle', 392 | 'central': 'middle', 393 | 'after-edge': 'bottom', 394 | 'text-after-edge': 'bottom', 395 | 'ideographic': 'ideographic', 396 | 'alphabetic': 'alphabetic', 397 | 'hanging': 'hanging', 398 | 'mathematical': 'alphabetic' 399 | }; 400 | svg.Property.prototype.toTextBaseline = function () { 401 | if (!this.hasValue()) return null; 402 | return textBaselineMapping[this.value]; 403 | } 404 | 405 | // fonts 406 | svg.Font = new (function() { 407 | this.Styles = 'normal|italic|oblique|inherit'; 408 | this.Variants = 'normal|small-caps|inherit'; 409 | this.Weights = 'normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900|inherit'; 410 | 411 | this.CreateFont = function(fontStyle, fontVariant, fontWeight, fontSize, fontFamily, inherit) { 412 | var f = inherit != null ? this.Parse(inherit) : this.CreateFont('', '', '', '', '', svg.ctx.font); 413 | return { 414 | fontFamily: fontFamily || f.fontFamily, 415 | fontSize: fontSize || f.fontSize, 416 | fontStyle: fontStyle || f.fontStyle, 417 | fontWeight: fontWeight || f.fontWeight, 418 | fontVariant: fontVariant || f.fontVariant, 419 | toString: function () { return [this.fontStyle, this.fontVariant, this.fontWeight, this.fontSize, this.fontFamily].join(' ') } 420 | } 421 | } 422 | 423 | var that = this; 424 | this.Parse = function(s) { 425 | var f = {}; 426 | var d = svg.trim(svg.compressSpaces(s || '')).split(' '); 427 | var set = { fontSize: false, fontStyle: false, fontWeight: false, fontVariant: false } 428 | var ff = ''; 429 | for (var i=0; i this.x2) this.x2 = x; 496 | } 497 | 498 | if (y != null) { 499 | if (isNaN(this.y1) || isNaN(this.y2)) { 500 | this.y1 = y; 501 | this.y2 = y; 502 | } 503 | if (y < this.y1) this.y1 = y; 504 | if (y > this.y2) this.y2 = y; 505 | } 506 | } 507 | this.addX = function(x) { this.addPoint(x, null); } 508 | this.addY = function(y) { this.addPoint(null, y); } 509 | 510 | this.addBoundingBox = function(bb) { 511 | this.addPoint(bb.x1, bb.y1); 512 | this.addPoint(bb.x2, bb.y2); 513 | } 514 | 515 | this.addQuadraticCurve = function(p0x, p0y, p1x, p1y, p2x, p2y) { 516 | var cp1x = p0x + 2/3 * (p1x - p0x); // CP1 = QP0 + 2/3 *(QP1-QP0) 517 | var cp1y = p0y + 2/3 * (p1y - p0y); // CP1 = QP0 + 2/3 *(QP1-QP0) 518 | var cp2x = cp1x + 1/3 * (p2x - p0x); // CP2 = CP1 + 1/3 *(QP2-QP0) 519 | var cp2y = cp1y + 1/3 * (p2y - p0y); // CP2 = CP1 + 1/3 *(QP2-QP0) 520 | this.addBezierCurve(p0x, p0y, cp1x, cp2x, cp1y, cp2y, p2x, p2y); 521 | } 522 | 523 | this.addBezierCurve = function(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) { 524 | // from http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html 525 | var p0 = [p0x, p0y], p1 = [p1x, p1y], p2 = [p2x, p2y], p3 = [p3x, p3y]; 526 | this.addPoint(p0[0], p0[1]); 527 | this.addPoint(p3[0], p3[1]); 528 | 529 | for (i=0; i<=1; i++) { 530 | var f = function(t) { 531 | return Math.pow(1-t, 3) * p0[i] 532 | + 3 * Math.pow(1-t, 2) * t * p1[i] 533 | + 3 * (1-t) * Math.pow(t, 2) * p2[i] 534 | + Math.pow(t, 3) * p3[i]; 535 | } 536 | 537 | var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i]; 538 | var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i]; 539 | var c = 3 * p1[i] - 3 * p0[i]; 540 | 541 | if (a == 0) { 542 | if (b == 0) continue; 543 | var t = -c / b; 544 | if (0 < t && t < 1) { 545 | if (i == 0) this.addX(f(t)); 546 | if (i == 1) this.addY(f(t)); 547 | } 548 | continue; 549 | } 550 | 551 | var b2ac = Math.pow(b, 2) - 4 * c * a; 552 | if (b2ac < 0) continue; 553 | var t1 = (-b + Math.sqrt(b2ac)) / (2 * a); 554 | if (0 < t1 && t1 < 1) { 555 | if (i == 0) this.addX(f(t1)); 556 | if (i == 1) this.addY(f(t1)); 557 | } 558 | var t2 = (-b - Math.sqrt(b2ac)) / (2 * a); 559 | if (0 < t2 && t2 < 1) { 560 | if (i == 0) this.addX(f(t2)); 561 | if (i == 1) this.addY(f(t2)); 562 | } 563 | } 564 | } 565 | 566 | this.isPointInBox = function(x, y) { 567 | return (this.x1 <= x && x <= this.x2 && this.y1 <= y && y <= this.y2); 568 | } 569 | 570 | this.addPoint(x1, y1); 571 | this.addPoint(x2, y2); 572 | } 573 | 574 | // transforms 575 | svg.Transform = function(v) { 576 | var that = this; 577 | this.Type = {} 578 | 579 | // translate 580 | this.Type.translate = function(s) { 581 | this.p = svg.CreatePoint(s); 582 | this.apply = function(ctx) { 583 | ctx.translate(this.p.x || 0.0, this.p.y || 0.0); 584 | } 585 | this.unapply = function(ctx) { 586 | ctx.translate(-1.0 * this.p.x || 0.0, -1.0 * this.p.y || 0.0); 587 | } 588 | this.applyToPoint = function(p) { 589 | p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]); 590 | } 591 | } 592 | 593 | // rotate 594 | this.Type.rotate = function(s) { 595 | var a = svg.ToNumberArray(s); 596 | this.angle = new svg.Property('angle', a[0]); 597 | this.cx = a[1] || 0; 598 | this.cy = a[2] || 0; 599 | this.apply = function(ctx) { 600 | ctx.translate(this.cx, this.cy); 601 | ctx.rotate(this.angle.toRadians()); 602 | ctx.translate(-this.cx, -this.cy); 603 | } 604 | this.unapply = function(ctx) { 605 | ctx.translate(this.cx, this.cy); 606 | ctx.rotate(-1.0 * this.angle.toRadians()); 607 | ctx.translate(-this.cx, -this.cy); 608 | } 609 | this.applyToPoint = function(p) { 610 | var a = this.angle.toRadians(); 611 | p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]); 612 | p.applyTransform([Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0]); 613 | p.applyTransform([1, 0, 0, 1, -this.p.x || 0.0, -this.p.y || 0.0]); 614 | } 615 | } 616 | 617 | this.Type.scale = function(s) { 618 | this.p = svg.CreatePoint(s); 619 | this.apply = function(ctx) { 620 | ctx.scale(this.p.x || 1.0, this.p.y || this.p.x || 1.0); 621 | } 622 | this.unapply = function(ctx) { 623 | ctx.scale(1.0 / this.p.x || 1.0, 1.0 / this.p.y || this.p.x || 1.0); 624 | } 625 | this.applyToPoint = function(p) { 626 | p.applyTransform([this.p.x || 0.0, 0, 0, this.p.y || 0.0, 0, 0]); 627 | } 628 | } 629 | 630 | this.Type.matrix = function(s) { 631 | this.m = svg.ToNumberArray(s); 632 | this.apply = function(ctx) { 633 | ctx.transform(this.m[0], this.m[1], this.m[2], this.m[3], this.m[4], this.m[5]); 634 | } 635 | this.unapply = function(ctx) { 636 | var a = this.m[0]; 637 | var b = this.m[2]; 638 | var c = this.m[4]; 639 | var d = this.m[1]; 640 | var e = this.m[3]; 641 | var f = this.m[5]; 642 | var g = 0.0; 643 | var h = 0.0; 644 | var i = 1.0; 645 | var det = 1 / (a*(e*i-f*h)-b*(d*i-f*g)+c*(d*h-e*g)); 646 | ctx.transform( 647 | det*(e*i-f*h), 648 | det*(f*g-d*i), 649 | det*(c*h-b*i), 650 | det*(a*i-c*g), 651 | det*(b*f-c*e), 652 | det*(c*d-a*f) 653 | ); 654 | } 655 | this.applyToPoint = function(p) { 656 | p.applyTransform(this.m); 657 | } 658 | } 659 | 660 | this.Type.SkewBase = function(s) { 661 | this.base = that.Type.matrix; 662 | this.base(s); 663 | this.angle = new svg.Property('angle', s); 664 | } 665 | this.Type.SkewBase.prototype = new this.Type.matrix; 666 | 667 | this.Type.skewX = function(s) { 668 | this.base = that.Type.SkewBase; 669 | this.base(s); 670 | this.m = [1, 0, Math.tan(this.angle.toRadians()), 1, 0, 0]; 671 | } 672 | this.Type.skewX.prototype = new this.Type.SkewBase; 673 | 674 | this.Type.skewY = function(s) { 675 | this.base = that.Type.SkewBase; 676 | this.base(s); 677 | this.m = [1, Math.tan(this.angle.toRadians()), 0, 1, 0, 0]; 678 | } 679 | this.Type.skewY.prototype = new this.Type.SkewBase; 680 | 681 | this.transforms = []; 682 | 683 | this.apply = function(ctx) { 684 | for (var i=0; i=0; i--) { 691 | this.transforms[i].unapply(ctx); 692 | } 693 | } 694 | 695 | this.applyToPoint = function(p) { 696 | for (var i=0; i existingSpecificity) { 865 | this.styles[name] = styles[name]; 866 | this.stylesSpecificity[name] = specificity; 867 | } 868 | } 869 | } 870 | } 871 | } 872 | }; 873 | 874 | if (node != null && node.nodeType == 1) { //ELEMENT_NODE 875 | // add attributes 876 | for (var i=0; i= this.tokens.length - 1; 1328 | } 1329 | 1330 | this.isCommandOrEnd = function() { 1331 | if (this.isEnd()) return true; 1332 | return this.tokens[this.i + 1].match(/^[A-Za-z]$/) != null; 1333 | } 1334 | 1335 | this.isRelativeCommand = function() { 1336 | switch(this.command) 1337 | { 1338 | case 'm': 1339 | case 'l': 1340 | case 'h': 1341 | case 'v': 1342 | case 'c': 1343 | case 's': 1344 | case 'q': 1345 | case 't': 1346 | case 'a': 1347 | case 'z': 1348 | return true; 1349 | break; 1350 | } 1351 | return false; 1352 | } 1353 | 1354 | this.getToken = function() { 1355 | this.i++; 1356 | return this.tokens[this.i]; 1357 | } 1358 | 1359 | this.getScalar = function() { 1360 | return parseFloat(this.getToken()); 1361 | } 1362 | 1363 | this.nextCommand = function() { 1364 | this.previousCommand = this.command; 1365 | this.command = this.getToken(); 1366 | } 1367 | 1368 | this.getPoint = function() { 1369 | var p = new svg.Point(this.getScalar(), this.getScalar()); 1370 | return this.makeAbsolute(p); 1371 | } 1372 | 1373 | this.getAsControlPoint = function() { 1374 | var p = this.getPoint(); 1375 | this.control = p; 1376 | return p; 1377 | } 1378 | 1379 | this.getAsCurrentPoint = function() { 1380 | var p = this.getPoint(); 1381 | this.current = p; 1382 | return p; 1383 | } 1384 | 1385 | this.getReflectedControlPoint = function() { 1386 | if (this.previousCommand.toLowerCase() != 'c' && 1387 | this.previousCommand.toLowerCase() != 's' && 1388 | this.previousCommand.toLowerCase() != 'q' && 1389 | this.previousCommand.toLowerCase() != 't' ){ 1390 | return this.current; 1391 | } 1392 | 1393 | // reflect point 1394 | var p = new svg.Point(2 * this.current.x - this.control.x, 2 * this.current.y - this.control.y); 1395 | return p; 1396 | } 1397 | 1398 | this.makeAbsolute = function(p) { 1399 | if (this.isRelativeCommand()) { 1400 | p.x += this.current.x; 1401 | p.y += this.current.y; 1402 | } 1403 | return p; 1404 | } 1405 | 1406 | this.addMarker = function(p, from, priorTo) { 1407 | // if the last angle isn't filled in because we didn't have this point yet ... 1408 | if (priorTo != null && this.angles.length > 0 && this.angles[this.angles.length-1] == null) { 1409 | this.angles[this.angles.length-1] = this.points[this.points.length-1].angleTo(priorTo); 1410 | } 1411 | this.addMarkerAngle(p, from == null ? null : from.angleTo(p)); 1412 | } 1413 | 1414 | this.addMarkerAngle = function(p, a) { 1415 | this.points.push(p); 1416 | this.angles.push(a); 1417 | } 1418 | 1419 | this.getMarkerPoints = function() { return this.points; } 1420 | this.getMarkerAngles = function() { 1421 | for (var i=0; i 1) { 1556 | rx *= Math.sqrt(l); 1557 | ry *= Math.sqrt(l); 1558 | } 1559 | // cx', cy' 1560 | var s = (largeArcFlag == sweepFlag ? -1 : 1) * Math.sqrt( 1561 | ((Math.pow(rx,2)*Math.pow(ry,2))-(Math.pow(rx,2)*Math.pow(currp.y,2))-(Math.pow(ry,2)*Math.pow(currp.x,2))) / 1562 | (Math.pow(rx,2)*Math.pow(currp.y,2)+Math.pow(ry,2)*Math.pow(currp.x,2)) 1563 | ); 1564 | if (isNaN(s)) s = 0; 1565 | var cpp = new svg.Point(s * rx * currp.y / ry, s * -ry * currp.x / rx); 1566 | // cx, cy 1567 | var centp = new svg.Point( 1568 | (curr.x + cp.x) / 2.0 + Math.cos(xAxisRotation) * cpp.x - Math.sin(xAxisRotation) * cpp.y, 1569 | (curr.y + cp.y) / 2.0 + Math.sin(xAxisRotation) * cpp.x + Math.cos(xAxisRotation) * cpp.y 1570 | ); 1571 | // vector magnitude 1572 | var m = function(v) { return Math.sqrt(Math.pow(v[0],2) + Math.pow(v[1],2)); } 1573 | // ratio between two vectors 1574 | var r = function(u, v) { return (u[0]*v[0]+u[1]*v[1]) / (m(u)*m(v)) } 1575 | // angle between two vectors 1576 | var a = function(u, v) { return (u[0]*v[1] < u[1]*v[0] ? -1 : 1) * Math.acos(r(u,v)); } 1577 | // initial angle 1578 | var a1 = a([1,0], [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry]); 1579 | // angle delta 1580 | var u = [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry]; 1581 | var v = [(-currp.x-cpp.x)/rx,(-currp.y-cpp.y)/ry]; 1582 | var ad = a(u, v); 1583 | if (r(u,v) <= -1) ad = Math.PI; 1584 | if (r(u,v) >= 1) ad = 0; 1585 | 1586 | // for markers 1587 | var dir = 1 - sweepFlag ? 1.0 : -1.0; 1588 | var ah = a1 + dir * (ad / 2.0); 1589 | var halfWay = new svg.Point( 1590 | centp.x + rx * Math.cos(ah), 1591 | centp.y + ry * Math.sin(ah) 1592 | ); 1593 | pp.addMarkerAngle(halfWay, ah - dir * Math.PI / 2); 1594 | pp.addMarkerAngle(cp, ah - dir * Math.PI); 1595 | 1596 | bb.addPoint(cp.x, cp.y); // TODO: this is too naive, make it better 1597 | if (ctx != null) { 1598 | var r = rx > ry ? rx : ry; 1599 | var sx = rx > ry ? 1 : rx / ry; 1600 | var sy = rx > ry ? ry / rx : 1; 1601 | 1602 | ctx.translate(centp.x, centp.y); 1603 | ctx.rotate(xAxisRotation); 1604 | ctx.scale(sx, sy); 1605 | ctx.arc(0, 0, r, a1, a1 + ad, 1 - sweepFlag); 1606 | ctx.scale(1/sx, 1/sy); 1607 | ctx.rotate(-xAxisRotation); 1608 | ctx.translate(-centp.x, -centp.y); 1609 | } 1610 | } 1611 | break; 1612 | case 'Z': 1613 | case 'z': 1614 | if (ctx != null) ctx.closePath(); 1615 | pp.current = pp.start; 1616 | } 1617 | } 1618 | 1619 | return bb; 1620 | } 1621 | 1622 | this.getMarkers = function() { 1623 | var points = this.PathParser.getMarkerPoints(); 1624 | var angles = this.PathParser.getMarkerAngles(); 1625 | 1626 | var markers = []; 1627 | for (var i=0; i 1) this.offset = 1; 1901 | 1902 | var stopColor = this.style('stop-color', true); 1903 | if (stopColor.value === '') stopColor.value = '#000'; 1904 | if (this.style('stop-opacity').hasValue()) stopColor = stopColor.addOpacity(this.style('stop-opacity')); 1905 | this.color = stopColor.value; 1906 | } 1907 | svg.Element.stop.prototype = new svg.Element.ElementBase; 1908 | 1909 | // animation base element 1910 | svg.Element.AnimateBase = function(node) { 1911 | this.base = svg.Element.ElementBase; 1912 | this.base(node); 1913 | 1914 | svg.Animations.push(this); 1915 | 1916 | this.duration = 0.0; 1917 | this.begin = this.attribute('begin').toMilliseconds(); 1918 | this.maxDuration = this.begin + this.attribute('dur').toMilliseconds(); 1919 | 1920 | this.getProperty = function() { 1921 | var attributeType = this.attribute('attributeType').value; 1922 | var attributeName = this.attribute('attributeName').value; 1923 | 1924 | if (attributeType == 'CSS') { 1925 | return this.parent.style(attributeName, true); 1926 | } 1927 | return this.parent.attribute(attributeName, true); 1928 | }; 1929 | 1930 | this.initialValue = null; 1931 | this.initialUnits = ''; 1932 | this.removed = false; 1933 | 1934 | this.calcValue = function() { 1935 | // OVERRIDE ME! 1936 | return ''; 1937 | } 1938 | 1939 | this.update = function(delta) { 1940 | // set initial value 1941 | if (this.initialValue == null) { 1942 | this.initialValue = this.getProperty().value; 1943 | this.initialUnits = this.getProperty().getUnits(); 1944 | } 1945 | 1946 | // if we're past the end time 1947 | if (this.duration > this.maxDuration) { 1948 | // loop for indefinitely repeating animations 1949 | if (this.attribute('repeatCount').value == 'indefinite' 1950 | || this.attribute('repeatDur').value == 'indefinite') { 1951 | this.duration = 0.0 1952 | } 1953 | else if (this.attribute('fill').valueOrDefault('remove') == 'freeze' && !this.frozen) { 1954 | this.frozen = true; 1955 | this.parent.animationFrozen = true; 1956 | this.parent.animationFrozenValue = this.getProperty().value; 1957 | } 1958 | else if (this.attribute('fill').valueOrDefault('remove') == 'remove' && !this.removed) { 1959 | this.removed = true; 1960 | this.getProperty().value = this.parent.animationFrozen ? this.parent.animationFrozenValue : this.initialValue; 1961 | return true; 1962 | } 1963 | return false; 1964 | } 1965 | this.duration = this.duration + delta; 1966 | 1967 | // if we're past the begin time 1968 | var updated = false; 1969 | if (this.begin < this.duration) { 1970 | var newValue = this.calcValue(); // tween 1971 | 1972 | if (this.attribute('type').hasValue()) { 1973 | // for transform, etc. 1974 | var type = this.attribute('type').value; 1975 | newValue = type + '(' + newValue + ')'; 1976 | } 1977 | 1978 | this.getProperty().value = newValue; 1979 | updated = true; 1980 | } 1981 | 1982 | return updated; 1983 | } 1984 | 1985 | this.from = this.attribute('from'); 1986 | this.to = this.attribute('to'); 1987 | this.values = this.attribute('values'); 1988 | if (this.values.hasValue()) this.values.value = this.values.value.split(';'); 1989 | 1990 | // fraction of duration we've covered 1991 | this.progress = function() { 1992 | var ret = { progress: (this.duration - this.begin) / (this.maxDuration - this.begin) }; 1993 | if (this.values.hasValue()) { 1994 | var p = ret.progress * (this.values.value.length - 1); 1995 | var lb = Math.floor(p), ub = Math.ceil(p); 1996 | ret.from = new svg.Property('from', parseFloat(this.values.value[lb])); 1997 | ret.to = new svg.Property('to', parseFloat(this.values.value[ub])); 1998 | ret.progress = (p - lb) / (ub - lb); 1999 | } 2000 | else { 2001 | ret.from = this.from; 2002 | ret.to = this.to; 2003 | } 2004 | return ret; 2005 | } 2006 | } 2007 | svg.Element.AnimateBase.prototype = new svg.Element.ElementBase; 2008 | 2009 | // animate element 2010 | svg.Element.animate = function(node) { 2011 | this.base = svg.Element.AnimateBase; 2012 | this.base(node); 2013 | 2014 | this.calcValue = function() { 2015 | var p = this.progress(); 2016 | 2017 | // tween value linearly 2018 | var newValue = p.from.numValue() + (p.to.numValue() - p.from.numValue()) * p.progress; 2019 | return newValue + this.initialUnits; 2020 | }; 2021 | } 2022 | svg.Element.animate.prototype = new svg.Element.AnimateBase; 2023 | 2024 | // animate color element 2025 | svg.Element.animateColor = function(node) { 2026 | this.base = svg.Element.AnimateBase; 2027 | this.base(node); 2028 | 2029 | this.calcValue = function() { 2030 | var p = this.progress(); 2031 | var from = new RGBColor(p.from.value); 2032 | var to = new RGBColor(p.to.value); 2033 | 2034 | if (from.ok && to.ok) { 2035 | // tween color linearly 2036 | var r = from.r + (to.r - from.r) * p.progress; 2037 | var g = from.g + (to.g - from.g) * p.progress; 2038 | var b = from.b + (to.b - from.b) * p.progress; 2039 | return 'rgb('+parseInt(r,10)+','+parseInt(g,10)+','+parseInt(b,10)+')'; 2040 | } 2041 | return this.attribute('from').value; 2042 | }; 2043 | } 2044 | svg.Element.animateColor.prototype = new svg.Element.AnimateBase; 2045 | 2046 | // animate transform element 2047 | svg.Element.animateTransform = function(node) { 2048 | this.base = svg.Element.AnimateBase; 2049 | this.base(node); 2050 | 2051 | this.calcValue = function() { 2052 | var p = this.progress(); 2053 | 2054 | // tween value linearly 2055 | var from = svg.ToNumberArray(p.from.value); 2056 | var to = svg.ToNumberArray(p.to.value); 2057 | var newValue = ''; 2058 | for (var i=0; i startI && child.attribute('x').hasValue()) break; // new group 2173 | width += child.measureTextRecursive(ctx); 2174 | } 2175 | return -1 * (textAnchor == 'end' ? width : width / 2.0); 2176 | } 2177 | return 0; 2178 | } 2179 | 2180 | this.renderChild = function(ctx, parent, i) { 2181 | var child = parent.children[i]; 2182 | if (child.attribute('x').hasValue()) { 2183 | child.x = child.attribute('x').toPixels('x') + parent.getAnchorDelta(ctx, parent, i); 2184 | if (child.attribute('dx').hasValue()) child.x += child.attribute('dx').toPixels('x'); 2185 | } 2186 | else { 2187 | if (child.attribute('dx').hasValue()) parent.x += child.attribute('dx').toPixels('x'); 2188 | child.x = parent.x; 2189 | } 2190 | parent.x = child.x + child.measureText(ctx); 2191 | 2192 | if (child.attribute('y').hasValue()) { 2193 | child.y = child.attribute('y').toPixels('y'); 2194 | if (child.attribute('dy').hasValue()) child.y += child.attribute('dy').toPixels('y'); 2195 | } 2196 | else { 2197 | if (child.attribute('dy').hasValue()) parent.y += child.attribute('dy').toPixels('y'); 2198 | child.y = parent.y; 2199 | } 2200 | parent.y = child.y; 2201 | 2202 | child.render(ctx); 2203 | 2204 | for (var i=0; i0 && text[i-1]!=' ' && i0 && text[i-1]!=' ' && (i == text.length-1 || text[i+1]==' ')) arabicForm = 'initial'; 2224 | if (typeof(font.glyphs[c]) != 'undefined') { 2225 | glyph = font.glyphs[c][arabicForm]; 2226 | if (glyph == null && font.glyphs[c].type == 'glyph') glyph = font.glyphs[c]; 2227 | } 2228 | } 2229 | else { 2230 | glyph = font.glyphs[c]; 2231 | } 2232 | if (glyph == null) glyph = font.missingGlyph; 2233 | return glyph; 2234 | } 2235 | 2236 | this.renderChildren = function(ctx) { 2237 | var customFont = this.parent.style('font-family').getDefinition(); 2238 | if (customFont != null) { 2239 | var fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize); 2240 | var fontStyle = this.parent.style('font-style').valueOrDefault(svg.Font.Parse(svg.ctx.font).fontStyle); 2241 | var text = this.getText(); 2242 | if (customFont.isRTL) text = text.split("").reverse().join(""); 2243 | 2244 | var dx = svg.ToNumberArray(this.parent.attribute('dx').value); 2245 | for (var i=0; i 0) { return ''; } 2323 | return this.text; 2324 | } 2325 | } 2326 | svg.Element.tspan.prototype = new svg.Element.TextElementBase; 2327 | 2328 | // tref 2329 | svg.Element.tref = function(node) { 2330 | this.base = svg.Element.TextElementBase; 2331 | this.base(node); 2332 | 2333 | this.getText = function() { 2334 | var element = this.getHrefAttribute().getDefinition(); 2335 | if (element != null) return element.children[0].getText(); 2336 | } 2337 | } 2338 | svg.Element.tref.prototype = new svg.Element.TextElementBase; 2339 | 2340 | // a element 2341 | svg.Element.a = function(node) { 2342 | this.base = svg.Element.TextElementBase; 2343 | this.base(node); 2344 | 2345 | this.hasText = node.childNodes.length > 0; 2346 | for (var i=0; i 0) { 2365 | // render as temporary group 2366 | var g = new svg.Element.g(); 2367 | g.children = this.children; 2368 | g.parent = this; 2369 | g.render(ctx); 2370 | } 2371 | } 2372 | 2373 | this.onclick = function() { 2374 | window.open(this.getHrefAttribute().value); 2375 | } 2376 | 2377 | this.onmousemove = function() { 2378 | svg.ctx.canvas.style.cursor = 'pointer'; 2379 | } 2380 | } 2381 | svg.Element.a.prototype = new svg.Element.TextElementBase; 2382 | 2383 | // image element 2384 | svg.Element.image = function(node) { 2385 | this.base = svg.Element.RenderedElementBase; 2386 | this.base(node); 2387 | 2388 | var href = this.getHrefAttribute().value; 2389 | if (href == '') { return; } 2390 | var isSvg = href.match(/\.svg$/) 2391 | 2392 | svg.Images.push(this); 2393 | this.loaded = false; 2394 | if (!isSvg) { 2395 | this.img = document.createElement('img'); 2396 | if (svg.opts['useCORS'] == true) { this.img.crossOrigin = 'Anonymous'; } 2397 | var self = this; 2398 | this.img.onload = function() { self.loaded = true; } 2399 | this.img.onerror = function() { svg.log('ERROR: image "' + href + '" not found'); self.loaded = true; } 2400 | this.img.src = href; 2401 | } 2402 | else { 2403 | this.img = svg.ajax(href); 2404 | this.loaded = true; 2405 | } 2406 | 2407 | this.renderChildren = function(ctx) { 2408 | var x = this.attribute('x').toPixels('x'); 2409 | var y = this.attribute('y').toPixels('y'); 2410 | 2411 | var width = this.attribute('width').toPixels('x'); 2412 | var height = this.attribute('height').toPixels('y'); 2413 | if (width == 0 || height == 0) return; 2414 | 2415 | ctx.save(); 2416 | if (isSvg) { 2417 | ctx.drawSvg(this.img, x, y, width, height); 2418 | } 2419 | else { 2420 | ctx.translate(x, y); 2421 | svg.AspectRatio(ctx, 2422 | this.attribute('preserveAspectRatio').value, 2423 | width, 2424 | this.img.width, 2425 | height, 2426 | this.img.height, 2427 | 0, 2428 | 0); 2429 | ctx.drawImage(this.img, 0, 0); 2430 | } 2431 | ctx.restore(); 2432 | } 2433 | 2434 | this.getBoundingBox = function() { 2435 | var x = this.attribute('x').toPixels('x'); 2436 | var y = this.attribute('y').toPixels('y'); 2437 | var width = this.attribute('width').toPixels('x'); 2438 | var height = this.attribute('height').toPixels('y'); 2439 | return new svg.BoundingBox(x, y, x + width, y + height); 2440 | } 2441 | } 2442 | svg.Element.image.prototype = new svg.Element.RenderedElementBase; 2443 | 2444 | // group element 2445 | svg.Element.g = function(node) { 2446 | this.base = svg.Element.RenderedElementBase; 2447 | this.base(node); 2448 | 2449 | this.getBoundingBox = function() { 2450 | var bb = new svg.BoundingBox(); 2451 | for (var i=0; i 0) { 2507 | var urlStart = srcs[s].indexOf('url'); 2508 | var urlEnd = srcs[s].indexOf(')', urlStart); 2509 | var url = srcs[s].substr(urlStart + 5, urlEnd - urlStart - 6); 2510 | var doc = svg.parseXml(svg.ajax(url)); 2511 | var fonts = doc.getElementsByTagName('font'); 2512 | for (var f=0; f 4 | * @link http://www.phpied.com/rgb-color-parser-in-javascript/ 5 | * @license Use it if you like it 6 | */ 7 | 8 | (function ( global ) { 9 | 10 | function RGBColor(color_string) 11 | { 12 | this.ok = false; 13 | 14 | // strip any leading # 15 | if (color_string.charAt(0) == '#') { // remove # if any 16 | color_string = color_string.substr(1,6); 17 | } 18 | 19 | color_string = color_string.replace(/ /g,''); 20 | color_string = color_string.toLowerCase(); 21 | 22 | // before getting into regexps, try simple matches 23 | // and overwrite the input 24 | var simple_colors = { 25 | aliceblue: 'f0f8ff', 26 | antiquewhite: 'faebd7', 27 | aqua: '00ffff', 28 | aquamarine: '7fffd4', 29 | azure: 'f0ffff', 30 | beige: 'f5f5dc', 31 | bisque: 'ffe4c4', 32 | black: '000000', 33 | blanchedalmond: 'ffebcd', 34 | blue: '0000ff', 35 | blueviolet: '8a2be2', 36 | brown: 'a52a2a', 37 | burlywood: 'deb887', 38 | cadetblue: '5f9ea0', 39 | chartreuse: '7fff00', 40 | chocolate: 'd2691e', 41 | coral: 'ff7f50', 42 | cornflowerblue: '6495ed', 43 | cornsilk: 'fff8dc', 44 | crimson: 'dc143c', 45 | cyan: '00ffff', 46 | darkblue: '00008b', 47 | darkcyan: '008b8b', 48 | darkgoldenrod: 'b8860b', 49 | darkgray: 'a9a9a9', 50 | darkgreen: '006400', 51 | darkkhaki: 'bdb76b', 52 | darkmagenta: '8b008b', 53 | darkolivegreen: '556b2f', 54 | darkorange: 'ff8c00', 55 | darkorchid: '9932cc', 56 | darkred: '8b0000', 57 | darksalmon: 'e9967a', 58 | darkseagreen: '8fbc8f', 59 | darkslateblue: '483d8b', 60 | darkslategray: '2f4f4f', 61 | darkturquoise: '00ced1', 62 | darkviolet: '9400d3', 63 | deeppink: 'ff1493', 64 | deepskyblue: '00bfff', 65 | dimgray: '696969', 66 | dodgerblue: '1e90ff', 67 | feldspar: 'd19275', 68 | firebrick: 'b22222', 69 | floralwhite: 'fffaf0', 70 | forestgreen: '228b22', 71 | fuchsia: 'ff00ff', 72 | gainsboro: 'dcdcdc', 73 | ghostwhite: 'f8f8ff', 74 | gold: 'ffd700', 75 | goldenrod: 'daa520', 76 | gray: '808080', 77 | green: '008000', 78 | greenyellow: 'adff2f', 79 | honeydew: 'f0fff0', 80 | hotpink: 'ff69b4', 81 | indianred : 'cd5c5c', 82 | indigo : '4b0082', 83 | ivory: 'fffff0', 84 | khaki: 'f0e68c', 85 | lavender: 'e6e6fa', 86 | lavenderblush: 'fff0f5', 87 | lawngreen: '7cfc00', 88 | lemonchiffon: 'fffacd', 89 | lightblue: 'add8e6', 90 | lightcoral: 'f08080', 91 | lightcyan: 'e0ffff', 92 | lightgoldenrodyellow: 'fafad2', 93 | lightgrey: 'd3d3d3', 94 | lightgreen: '90ee90', 95 | lightpink: 'ffb6c1', 96 | lightsalmon: 'ffa07a', 97 | lightseagreen: '20b2aa', 98 | lightskyblue: '87cefa', 99 | lightslateblue: '8470ff', 100 | lightslategray: '778899', 101 | lightsteelblue: 'b0c4de', 102 | lightyellow: 'ffffe0', 103 | lime: '00ff00', 104 | limegreen: '32cd32', 105 | linen: 'faf0e6', 106 | magenta: 'ff00ff', 107 | maroon: '800000', 108 | mediumaquamarine: '66cdaa', 109 | mediumblue: '0000cd', 110 | mediumorchid: 'ba55d3', 111 | mediumpurple: '9370d8', 112 | mediumseagreen: '3cb371', 113 | mediumslateblue: '7b68ee', 114 | mediumspringgreen: '00fa9a', 115 | mediumturquoise: '48d1cc', 116 | mediumvioletred: 'c71585', 117 | midnightblue: '191970', 118 | mintcream: 'f5fffa', 119 | mistyrose: 'ffe4e1', 120 | moccasin: 'ffe4b5', 121 | navajowhite: 'ffdead', 122 | navy: '000080', 123 | oldlace: 'fdf5e6', 124 | olive: '808000', 125 | olivedrab: '6b8e23', 126 | orange: 'ffa500', 127 | orangered: 'ff4500', 128 | orchid: 'da70d6', 129 | palegoldenrod: 'eee8aa', 130 | palegreen: '98fb98', 131 | paleturquoise: 'afeeee', 132 | palevioletred: 'd87093', 133 | papayawhip: 'ffefd5', 134 | peachpuff: 'ffdab9', 135 | peru: 'cd853f', 136 | pink: 'ffc0cb', 137 | plum: 'dda0dd', 138 | powderblue: 'b0e0e6', 139 | purple: '800080', 140 | red: 'ff0000', 141 | rosybrown: 'bc8f8f', 142 | royalblue: '4169e1', 143 | saddlebrown: '8b4513', 144 | salmon: 'fa8072', 145 | sandybrown: 'f4a460', 146 | seagreen: '2e8b57', 147 | seashell: 'fff5ee', 148 | sienna: 'a0522d', 149 | silver: 'c0c0c0', 150 | skyblue: '87ceeb', 151 | slateblue: '6a5acd', 152 | slategray: '708090', 153 | snow: 'fffafa', 154 | springgreen: '00ff7f', 155 | steelblue: '4682b4', 156 | tan: 'd2b48c', 157 | teal: '008080', 158 | thistle: 'd8bfd8', 159 | tomato: 'ff6347', 160 | turquoise: '40e0d0', 161 | violet: 'ee82ee', 162 | violetred: 'd02090', 163 | wheat: 'f5deb3', 164 | white: 'ffffff', 165 | whitesmoke: 'f5f5f5', 166 | yellow: 'ffff00', 167 | yellowgreen: '9acd32' 168 | }; 169 | for (var key in simple_colors) { 170 | if (color_string == key) { 171 | color_string = simple_colors[key]; 172 | } 173 | } 174 | // emd of simple type-in colors 175 | 176 | // array of color definition objects 177 | var color_defs = [ 178 | { 179 | re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/, 180 | example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'], 181 | process: function (bits){ 182 | return [ 183 | parseInt(bits[1]), 184 | parseInt(bits[2]), 185 | parseInt(bits[3]) 186 | ]; 187 | } 188 | }, 189 | { 190 | re: /^(\w{2})(\w{2})(\w{2})$/, 191 | example: ['#00ff00', '336699'], 192 | process: function (bits){ 193 | return [ 194 | parseInt(bits[1], 16), 195 | parseInt(bits[2], 16), 196 | parseInt(bits[3], 16) 197 | ]; 198 | } 199 | }, 200 | { 201 | re: /^(\w{1})(\w{1})(\w{1})$/, 202 | example: ['#fb0', 'f0f'], 203 | process: function (bits){ 204 | return [ 205 | parseInt(bits[1] + bits[1], 16), 206 | parseInt(bits[2] + bits[2], 16), 207 | parseInt(bits[3] + bits[3], 16) 208 | ]; 209 | } 210 | } 211 | ]; 212 | 213 | // search through the definitions to find a match 214 | for (var i = 0; i < color_defs.length; i++) { 215 | var re = color_defs[i].re; 216 | var processor = color_defs[i].process; 217 | var bits = re.exec(color_string); 218 | if (bits) { 219 | channels = processor(bits); 220 | this.r = channels[0]; 221 | this.g = channels[1]; 222 | this.b = channels[2]; 223 | this.ok = true; 224 | } 225 | 226 | } 227 | 228 | // validate/cleanup values 229 | this.r = (this.r < 0 || isNaN(this.r)) ? 0 : ((this.r > 255) ? 255 : this.r); 230 | this.g = (this.g < 0 || isNaN(this.g)) ? 0 : ((this.g > 255) ? 255 : this.g); 231 | this.b = (this.b < 0 || isNaN(this.b)) ? 0 : ((this.b > 255) ? 255 : this.b); 232 | 233 | // some getters 234 | this.toRGB = function () { 235 | return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')'; 236 | } 237 | this.toHex = function () { 238 | var r = this.r.toString(16); 239 | var g = this.g.toString(16); 240 | var b = this.b.toString(16); 241 | if (r.length == 1) r = '0' + r; 242 | if (g.length == 1) g = '0' + g; 243 | if (b.length == 1) b = '0' + b; 244 | return '#' + r + g + b; 245 | } 246 | 247 | // help 248 | this.getHelpXML = function () { 249 | 250 | var examples = new Array(); 251 | // add regexps 252 | for (var i = 0; i < color_defs.length; i++) { 253 | var example = color_defs[i].example; 254 | for (var j = 0; j < example.length; j++) { 255 | examples[examples.length] = example[j]; 256 | } 257 | } 258 | // add type-in colors 259 | for (var sc in simple_colors) { 260 | examples[examples.length] = sc; 261 | } 262 | 263 | var xml = document.createElement('ul'); 264 | xml.setAttribute('id', 'rgbcolor-examples'); 265 | for (var i = 0; i < examples.length; i++) { 266 | try { 267 | var list_item = document.createElement('li'); 268 | var list_color = new RGBColor(examples[i]); 269 | var example_div = document.createElement('div'); 270 | example_div.style.cssText = 271 | 'margin: 3px; ' 272 | + 'border: 1px solid black; ' 273 | + 'background:' + list_color.toHex() + '; ' 274 | + 'color:' + list_color.toHex() 275 | ; 276 | example_div.appendChild(document.createTextNode('test')); 277 | var list_item_value = document.createTextNode( 278 | ' ' + examples[i] + ' -> ' + list_color.toRGB() + ' -> ' + list_color.toHex() 279 | ); 280 | list_item.appendChild(example_div); 281 | list_item.appendChild(list_item_value); 282 | xml.appendChild(list_item); 283 | 284 | } catch(e){} 285 | } 286 | return xml; 287 | 288 | } 289 | 290 | } 291 | 292 | // export as AMD... 293 | if ( typeof define !== 'undefined' && define.amd ) { 294 | define( function () { return RGBColor; }); 295 | } 296 | 297 | // ...or as browserify 298 | else if ( typeof module !== 'undefined' && module.exports ) { 299 | module.exports = RGBColor; 300 | } 301 | 302 | global.RGBColor = RGBColor; 303 | 304 | }( typeof window !== 'undefined' ? window : this )); 305 | -------------------------------------------------------------------------------- /phosphorus.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "follow_symlinks": true, 6 | "path": "." 7 | } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /player.css: -------------------------------------------------------------------------------- 1 | .progress-bar { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | bottom: 0; 6 | width: 0; 7 | background: #cde; 8 | -webkit-transition: .3s; 9 | -moz-transition: .3s; 10 | -o-transition: .3s; 11 | transition: .3s; 12 | } 13 | .light-content .progress-bar { 14 | background: #468; 15 | } 16 | .progress-bar.error { 17 | background: #ecc; 18 | } 19 | .light-content .progress-bar.error { 20 | background: #844; 21 | } 22 | .controls { 23 | position: relative; 24 | height: 32px; 25 | } 26 | .controls span, 27 | .project-link { 28 | width: 32px; 29 | height: 32px; 30 | float: right; 31 | cursor: pointer; 32 | text-align: center; 33 | opacity: .4; 34 | background-image: url(icons.svg); 35 | text-decoration: none; 36 | } 37 | .controls .flag { 38 | background-position: 0 0; 39 | } 40 | .controls .stop { 41 | background-position: -96px 0; 42 | } 43 | .controls .pause { 44 | background-position: -32px 0; 45 | } 46 | .controls .play { 47 | background-position: -64px 0; 48 | } 49 | .controls .full-screen { 50 | float: left; 51 | background-position: -128px 0; 52 | } 53 | .controls span:active, 54 | .project-link:active { 55 | opacity: 1 !important; 56 | } 57 | .controls .turbo { 58 | float: right; 59 | display: none; 60 | cursor: default; 61 | color: rgba(0, 0, 0, .4); 62 | font: 500 12px/32px Helvetica Neue, Helvetica, Arial, sans-serif; 63 | padding: 0 8px; 64 | } 65 | .player { 66 | box-shadow: 0 0 0 1px rgba(0, 0, 0, .4); 67 | width: 480px; 68 | height: 360px; 69 | position: relative; 70 | -webkit-transform-origin: 0 0; 71 | -moz-transform-origin: 0 0; 72 | -ms-transform-origin: 0 0; 73 | -o-transform-origin: 0 0; 74 | transform-origin: 0 0; 75 | } 76 | .light-content .player { 77 | box-shadow: 0 0 0 1px rgba(255, 255, 255, .4); 78 | } 79 | 80 | .internal-error { 81 | color: rgba(128, 0, 0, .6); 82 | font: 500 12px Helvetica Neue, Helvetica, Arial, sans-serif; 83 | padding: 8px; 84 | display: none; 85 | } 86 | .internal-error a { 87 | color: rgba(128, 0, 0, .8); 88 | } 89 | 90 | .fs { 91 | background: #000; 92 | width: 100%; 93 | height: 100%; 94 | } 95 | .fs body { 96 | margin: 0; 97 | padding: 0; 98 | overflow: hidden; 99 | } 100 | .fs .title, 101 | .fs section { 102 | display: none; 103 | } 104 | .fs .controls span, 105 | .light-content .controls span { 106 | background-position-y: -32px; 107 | opacity: .6; 108 | } 109 | .light-content .controls .full-screen { 110 | background-position-y: -64px; 111 | } 112 | .fs .light-content .controls .full-screen { 113 | background-position-y: -32px; 114 | } 115 | .fs .controls .turbo, 116 | .light-content .controls .turbo { 117 | color: rgba(255, 255, 255, .6); 118 | } 119 | .fs .player { 120 | box-shadow: none; 121 | } 122 | -------------------------------------------------------------------------------- /player.js: -------------------------------------------------------------------------------- 1 | P.player = (function() { 2 | 'use strict'; 3 | 4 | var stage; 5 | var frameId = null; 6 | var isFullScreen = false; 7 | 8 | var progressBar = document.querySelector('.progress-bar'); 9 | var player = document.querySelector('.player'); 10 | var projectLink = document.querySelector('.project-link'); 11 | var bugLink = document.querySelector('#bug-link'); 12 | 13 | var controls = document.querySelector('.controls'); 14 | var flag = document.querySelector('.flag'); 15 | var turbo = document.querySelector('.turbo'); 16 | var pause = document.querySelector('.pause'); 17 | var stop = document.querySelector('.stop'); 18 | var fullScreen = document.querySelector('.full-screen'); 19 | 20 | var error = document.querySelector('.internal-error'); 21 | var errorBugLink = document.querySelector('#error-bug-link'); 22 | 23 | var flagTouchTimeout; 24 | function flagTouchStart() { 25 | flagTouchTimeout = setTimeout(function() { 26 | turboClick(); 27 | flagTouchTimeout = true; 28 | }, 500); 29 | } 30 | function turboClick() { 31 | stage.isTurbo = !stage.isTurbo; 32 | flag.title = stage.isTurbo ? 'Turbo mode enabled. Shift+click to disable.' : 'Shift+click to enable turbo mode.'; 33 | turbo.style.display = stage.isTurbo ? 'block' : 'none'; 34 | } 35 | function flagClick(e) { 36 | if (!stage) return; 37 | if (flagTouchTimeout === true) return; 38 | if (flagTouchTimeout) { 39 | clearTimeout(flagTouchTimeout); 40 | } 41 | if (e.shiftKey) { 42 | turboClick(); 43 | } else { 44 | stage.start(); 45 | pause.className = 'pause'; 46 | stage.stopAll(); 47 | stage.triggerGreenFlag(); 48 | } 49 | stage.focus(); 50 | e.preventDefault(); 51 | } 52 | 53 | function pauseClick(e) { 54 | if (!stage) return; 55 | if (stage.isRunning) { 56 | stage.pause(); 57 | pause.className = 'play'; 58 | } else { 59 | stage.start(); 60 | pause.className = 'pause'; 61 | } 62 | stage.focus(); 63 | e.preventDefault(); 64 | } 65 | 66 | function stopClick(e) { 67 | if (!stage) return; 68 | stage.start(); 69 | pause.className = 'pause'; 70 | stage.stopAll(); 71 | stage.focus(); 72 | e.preventDefault(); 73 | } 74 | 75 | function fullScreenClick(e) { 76 | if (e) e.preventDefault(); 77 | if (!stage) return; 78 | document.documentElement.classList.toggle('fs'); 79 | isFullScreen = !isFullScreen; 80 | if (!e || !e.shiftKey) { 81 | if (isFullScreen) { 82 | var el = document.documentElement; 83 | if (el.requestFullScreenWithKeys) { 84 | el.requestFullScreenWithKeys(); 85 | } else if (el.webkitRequestFullScreen) { 86 | el.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT); 87 | } 88 | } else { 89 | if (document.exitFullscreen) { 90 | document.exitFullscreen(); 91 | } else if (document.mozCancelFullScreen) { 92 | document.mozCancelFullScreen(); 93 | } else if (document.webkitCancelFullScreen) { 94 | document.webkitCancelFullScreen(); 95 | } 96 | } 97 | } 98 | if (!isFullScreen) { 99 | document.body.style.width = 100 | document.body.style.height = 101 | document.body.style.marginLeft = 102 | document.body.style.marginTop = ''; 103 | } 104 | updateFullScreen(); 105 | if (!stage.isRunning) { 106 | stage.draw(); 107 | } 108 | stage.focus(); 109 | } 110 | 111 | function exitFullScreen(e) { 112 | if (isFullScreen && e.keyCode === 27) { 113 | fullScreenClick(e); 114 | } 115 | } 116 | 117 | function updateFullScreen() { 118 | if (!stage) return; 119 | if (isFullScreen) { 120 | window.scrollTo(0, 0); 121 | var padding = 8; 122 | var w = window.innerWidth - padding * 2; 123 | var h = window.innerHeight - padding - controls.offsetHeight; 124 | w = Math.min(w, h / .75); 125 | h = w * .75 + controls.offsetHeight; 126 | document.body.style.width = w + 'px'; 127 | document.body.style.height = h + 'px'; 128 | document.body.style.marginLeft = (window.innerWidth - w) / 2 + 'px'; 129 | document.body.style.marginTop = (window.innerHeight - h - padding) / 2 + 'px'; 130 | stage.setZoom(w / 480); 131 | } else { 132 | stage.setZoom(1); 133 | } 134 | } 135 | 136 | function preventDefault(e) { 137 | e.preventDefault(); 138 | } 139 | 140 | window.addEventListener('resize', updateFullScreen); 141 | 142 | if (P.hasTouchEvents) { 143 | flag.addEventListener('touchstart', flagTouchStart); 144 | flag.addEventListener('touchend', flagClick); 145 | pause.addEventListener('touchend', pauseClick); 146 | stop.addEventListener('touchend', stopClick); 147 | fullScreen.addEventListener('touchend', fullScreenClick); 148 | 149 | flag.addEventListener('touchstart', preventDefault); 150 | pause.addEventListener('touchstart', preventDefault); 151 | stop.addEventListener('touchstart', preventDefault); 152 | fullScreen.addEventListener('touchstart', preventDefault); 153 | 154 | document.addEventListener('touchmove', function(e) { 155 | if (isFullScreen) e.preventDefault(); 156 | }); 157 | } else { 158 | flag.addEventListener('click', flagClick); 159 | pause.addEventListener('click', pauseClick); 160 | stop.addEventListener('click', stopClick); 161 | fullScreen.addEventListener('click', fullScreenClick); 162 | } 163 | 164 | document.addEventListener("fullscreenchange", function () { 165 | if (isFullScreen !== document.fullscreen) fullScreenClick(); 166 | }); 167 | document.addEventListener("mozfullscreenchange", function () { 168 | if (isFullScreen !== document.mozFullScreen) fullScreenClick(); 169 | }); 170 | document.addEventListener("webkitfullscreenchange", function () { 171 | if (isFullScreen !== document.webkitIsFullScreen) fullScreenClick(); 172 | }); 173 | 174 | function load(id, cb, titleCallback) { 175 | P.player.projectId = id; 176 | P.player.projectURL = id ? 'https://scratch.mit.edu/projects/' + id + '/' : ''; 177 | 178 | if (stage) stage.destroy(); 179 | while (player.firstChild) player.removeChild(player.lastChild); 180 | turbo.style.display = 'none'; 181 | error.style.display = 'none'; 182 | pause.className = 'pause'; 183 | progressBar.style.display = 'none'; 184 | 185 | if (id) { 186 | showProgress(P.IO.loadScratchr2Project(id), cb); 187 | P.IO.loadScratchr2ProjectTitle(id, function(title) { 188 | if (titleCallback) titleCallback(P.player.projectTitle = title); 189 | }); 190 | } else { 191 | if (titleCallback) setTimeout(function() { 192 | titleCallback(''); 193 | }); 194 | } 195 | } 196 | 197 | function showError(e) { 198 | error.style.display = 'block'; 199 | errorBugLink.href = 'https://github.com/nathan/phosphorus/issues/new?title=' + encodeURIComponent(P.player.projectTitle || P.player.projectURL) + '&body=' + encodeURIComponent('\n\n\n' + P.player.projectURL + '\nhttp://phosphorus.github.io/#' + P.player.projectId + '\n' + navigator.userAgent + (e.stack ? '\n\n```\n' + e.stack + '\n```' : '')); 200 | console.error(e.stack); 201 | } 202 | 203 | function showProgress(request, loadCallback) { 204 | progressBar.style.display = 'none'; 205 | setTimeout(function() { 206 | progressBar.style.width = '10%'; 207 | progressBar.className = 'progress-bar'; 208 | progressBar.style.opacity = 1; 209 | progressBar.style.display = 'block'; 210 | }); 211 | request.onload = function(s) { 212 | progressBar.style.width = '100%'; 213 | setTimeout(function() { 214 | progressBar.style.opacity = 0; 215 | setTimeout(function() { 216 | progressBar.style.display = 'none'; 217 | }, 300); 218 | }, 100); 219 | 220 | var zoom = stage ? stage.zoom : 1; 221 | window.stage = stage = s; 222 | stage.start(); 223 | stage.setZoom(zoom); 224 | 225 | stage.root.addEventListener('keydown', exitFullScreen); 226 | stage.handleError = showError; 227 | 228 | player.appendChild(stage.root); 229 | stage.focus(); 230 | if (loadCallback) { 231 | loadCallback(stage); 232 | loadCallback = null; 233 | } 234 | }; 235 | request.onerror = function(e) { 236 | progressBar.style.width = '100%'; 237 | progressBar.className = 'progress-bar error'; 238 | console.error(e.stack); 239 | }; 240 | request.onprogress = function(e) { 241 | progressBar.style.width = (10 + e.loaded / e.total * 90) + '%'; 242 | }; 243 | } 244 | 245 | return { 246 | load: load, 247 | showProgress: showProgress 248 | }; 249 | 250 | }()); 251 | --------------------------------------------------------------------------------