PageRenderTime 161ms CodeModel.GetById 31ms RepoModel.GetById 1ms app.codeStats 3ms

/common/static/js/vendor/ova/openseadragon.js

https://github.com/Edraak/edx-platform
JavaScript | 12946 lines | 7061 code | 1652 blank | 4233 comment | 1160 complexity | a6232d4bfe59231317a18ea21a13ffa4 MD5 | raw file
Possible License(s): AGPL-3.0, BSD-3-Clause
  1. //! OpenSeadragon 0.9.131
  2. //! Built on 2013-11-05
  3. //! Git commit: v0.9.131-144-gdbb7cee
  4. //! http://openseadragon.github.io
  5. //! License: http://openseadragon.github.io/license/
  6. /*
  7. * OpenSeadragon
  8. *
  9. * Copyright (C) 2009 CodePlex Foundation
  10. * Copyright (C) 2010-2013 OpenSeadragon contributors
  11. *
  12. * Redistribution and use in source and binary forms, with or without
  13. * modification, are permitted provided that the following conditions are
  14. * met:
  15. *
  16. * - Redistributions of source code must retain the above copyright notice,
  17. * this list of conditions and the following disclaimer.
  18. *
  19. * - Redistributions in binary form must reproduce the above copyright
  20. * notice, this list of conditions and the following disclaimer in the
  21. * documentation and/or other materials provided with the distribution.
  22. *
  23. * - Neither the name of CodePlex Foundation nor the names of its
  24. * contributors may be used to endorse or promote products derived from
  25. * this software without specific prior written permission.
  26. *
  27. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  28. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  29. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  30. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  31. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  32. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  33. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  34. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  35. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  36. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  37. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  38. */
  39. /*
  40. * Portions of this source file taken from jQuery:
  41. *
  42. * Copyright 2011 John Resig
  43. *
  44. * Permission is hereby granted, free of charge, to any person obtaining
  45. * a copy of this software and associated documentation files (the
  46. * "Software"), to deal in the Software without restriction, including
  47. * without limitation the rights to use, copy, modify, merge, publish,
  48. * distribute, sublicense, and/or sell copies of the Software, and to
  49. * permit persons to whom the Software is furnished to do so, subject to
  50. * the following conditions:
  51. *
  52. * The above copyright notice and this permission notice shall be
  53. * included in all copies or substantial portions of the Software.
  54. *
  55. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  56. * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  57. * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  58. * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  59. * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  60. * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  61. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  62. */
  63. /*
  64. * Portions of this source file taken from mattsnider.com:
  65. *
  66. * Copyright (c) 2006-2013 Matt Snider
  67. *
  68. * Permission is hereby granted, free of charge, to any person obtaining a
  69. * copy of this software and associated documentation files (the "Software"),
  70. * to deal in the Software without restriction, including without limitation
  71. * the rights to use, copy, modify, merge, publish, distribute, sublicense,
  72. * and/or sell copies of the Software, and to permit persons to whom the
  73. * Software is furnished to do so, subject to the following conditions:
  74. *
  75. * The above copyright notice and this permission notice shall be included
  76. * in all copies or substantial portions of the Software.
  77. *
  78. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  79. * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  80. * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  81. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  82. * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
  83. * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
  84. * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  85. */
  86. /**
  87. * @version OpenSeadragon 0.9.131
  88. *
  89. * @fileOverview
  90. * <h2>
  91. * <strong>
  92. * OpenSeadragon - Javascript Deep Zooming
  93. * </strong>
  94. * </h2>
  95. * <p>
  96. * OpenSeadragon is provides an html interface for creating
  97. * deep zoom user interfaces. The simplest examples include deep
  98. * zoom for large resolution images, and complex examples include
  99. * zoomable map interfaces driven by SVG files.
  100. * </p>
  101. */
  102. /**
  103. * The root namespace for OpenSeadragon, this function also serves as a single
  104. * point of instantiation for an {@link OpenSeadragon.Viewer}, including all
  105. * combinations of out-of-the-box configurable features. All utility methods
  106. * and classes are defined on or below this namespace.
  107. *
  108. * @namespace
  109. * @function
  110. * @name OpenSeadragon
  111. * @exports $ as OpenSeadragon
  112. *
  113. * @param {Object} options All required and optional settings for instantiating
  114. * a new instance of an OpenSeadragon image viewer.
  115. *
  116. * @param {String} options.xmlPath
  117. * DEPRECATED. A relative path to load a DZI file from the server.
  118. * Prefer the newer options.tileSources.
  119. *
  120. * @param {Array|String|Function|Object[]|Array[]|String[]|Function[]} options.tileSources
  121. * As an Array, the tileSource can hold either be all Objects or mixed
  122. * types of Arrays of Objects, String, Function. When a value is a String,
  123. * the tileSource is used to create a {@link OpenSeadragon.DziTileSource}.
  124. * When a value is a Function, the function is used to create a new
  125. * {@link OpenSeadragon.TileSource} whose abstract method
  126. * getUrl( level, x, y ) is implemented by the function. Finally, when it
  127. * is an Array of objects, it is used to create a
  128. * {@link OpenSeadragon.LegacyTileSource}.
  129. *
  130. * @param {Boolean} [options.debugMode=true]
  131. * Currently does nothing. TODO: provide an in-screen panel providing event
  132. * detail feedback.
  133. *
  134. * @param {Number} [options.animationTime=1.5]
  135. * Specifies the animation duration per each {@link OpenSeadragon.Spring}
  136. * which occur when the image is dragged or zoomed.
  137. *
  138. * @param {Number} [options.blendTime=0.5]
  139. * Specifies the duration of animation as higher or lower level tiles are
  140. * replacing the existing tile.
  141. *
  142. * @param {Boolean} [options.alwaysBlend=false]
  143. * Forces the tile to always blend. By default the tiles skip blending
  144. * when the blendTime is surpassed and the current animation frame would
  145. * not complete the blend.
  146. *
  147. * @param {Boolean} [options.autoHideControls=true]
  148. * If the user stops interacting with the viewport, fade the navigation
  149. * controls. Useful for presentation since the controls are by default
  150. * floated on top of the image the user is viewing.
  151. *
  152. * @param {Boolean} [options.immediateRender=false]
  153. * Render the best closest level first, ignoring the lowering levels which
  154. * provide the effect of very blurry to sharp. It is recommended to change
  155. * setting to true for mobile devices.
  156. *
  157. * @param {Boolean} [options.wrapHorizontal=false]
  158. * Set to true to force the image to wrap horizontally within the viewport.
  159. * Useful for maps or images representing the surface of a sphere or cylinder.
  160. *
  161. * @param {Boolean} [options.wrapVertical=false]
  162. * Set to true to force the image to wrap vertically within the viewport.
  163. * Useful for maps or images representing the surface of a sphere or cylinder.
  164. *
  165. * @param {Number} [options.minZoomImageRatio=0.8]
  166. * The minimum percentage ( expressed as a number between 0 and 1 ) of
  167. * the viewport height or width at which the zoom out will be constrained.
  168. * Setting it to 0, for example will allow you to zoom out infinitly.
  169. *
  170. * @param {Number} [options.maxZoomPixelRatio=2]
  171. * The maximum ratio to allow a zoom-in to affect the highest level pixel
  172. * ratio. This can be set to Infinity to allow 'infinite' zooming into the
  173. * image though it is less effective visually if the HTML5 Canvas is not
  174. * availble on the viewing device.
  175. *
  176. * @param {Number} [options.visibilityRatio=0.5]
  177. * The percentage ( as a number from 0 to 1 ) of the source image which
  178. * must be kept within the viewport. If the image is dragged beyond that
  179. * limit, it will 'bounce' back until the minimum visibility ration is
  180. * achieved. Setting this to 0 and wrapHorizontal ( or wrapVertical ) to
  181. * true will provide the effect of an infinitely scrolling viewport.
  182. *
  183. * @param {Number} [options.springStiffness=5.0]
  184. *
  185. * @param {Number} [options.imageLoaderLimit=0]
  186. * The maximum number of image requests to make concurrently. By default
  187. * it is set to 0 allowing the browser to make the maximum number of
  188. * image requests in parallel as allowed by the browsers policy.
  189. *
  190. * @param {Number} [options.clickTimeThreshold=200]
  191. * If multiple mouse clicks occurs within less than this number of
  192. * milliseconds, treat them as a single click.
  193. *
  194. * @param {Number} [options.clickDistThreshold=5]
  195. * If a mouse or touch drag occurs and the distance to the starting drag
  196. * point is less than this many pixels, ignore the drag event.
  197. *
  198. * @param {Number} [options.zoomPerClick=2.0]
  199. * The "zoom distance" per mouse click or touch tap.
  200. *
  201. * @param {Number} [options.zoomPerScroll=1.2]
  202. * The "zoom distance" per mouse scroll or touch pinch.
  203. *
  204. * @param {Number} [options.zoomPerSecond=2.0]
  205. * The number of seconds to animate a single zoom event over.
  206. *
  207. * @param {Boolean} [options.showNavigationControl=true]
  208. * Set to false to prevent the appearance of the default navigation controls.
  209. *
  210. * @param {Boolean} [options.showNavigator=false]
  211. * Set to true to make the navigator minimap appear.
  212. *
  213. * @param {Boolean} [options.navigatorId=navigator-GENERATED DATE]
  214. * Set the ID of a div to hold the navigator minimap. If one is not specified,
  215. * one will be generated and placed on top of the main image
  216. *
  217. * @param {Number} [options.controlsFadeDelay=2000]
  218. * The number of milliseconds to wait once the user has stopped interacting
  219. * with the interface before begining to fade the controls. Assumes
  220. * showNavigationControl and autoHideControls are both true.
  221. *
  222. * @param {Number} [options.controlsFadeLength=1500]
  223. * The number of milliseconds to animate the controls fading out.
  224. *
  225. * @param {Number} [options.maxImageCacheCount=100]
  226. * The max number of images we should keep in memory (per drawer).
  227. *
  228. * @param {Number} [options.minPixelRatio=0.5]
  229. * The higher the minPixelRatio, the lower the quality of the image that
  230. * is considered sufficient to stop rendering a given zoom level. For
  231. * example, if you are targeting mobile devices with less bandwith you may
  232. * try setting this to 1.5 or higher.
  233. *
  234. * @param {Boolean} [options.mouseNavEnabled=true]
  235. * Is the user able to interact with the image via mouse or touch. Default
  236. * interactions include draging the image in a plane, and zooming in toward
  237. * and away from the image.
  238. *
  239. * @param {Boolean} [options.preserveViewport=false]
  240. * If the viewer has been configured with a sequence of tile sources, then
  241. * normally navigating to through each image resets the viewport to 'home'
  242. * position. If preserveViewport is set to true, then the viewport position
  243. * is preserved when navigating between images in the sequence.
  244. *
  245. * @param {String} [options.prefixUrl='/images/']
  246. * Prepends the prefixUrl to navImages paths, which is very useful
  247. * since the default paths are rarely useful for production
  248. * environments.
  249. *
  250. * @param {Object} [options.navImages=]
  251. * An object with a property for each button or other built-in navigation
  252. * control, eg the current 'zoomIn', 'zoomOut', 'home', and 'fullpage'.
  253. * Each of those in turn provides an image path for each state of the botton
  254. * or navigation control, eg 'REST', 'GROUP', 'HOVER', 'PRESS'. Finally the
  255. * image paths, by default assume there is a folder on the servers root path
  256. * called '/images', eg '/images/zoomin_rest.png'. If you need to adjust
  257. * these paths, prefer setting the option.prefixUrl rather than overriding
  258. * every image path directly through this setting.
  259. *
  260. * @param {Boolean} [options.navPrevNextWrap=false]
  261. * If the 'previous' button will wrap to the last image when viewing the first
  262. * image and if the 'next' button will wrap to the first image when viewing
  263. * the last image.
  264. *
  265. * @returns {OpenSeadragon.Viewer}
  266. */
  267. window.OpenSeadragon = window.OpenSeadragon || function( options ){
  268. return new OpenSeadragon.Viewer( options );
  269. };
  270. (function( $ ){
  271. /**
  272. * Taken from jquery 1.6.1
  273. * [[Class]] -> type pairs
  274. * @private
  275. */
  276. var class2type = {
  277. '[object Boolean]': 'boolean',
  278. '[object Number]': 'number',
  279. '[object String]': 'string',
  280. '[object Function]': 'function',
  281. '[object Array]': 'array',
  282. '[object Date]': 'date',
  283. '[object RegExp]': 'regexp',
  284. '[object Object]': 'object'
  285. },
  286. // Save a reference to some core methods
  287. toString = Object.prototype.toString,
  288. hasOwn = Object.prototype.hasOwnProperty;
  289. /**
  290. * Taken from jQuery 1.6.1
  291. * @name $.isFunction
  292. * @function
  293. * @see <a href='http://www.jquery.com/'>jQuery</a>
  294. */
  295. $.isFunction = function( obj ) {
  296. return $.type(obj) === "function";
  297. };
  298. /**
  299. * Taken from jQuery 1.6.1
  300. * @name $.isArray
  301. * @function
  302. * @see <a href='http://www.jquery.com/'>jQuery</a>
  303. */
  304. $.isArray = Array.isArray || function( obj ) {
  305. return $.type(obj) === "array";
  306. };
  307. /**
  308. * A crude way of determining if an object is a window.
  309. * Taken from jQuery 1.6.1
  310. * @name $.isWindow
  311. * @function
  312. * @see <a href='http://www.jquery.com/'>jQuery</a>
  313. */
  314. $.isWindow = function( obj ) {
  315. return obj && typeof obj === "object" && "setInterval" in obj;
  316. };
  317. /**
  318. * Taken from jQuery 1.6.1
  319. * @name $.type
  320. * @function
  321. * @see <a href='http://www.jquery.com/'>jQuery</a>
  322. */
  323. $.type = function( obj ) {
  324. return ( obj === null ) || ( obj === undefined ) ?
  325. String( obj ) :
  326. class2type[ toString.call(obj) ] || "object";
  327. };
  328. /**
  329. * Taken from jQuery 1.6.1
  330. * @name $.isPlainObject
  331. * @function
  332. * @see <a href='http://www.jquery.com/'>jQuery</a>
  333. */
  334. $.isPlainObject = function( obj ) {
  335. // Must be an Object.
  336. // Because of IE, we also have to check the presence of the constructor property.
  337. // Make sure that DOM nodes and window objects don't pass through, as well
  338. if ( !obj || OpenSeadragon.type(obj) !== "object" || obj.nodeType || $.isWindow( obj ) ) {
  339. return false;
  340. }
  341. // Not own constructor property must be Object
  342. if ( obj.constructor &&
  343. !hasOwn.call(obj, "constructor") &&
  344. !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
  345. return false;
  346. }
  347. // Own properties are enumerated firstly, so to speed up,
  348. // if last one is own, then all properties are own.
  349. var key;
  350. for ( key in obj ) {}
  351. return key === undefined || hasOwn.call( obj, key );
  352. };
  353. /**
  354. * Taken from jQuery 1.6.1
  355. * @name $.isEmptyObject
  356. * @function
  357. * @see <a href='http://www.jquery.com/'>jQuery</a>
  358. */
  359. $.isEmptyObject = function( obj ) {
  360. for ( var name in obj ) {
  361. return false;
  362. }
  363. return true;
  364. };
  365. /**
  366. * Detect event model and create appropriate _addEvent/_removeEvent methods
  367. */
  368. if ( window.addEventListener ) {
  369. $._addEvent = function ( element, eventName, handler, useCapture ) {
  370. element = $.getElement( element );
  371. element.addEventListener( eventName, handler, useCapture );
  372. };
  373. } else if ( window.attachEvent ) {
  374. $._addEvent = function ( element, eventName, handler, useCapture ) {
  375. element = $.getElement( element );
  376. element.attachEvent( 'on' + eventName, handler );
  377. if ( useCapture && element.setCapture ) {
  378. element.setCapture();
  379. }
  380. };
  381. } else {
  382. throw new Error( "No known event model." );
  383. }
  384. if ( window.removeEventListener ) {
  385. $._removeEvent = function ( element, eventName, handler, useCapture ) {
  386. element = $.getElement( element );
  387. element.removeEventListener( eventName, handler, useCapture );
  388. };
  389. } else if ( window.detachEvent ) {
  390. $._removeEvent = function( element, eventName, handler, useCapture ) {
  391. element = $.getElement( element );
  392. element.detachEvent( 'on' + eventName, handler );
  393. if ( useCapture && element.releaseCapture ) {
  394. element.releaseCapture();
  395. }
  396. };
  397. } else {
  398. throw new Error( "No known event model." );
  399. }
  400. }( OpenSeadragon ));
  401. /**
  402. * This closure defines all static methods available to the OpenSeadragon
  403. * namespace. Many, if not most, are taked directly from jQuery for use
  404. * to simplify and reduce common programming patterns. More static methods
  405. * from jQuery may eventually make their way into this though we are
  406. * attempting to avoid an explicit dependency on jQuery only because
  407. * OpenSeadragon is a broadly useful code base and would be made less broad
  408. * by requiring jQuery fully.
  409. *
  410. * Some static methods have also been refactored from the original OpenSeadragon
  411. * project.
  412. */
  413. (function( $ ){
  414. /**
  415. * Taken from jQuery 1.6.1
  416. * @see <a href='http://www.jquery.com/'>jQuery</a>
  417. */
  418. $.extend = function() {
  419. var options,
  420. name,
  421. src,
  422. copy,
  423. copyIsArray,
  424. clone,
  425. target = arguments[ 0 ] || {},
  426. length = arguments.length,
  427. deep = false,
  428. i = 1;
  429. // Handle a deep copy situation
  430. if ( typeof target === "boolean" ) {
  431. deep = target;
  432. target = arguments[ 1 ] || {};
  433. // skip the boolean and the target
  434. i = 2;
  435. }
  436. // Handle case when target is a string or something (possible in deep copy)
  437. if ( typeof target !== "object" && !OpenSeadragon.isFunction( target ) ) {
  438. target = {};
  439. }
  440. // extend jQuery itself if only one argument is passed
  441. if ( length === i ) {
  442. target = this;
  443. --i;
  444. }
  445. for ( ; i < length; i++ ) {
  446. // Only deal with non-null/undefined values
  447. options = arguments[ i ];
  448. if ( options !== null || options !== undefined ) {
  449. // Extend the base object
  450. for ( name in options ) {
  451. src = target[ name ];
  452. copy = options[ name ];
  453. // Prevent never-ending loop
  454. if ( target === copy ) {
  455. continue;
  456. }
  457. // Recurse if we're merging plain objects or arrays
  458. if ( deep && copy && ( OpenSeadragon.isPlainObject( copy ) || ( copyIsArray = OpenSeadragon.isArray( copy ) ) ) ) {
  459. if ( copyIsArray ) {
  460. copyIsArray = false;
  461. clone = src && OpenSeadragon.isArray( src ) ? src : [];
  462. } else {
  463. clone = src && OpenSeadragon.isPlainObject( src ) ? src : {};
  464. }
  465. // Never move original objects, clone them
  466. target[ name ] = OpenSeadragon.extend( deep, clone, copy );
  467. // Don't bring in undefined values
  468. } else if ( copy !== undefined ) {
  469. target[ name ] = copy;
  470. }
  471. }
  472. }
  473. }
  474. // Return the modified object
  475. return target;
  476. };
  477. $.extend( $, {
  478. /**
  479. * These are the default values for the optional settings documented
  480. * in the {@link OpenSeadragon} constructor detail.
  481. * @name $.DEFAULT_SETTINGS
  482. * @static
  483. */
  484. DEFAULT_SETTINGS: {
  485. //DATA SOURCE DETAILS
  486. xmlPath: null,
  487. tileSources: null,
  488. tileHost: null,
  489. initialPage: 0,
  490. //PAN AND ZOOM SETTINGS AND CONSTRAINTS
  491. panHorizontal: true,
  492. panVertical: true,
  493. constrainDuringPan: false,
  494. wrapHorizontal: false,
  495. wrapVertical: false,
  496. visibilityRatio: 0.5, //-> how much of the viewer can be negative space
  497. minPixelRatio: 0.5, //->closer to 0 draws tiles meant for a higher zoom at this zoom
  498. defaultZoomLevel: 0,
  499. minZoomLevel: null,
  500. maxZoomLevel: null,
  501. //UI RESPONSIVENESS AND FEEL
  502. springStiffness: 7.0,
  503. clickTimeThreshold: 300,
  504. clickDistThreshold: 5,
  505. zoomPerClick: 2,
  506. zoomPerScroll: 1.2,
  507. zoomPerSecond: 1.0,
  508. animationTime: 1.2,
  509. blendTime: 0,
  510. alwaysBlend: false,
  511. autoHideControls: true,
  512. immediateRender: false,
  513. minZoomImageRatio: 0.9, //-> closer to 0 allows zoom out to infinity
  514. maxZoomPixelRatio: 1.1, //-> higher allows 'over zoom' into pixels
  515. pixelsPerWheelLine: 40,
  516. //DEFAULT CONTROL SETTINGS
  517. showSequenceControl: true, //SEQUENCE
  518. preserveViewport: false, //SEQUENCE
  519. showNavigationControl: true, //ZOOM/HOME/FULL/SEQUENCE
  520. controlsFadeDelay: 2000, //ZOOM/HOME/FULL/SEQUENCE
  521. controlsFadeLength: 1500, //ZOOM/HOME/FULL/SEQUENCE
  522. mouseNavEnabled: true, //GENERAL MOUSE INTERACTIVITY
  523. //VIEWPORT NAVIGATOR SETTINGS
  524. showNavigator: false,
  525. navigatorId: null,
  526. navigatorHeight: null,
  527. navigatorWidth: null,
  528. navigatorPosition: null,
  529. navigatorSizeRatio: 0.2,
  530. // INITIAL ROTATION
  531. degrees: 0,
  532. //REFERENCE STRIP SETTINGS
  533. showReferenceStrip: false,
  534. referenceStripScroll: 'horizontal',
  535. referenceStripElement: null,
  536. referenceStripHeight: null,
  537. referenceStripWidth: null,
  538. referenceStripPosition: 'BOTTOM_LEFT',
  539. referenceStripSizeRatio: 0.2,
  540. //COLLECTION VISUALIZATION SETTINGS
  541. collectionRows: 3, //or columns depending on layout
  542. collectionLayout: 'horizontal', //vertical
  543. collectionMode: false,
  544. collectionTileSize: 800,
  545. //EVENT RELATED CALLBACKS
  546. onPageChange: null,
  547. //PERFORMANCE SETTINGS
  548. imageLoaderLimit: 0,
  549. maxImageCacheCount: 200,
  550. timeout: 30000,
  551. //INTERFACE RESOURCE SETTINGS
  552. prefixUrl: "${settings.STATIC_URL}" + "js/vendor/ova/images",
  553. navImages: {
  554. zoomIn: {
  555. REST: 'zoomin_rest.png',
  556. GROUP: 'zoomin_grouphover.png',
  557. HOVER: 'zoomin_hover.png',
  558. DOWN: 'zoomin_pressed.png'
  559. },
  560. zoomOut: {
  561. REST: 'zoomout_rest.png',
  562. GROUP: 'zoomout_grouphover.png',
  563. HOVER: 'zoomout_hover.png',
  564. DOWN: 'zoomout_pressed.png'
  565. },
  566. home: {
  567. REST: 'home_rest.png',
  568. GROUP: 'home_grouphover.png',
  569. HOVER: 'home_hover.png',
  570. DOWN: 'home_pressed.png'
  571. },
  572. fullpage: {
  573. REST: 'fullpage_rest.png',
  574. GROUP: 'fullpage_grouphover.png',
  575. HOVER: 'fullpage_hover.png',
  576. DOWN: 'fullpage_pressed.png'
  577. },
  578. previous: {
  579. REST: 'previous_rest.png',
  580. GROUP: 'previous_grouphover.png',
  581. HOVER: 'previous_hover.png',
  582. DOWN: 'previous_pressed.png'
  583. },
  584. next: {
  585. REST: 'next_rest.png',
  586. GROUP: 'next_grouphover.png',
  587. HOVER: 'next_hover.png',
  588. DOWN: 'next_pressed.png'
  589. }
  590. },
  591. navPrevNextWrap: false,
  592. //DEVELOPER SETTINGS
  593. debugMode: false,
  594. debugGridColor: '#437AB2'
  595. },
  596. /**
  597. * TODO: get rid of this. I can't see how it's required at all. Looks
  598. * like an early legacy code artifact.
  599. * @static
  600. * @ignore
  601. */
  602. SIGNAL: "----seadragon----",
  603. /**
  604. * Invokes the the method as if it where a method belonging to the object.
  605. * @name $.delegate
  606. * @function
  607. * @param {Object} object
  608. * @param {Function} method
  609. */
  610. delegate: function( object, method ) {
  611. return function(){
  612. var args = arguments;
  613. if ( args === undefined ){
  614. args = [];
  615. }
  616. return method.apply( object, args );
  617. };
  618. },
  619. /**
  620. * An enumeration of Browser vendors including UNKNOWN, IE, FIREFOX,
  621. * SAFARI, CHROME, and OPERA.
  622. * @name $.BROWSERS
  623. * @static
  624. */
  625. BROWSERS: {
  626. UNKNOWN: 0,
  627. IE: 1,
  628. FIREFOX: 2,
  629. SAFARI: 3,
  630. CHROME: 4,
  631. OPERA: 5
  632. },
  633. /**
  634. * Returns a DOM Element for the given id or element.
  635. * @function
  636. * @name OpenSeadragon.getElement
  637. * @param {String|Element} element Accepts an id or element.
  638. * @returns {Element} The element with the given id, null, or the element itself.
  639. */
  640. getElement: function( element ) {
  641. if ( typeof ( element ) == "string" ) {
  642. element = document.getElementById( element );
  643. }
  644. return element;
  645. },
  646. /**
  647. * Determines the position of the upper-left corner of the element.
  648. * @function
  649. * @name OpenSeadragon.getElementPosition
  650. * @param {Element|String} element - the elemenet we want the position for.
  651. * @returns {Point} - the position of the upper left corner of the element.
  652. */
  653. getElementPosition: function( element ) {
  654. var result = new $.Point(),
  655. isFixed,
  656. offsetParent;
  657. element = $.getElement( element );
  658. isFixed = $.getElementStyle( element ).position == "fixed";
  659. offsetParent = getOffsetParent( element, isFixed );
  660. while ( offsetParent ) {
  661. result.x += element.offsetLeft;
  662. result.y += element.offsetTop;
  663. if ( isFixed ) {
  664. result = result.plus( $.getPageScroll() );
  665. }
  666. element = offsetParent;
  667. isFixed = $.getElementStyle( element ).position == "fixed";
  668. offsetParent = getOffsetParent( element, isFixed );
  669. }
  670. return result;
  671. },
  672. /**
  673. * Determines the position of the upper-left corner of the element adjusted for current page and/or element scroll.
  674. * @function
  675. * @name OpenSeadragon.getElementOffset
  676. * @param {Element|String} element - the element we want the position for.
  677. * @returns {Point} - the position of the upper left corner of the element adjusted for current page and/or element scroll.
  678. */
  679. getElementOffset: function( element ) {
  680. element = $.getElement( element );
  681. var doc = element && element.ownerDocument,
  682. docElement,
  683. win,
  684. boundingRect = { top: 0, left: 0 };
  685. if ( !doc ) {
  686. return new $.Point();
  687. }
  688. docElement = doc.documentElement;
  689. if ( typeof element.getBoundingClientRect !== typeof undefined ) {
  690. boundingRect = element.getBoundingClientRect();
  691. }
  692. win = ( doc == doc.window ) ?
  693. doc :
  694. ( doc.nodeType === 9 ) ?
  695. doc.defaultView || doc.parentWindow :
  696. false;
  697. return new $.Point(
  698. boundingRect.left + ( win.pageXOffset || docElement.scrollLeft ) - ( docElement.clientLeft || 0 ),
  699. boundingRect.top + ( win.pageYOffset || docElement.scrollTop ) - ( docElement.clientTop || 0 )
  700. );
  701. },
  702. /**
  703. * Determines the height and width of the given element.
  704. * @function
  705. * @name OpenSeadragon.getElementSize
  706. * @param {Element|String} element
  707. * @returns {Point}
  708. */
  709. getElementSize: function( element ) {
  710. element = $.getElement( element );
  711. return new $.Point(
  712. element.clientWidth,
  713. element.clientHeight
  714. );
  715. },
  716. /**
  717. * Returns the CSSStyle object for the given element.
  718. * @function
  719. * @name OpenSeadragon.getElementStyle
  720. * @param {Element|String} element
  721. * @returns {CSSStyle}
  722. */
  723. getElementStyle:
  724. document.documentElement.currentStyle ?
  725. function( element ) {
  726. element = $.getElement( element );
  727. return element.currentStyle;
  728. } :
  729. function( element ) {
  730. element = $.getElement( element );
  731. return window.getComputedStyle( element, "" );
  732. },
  733. /**
  734. * Gets the latest event, really only useful internally since its
  735. * specific to IE behavior. TODO: Deprecate this from the api and
  736. * use it internally.
  737. * @function
  738. * @name OpenSeadragon.getEvent
  739. * @param {Event} [event]
  740. * @returns {Event}
  741. */
  742. getEvent: function( event ) {
  743. if( event ){
  744. $.getEvent = function( event ) {
  745. return event;
  746. };
  747. } else {
  748. $.getEvent = function() {
  749. return window.event;
  750. };
  751. }
  752. return $.getEvent( event );
  753. },
  754. /**
  755. * Gets the position of the mouse on the screen for a given event.
  756. * @function
  757. * @name OpenSeadragon.getMousePosition
  758. * @param {Event} [event]
  759. * @returns {Point}
  760. */
  761. getMousePosition: function( event ) {
  762. if ( typeof( event.pageX ) == "number" ) {
  763. $.getMousePosition = function( event ){
  764. var result = new $.Point();
  765. event = $.getEvent( event );
  766. result.x = event.pageX;
  767. result.y = event.pageY;
  768. return result;
  769. };
  770. } else if ( typeof( event.clientX ) == "number" ) {
  771. $.getMousePosition = function( event ){
  772. var result = new $.Point();
  773. event = $.getEvent( event );
  774. result.x =
  775. event.clientX +
  776. document.body.scrollLeft +
  777. document.documentElement.scrollLeft;
  778. result.y =
  779. event.clientY +
  780. document.body.scrollTop +
  781. document.documentElement.scrollTop;
  782. return result;
  783. };
  784. } else {
  785. throw new Error(
  786. "Unknown event mouse position, no known technique."
  787. );
  788. }
  789. return $.getMousePosition( event );
  790. },
  791. /**
  792. * Determines the pages current scroll position.
  793. * @function
  794. * @name OpenSeadragon.getPageScroll
  795. * @returns {Point}
  796. */
  797. getPageScroll: function() {
  798. var docElement = document.documentElement || {},
  799. body = document.body || {};
  800. if ( typeof( window.pageXOffset ) == "number" ) {
  801. $.getPageScroll = function(){
  802. return new $.Point(
  803. window.pageXOffset,
  804. window.pageYOffset
  805. );
  806. };
  807. } else if ( body.scrollLeft || body.scrollTop ) {
  808. $.getPageScroll = function(){
  809. return new $.Point(
  810. document.body.scrollLeft,
  811. document.body.scrollTop
  812. );
  813. };
  814. } else if ( docElement.scrollLeft || docElement.scrollTop ) {
  815. $.getPageScroll = function(){
  816. return new $.Point(
  817. document.documentElement.scrollLeft,
  818. document.documentElement.scrollTop
  819. );
  820. };
  821. } else {
  822. $.getPageScroll = function(){
  823. return new $.Point(0,0);
  824. };
  825. }
  826. return $.getPageScroll();
  827. },
  828. /**
  829. * Determines the size of the browsers window.
  830. * @function
  831. * @name OpenSeadragon.getWindowSize
  832. * @returns {Point}
  833. */
  834. getWindowSize: function() {
  835. var docElement = document.documentElement || {},
  836. body = document.body || {};
  837. if ( typeof( window.innerWidth ) == 'number' ) {
  838. $.getWindowSize = function(){
  839. return new $.Point(
  840. window.innerWidth,
  841. window.innerHeight
  842. );
  843. };
  844. } else if ( docElement.clientWidth || docElement.clientHeight ) {
  845. $.getWindowSize = function(){
  846. return new $.Point(
  847. document.documentElement.clientWidth,
  848. document.documentElement.clientHeight
  849. );
  850. };
  851. } else if ( body.clientWidth || body.clientHeight ) {
  852. $.getWindowSize = function(){
  853. return new $.Point(
  854. document.body.clientWidth,
  855. document.body.clientHeight
  856. );
  857. };
  858. } else {
  859. throw new Error("Unknown window size, no known technique.");
  860. }
  861. return $.getWindowSize();
  862. },
  863. /**
  864. * Wraps the given element in a nest of divs so that the element can
  865. * be easily centered using CSS tables
  866. * @function
  867. * @name OpenSeadragon.makeCenteredNode
  868. * @param {Element|String} element
  869. * @returns {Element} outermost wrapper element
  870. */
  871. makeCenteredNode: function( element ) {
  872. // Convert a possible ID to an actual HTMLElement
  873. element = $.getElement( element );
  874. /*
  875. CSS tables require you to have a display:table/row/cell hierarchy so we need to create
  876. three nested wrapper divs:
  877. */
  878. var wrappers = [
  879. $.makeNeutralElement( 'div' ),
  880. $.makeNeutralElement( 'div' ),
  881. $.makeNeutralElement( 'div' )
  882. ];
  883. // It feels like we should be able to pass style dicts to makeNeutralElement:
  884. $.extend(wrappers[0].style, {
  885. display: "table",
  886. height: "100%",
  887. width: "100%"
  888. });
  889. $.extend(wrappers[1].style, {
  890. display: "table-row"
  891. });
  892. $.extend(wrappers[2].style, {
  893. display: "table-cell",
  894. verticalAlign: "middle",
  895. textAlign: "center"
  896. });
  897. wrappers[0].appendChild(wrappers[1]);
  898. wrappers[1].appendChild(wrappers[2]);
  899. wrappers[2].appendChild(element);
  900. return wrappers[0];
  901. },
  902. /**
  903. * Creates an easily positionable element of the given type that therefor
  904. * serves as an excellent container element.
  905. * @function
  906. * @name OpenSeadragon.makeNeutralElement
  907. * @param {String} tagName
  908. * @returns {Element}
  909. */
  910. makeNeutralElement: function( tagName ) {
  911. var element = document.createElement( tagName ),
  912. style = element.style;
  913. style.background = "transparent none";
  914. style.border = "none";
  915. style.margin = "0px";
  916. style.padding = "0px";
  917. style.position = "static";
  918. return element;
  919. },
  920. /**
  921. * Returns the current milliseconds, using Date.now() if available
  922. * @name $.now
  923. * @function
  924. */
  925. now: function( ) {
  926. if (Date.now) {
  927. $.now = Date.now;
  928. } else {
  929. $.now = function() { return new Date().getTime(); };
  930. }
  931. return $.now();
  932. },
  933. /**
  934. * Ensures an image is loaded correctly to support alpha transparency.
  935. * Generally only IE has issues doing this correctly for formats like
  936. * png.
  937. * @function
  938. * @name OpenSeadragon.makeTransparentImage
  939. * @param {String} src
  940. * @returns {Element}
  941. */
  942. makeTransparentImage: function( src ) {
  943. $.makeTransparentImage = function( src ){
  944. var img = $.makeNeutralElement( "img" );
  945. img.src = src;
  946. return img;
  947. };
  948. if ( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 7 ) {
  949. $.makeTransparentImage = function( src ){
  950. var img = $.makeNeutralElement( "img" ),
  951. element = null;
  952. element = $.makeNeutralElement("span");
  953. element.style.display = "inline-block";
  954. img.onload = function() {
  955. element.style.width = element.style.width || img.width + "px";
  956. element.style.height = element.style.height || img.height + "px";
  957. img.onload = null;
  958. img = null; // to prevent memory leaks in IE
  959. };
  960. img.src = src;
  961. element.style.filter =
  962. "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" +
  963. src +
  964. "', sizingMethod='scale')";
  965. return element;
  966. };
  967. }
  968. return $.makeTransparentImage( src );
  969. },
  970. /**
  971. * Sets the opacity of the specified element.
  972. * @function
  973. * @name OpenSeadragon.setElementOpacity
  974. * @param {Element|String} element
  975. * @param {Number} opacity
  976. * @param {Boolean} [usesAlpha]
  977. */
  978. setElementOpacity: function( element, opacity, usesAlpha ) {
  979. var ieOpacity,
  980. ieFilter;
  981. element = $.getElement( element );
  982. if ( usesAlpha && !$.Browser.alpha ) {
  983. opacity = Math.round( opacity );
  984. }
  985. if ( $.Browser.opacity ) {
  986. element.style.opacity = opacity < 1 ? opacity : "";
  987. } else {
  988. if ( opacity < 1 ) {
  989. ieOpacity = Math.round( 100 * opacity );
  990. ieFilter = "alpha(opacity=" + ieOpacity + ")";
  991. element.style.filter = ieFilter;
  992. } else {
  993. element.style.filter = "";
  994. }
  995. }
  996. },
  997. /**
  998. * Add the specified CSS class to the element if not present.
  999. * @name $.addClass
  1000. * @function
  1001. * @param {Element|String} element
  1002. * @param {String} className
  1003. */
  1004. addClass: function( element, className ) {
  1005. element = $.getElement( element );
  1006. if ( ! element.className ) {
  1007. element.className = className;
  1008. } else if ( ( ' ' + element.className + ' ' ).
  1009. indexOf( ' ' + className + ' ' ) === -1 ) {
  1010. element.className += ' ' + className;
  1011. }
  1012. },
  1013. /**
  1014. * Remove the specified CSS class from the element.
  1015. * @name $.removeClass
  1016. * @function
  1017. * @param {Element|String} element
  1018. * @param {String} className
  1019. */
  1020. removeClass: function( element, className ) {
  1021. var oldClasses,
  1022. newClasses = [],
  1023. i;
  1024. element = $.getElement( element );
  1025. oldClasses = element.className.split( /\s+/ );
  1026. for ( i = 0; i < oldClasses.length; i++ ) {
  1027. if ( oldClasses[ i ] && oldClasses[ i ] !== className ) {
  1028. newClasses.push( oldClasses[ i ] );
  1029. }
  1030. }
  1031. element.className = newClasses.join(' ');
  1032. },
  1033. /**
  1034. * Adds an event listener for the given element, eventName and handler.
  1035. * @function
  1036. * @name OpenSeadragon.addEvent
  1037. * @param {Element|String} element
  1038. * @param {String} eventName
  1039. * @param {Function} handler
  1040. * @param {Boolean} [useCapture]
  1041. */
  1042. addEvent: function( element, eventName, handler, useCapture ) {
  1043. return $._addEvent( element, eventName, handler, useCapture );
  1044. },
  1045. /**
  1046. * Remove a given event listener for the given element, event type and
  1047. * handler.
  1048. * @function
  1049. * @name OpenSeadragon.removeEvent
  1050. * @param {Element|String} element
  1051. * @param {String} eventName
  1052. * @param {Function} handler
  1053. * @param {Boolean} [useCapture]
  1054. */
  1055. removeEvent: function( element, eventName, handler, useCapture ) {
  1056. return $._removeEvent( element, eventName, handler, useCapture );
  1057. },
  1058. /**
  1059. * Cancels the default browser behavior had the event propagated all
  1060. * the way up the DOM to the window object.
  1061. * @function
  1062. * @name OpenSeadragon.cancelEvent
  1063. * @param {Event} [event]
  1064. */
  1065. cancelEvent: function( event ) {
  1066. event = $.getEvent( event );
  1067. if ( event.preventDefault ) {
  1068. $.cancelEvent = function( event ){
  1069. // W3C for preventing default
  1070. event.preventDefault();
  1071. };
  1072. } else {
  1073. $.cancelEvent = function( event ){
  1074. event = $.getEvent( event );
  1075. // legacy for preventing default
  1076. event.cancel = true;
  1077. // IE for preventing default
  1078. event.returnValue = false;
  1079. };
  1080. }
  1081. $.cancelEvent( event );
  1082. },
  1083. /**
  1084. * Stops the propagation of the event up the DOM.
  1085. * @function
  1086. * @name OpenSeadragon.stopEvent
  1087. * @param {Event} [event]
  1088. */
  1089. stopEvent: function( event ) {
  1090. event = $.getEvent( event );
  1091. if ( event.stopPropagation ) {
  1092. // W3C for stopping propagation
  1093. $.stopEvent = function( event ){
  1094. event.stopPropagation();
  1095. };
  1096. } else {
  1097. // IE for stopping propagation
  1098. $.stopEvent = function( event ){
  1099. event = $.getEvent( event );
  1100. event.cancelBubble = true;
  1101. };
  1102. }
  1103. $.stopEvent( event );
  1104. },
  1105. /**
  1106. * Similar to OpenSeadragon.delegate, but it does not immediately call
  1107. * the method on the object, returning a function which can be called
  1108. * repeatedly to delegate the method. It also allows additonal arguments
  1109. * to be passed during construction which will be added during each
  1110. * invocation, and each invocation can add additional arguments as well.
  1111. *
  1112. * @function
  1113. * @name OpenSeadragon.createCallback
  1114. * @param {Object} object
  1115. * @param {Function} method
  1116. * @param [args] any additional arguments are passed as arguments to the
  1117. * created callback
  1118. * @returns {Function}
  1119. */
  1120. createCallback: function( object, method ) {
  1121. //TODO: This pattern is painful to use and debug. It's much cleaner
  1122. // to use pinning plus anonymous functions. Get rid of this
  1123. // pattern!
  1124. var initialArgs = [],
  1125. i;
  1126. for ( i = 2; i < arguments.length; i++ ) {
  1127. initialArgs.push( arguments[ i ] );
  1128. }
  1129. return function() {
  1130. var args = initialArgs.concat( [] ),
  1131. i;
  1132. for ( i = 0; i < arguments.length; i++ ) {
  1133. args.push( arguments[ i ] );
  1134. }
  1135. return method.apply( object, args );
  1136. };
  1137. },
  1138. /**
  1139. * Retreives the value of a url parameter from the window.location string.
  1140. * @function
  1141. * @name OpenSeadragon.getUrlParameter
  1142. * @param {String} key
  1143. * @returns {String} The value of the url parameter or null if no param matches.
  1144. */
  1145. getUrlParameter: function( key ) {
  1146. var value = URLPARAMS[ key ];
  1147. return value ? value : null;
  1148. },
  1149. createAjaxRequest: function(){
  1150. var request;
  1151. if ( window.XMLHttpRequest ) {
  1152. $.createAjaxRequest = function( ){
  1153. return new XMLHttpRequest();
  1154. };
  1155. request = new XMLHttpRequest();
  1156. } else if ( window.ActiveXObject ) {
  1157. /*jshint loopfunc:true*/
  1158. /* global ActiveXObject:true */
  1159. for ( var i = 0; i < ACTIVEX.length; i++ ) {
  1160. try {
  1161. request = new ActiveXObject( ACTIVEX[ i ] );
  1162. $.createAjaxRequest = function( ){
  1163. return new ActiveXObject( ACTIVEX[ i ] );
  1164. };
  1165. break;
  1166. } catch (e) {
  1167. continue;
  1168. }
  1169. }
  1170. }
  1171. if ( !request ) {
  1172. throw new Error( "Browser doesn't support XMLHttpRequest." );
  1173. }
  1174. return request;
  1175. },
  1176. /**
  1177. * Makes an AJAX request.
  1178. * @function
  1179. * @name OpenSeadragon.makeAjaxRequest
  1180. * @param {String} url - the url to request
  1181. * @param {Function} onSuccess - a function to call on a successful response
  1182. * @param {Function} onError - a function to call on when an error occurs
  1183. * @throws {Error}
  1184. */
  1185. makeAjaxRequest: function( url, onSuccess, onError ) {
  1186. var request = $.createAjaxRequest();
  1187. if ( !$.isFunction( onSuccess ) ) {
  1188. throw new Error( "makeAjaxRequest requires a success callback" );
  1189. }
  1190. request.onreadystatechange = function() {
  1191. // 4 = DONE (https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#Properties)
  1192. if ( request.readyState == 4 ) {
  1193. request.onreadystatechange = function(){};
  1194. if ( request.status == 200 ) {
  1195. onSuccess( request );
  1196. } else {
  1197. $.console.log( "AJAX request returned %s: %s", request.status, url );
  1198. if ( $.isFunction( onError ) ) {
  1199. onError( request );
  1200. }
  1201. }
  1202. }
  1203. };
  1204. try {
  1205. request.open( "GET", url, true );
  1206. request.send( null );
  1207. } catch (e) {
  1208. var msg = e.message;
  1209. /*
  1210. IE < 10 does not support CORS and an XHR request to a different origin will fail as soon
  1211. as send() is called. This is particularly easy to miss during development and appear in
  1212. production if you use a CDN or domain sharding and the security policy is likely to break
  1213. exception handlers since any attempt to access a property of the request object will
  1214. raise an access denied TypeError inside the catch block.
  1215. To be friendlier, we'll check for this specific error and add a documentation pointer
  1216. to point developers in the right direction. We test the exception number because IE's
  1217. error messages are localized.
  1218. */
  1219. var oldIE = $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 10;
  1220. if ( oldIE && typeof( e.number ) != "undefined" && e.number == -2147024891 ) {
  1221. msg += "\nSee http://msdn.microsoft.com/en-us/library/ms537505(v=vs.85).aspx#xdomain";
  1222. }
  1223. $.console.log( "%s while making AJAX request: %s", e.name, msg );
  1224. request.onreadystatechange = function(){};
  1225. if ( $.isFunction( onError ) ) {
  1226. onError( request, e );
  1227. }
  1228. }
  1229. },
  1230. /**
  1231. * Taken from jQuery 1.6.1
  1232. * @function
  1233. * @name OpenSeadragon.jsonp
  1234. * @param {Object} options
  1235. * @param {String} options.url
  1236. * @param {Function} options.callback
  1237. * @param {String} [options.param='callback'] The name of the url parameter
  1238. * to request the jsonp provider with.
  1239. * @param {String} [options.callbackName=] The name of the callback to
  1240. * request the jsonp provider with.
  1241. */
  1242. jsonp: function( options ){
  1243. var script,
  1244. url = options.url,
  1245. head = document.head ||
  1246. document.getElementsByTagName( "head" )[ 0 ] ||
  1247. document.documentElement,
  1248. jsonpCallback = options.callbackName || 'openseadragon' + $.now(),
  1249. previous = window[ jsonpCallback ],
  1250. replace = "$1" + jsonpCallback + "$2",
  1251. callbackParam = options.param || 'callback',
  1252. callback = options.callback;
  1253. url = url.replace( /(\=)\?(&|$)|\?\?/i, replace );
  1254. // Add callback manually
  1255. url += (/\?/.test( url ) ? "&" : "?") + callbackParam + "=" + jsonpCallback;
  1256. // Install callback
  1257. window[ jsonpCallback ] = function( response ) {
  1258. if ( !previous ){
  1259. try{
  1260. delete window[ jsonpCallback ];
  1261. }catch(e){
  1262. //swallow
  1263. }
  1264. } else {
  1265. window[ jsonpCallback ] = previous;
  1266. }
  1267. if( callback && $.isFunction( callback ) ){
  1268. callback( response );
  1269. }
  1270. };
  1271. script = document.createElement( "script" );
  1272. //TODO: having an issue with async info requests
  1273. if( undefined !== options.async || false !== options.async ){
  1274. script.async = "async";
  1275. }
  1276. if ( options.scriptCharset ) {
  1277. script.charset = options.scriptCharset;
  1278. }
  1279. script.src = url;
  1280. // Attach handlers for all browsers
  1281. script.onload = script.onreadystatechange = function( _, isAbort ) {
  1282. if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
  1283. // Handle memory leak in IE
  1284. script.onload = script.onreadystatechange = null;
  1285. // Remove the script
  1286. if ( head && script.parentNode ) {
  1287. head.removeChild( script );
  1288. }
  1289. // Dereference the script
  1290. script = undefined;
  1291. }
  1292. };
  1293. // Use insertBefore instead of appendChild to circumvent an IE6 bug.
  1294. // This arises when a base node is used (#2709 and #4378).
  1295. head.insertBefore( script, head.firstChild );
  1296. },
  1297. /**
  1298. * Fully deprecated. Will throw an error.
  1299. * @function
  1300. * @name OpenSeadragon.createFromDZI
  1301. * @deprecated - use OpenSeadragon.Viewer.prototype.open
  1302. */
  1303. createFromDZI: function() {
  1304. throw "OpenSeadragon.createFromDZI is deprecated, use Viewer.open.";
  1305. },
  1306. /**
  1307. * Parses an XML string into a DOM Document.
  1308. * @function
  1309. * @name OpenSeadragon.parseXml
  1310. * @param {String} string
  1311. * @returns {Document}
  1312. */
  1313. parseXml: function( string ) {
  1314. //TODO: yet another example where we can determine the correct
  1315. // implementation once at start-up instead of everytime we use
  1316. // the function. DONE.
  1317. if ( window.ActiveXObject ) {
  1318. $.parseXml = function( string ){
  1319. var xmlDoc = null;
  1320. xmlDoc = new ActiveXObject( "Microsoft.XMLDOM" );
  1321. xmlDoc.async = false;
  1322. xmlDoc.loadXML( string );
  1323. return xmlDoc;
  1324. };
  1325. } else if ( window.DOMParser ) {
  1326. $.parseXml = function( string ){
  1327. var xmlDoc = null,
  1328. parser;
  1329. parser = new DOMParser();
  1330. xmlDoc = parser.parseFromString( string, "text/xml" );
  1331. return xmlDoc;
  1332. };
  1333. } else {
  1334. throw new Error( "Browser doesn't support XML DOM." );
  1335. }
  1336. return $.parseXml( string );
  1337. },
  1338. /**
  1339. * Reports whether the image format is supported for tiling in this
  1340. * version.
  1341. * @function
  1342. * @name OpenSeadragon.imageFormatSupported
  1343. * @param {String} [extension]
  1344. * @returns {Boolean}
  1345. */
  1346. imageFormatSupported: function( extension ) {
  1347. extension = extension ? extension : "";
  1348. return !!FILEFORMATS[ extension.toLowerCase() ];
  1349. }
  1350. });
  1351. /**
  1352. * The current browser vendor, version, and related information regarding
  1353. * detected features. Features include <br/>
  1354. * <strong>'alpha'</strong> - Does the browser support image alpha
  1355. * transparency.<br/>
  1356. * @name $.Browser
  1357. * @static
  1358. */
  1359. $.Browser = {
  1360. vendor: $.BROWSERS.UNKNOWN,
  1361. version: 0,
  1362. alpha: true
  1363. };
  1364. var ACTIVEX = [
  1365. "Msxml2.XMLHTTP",
  1366. "Msxml3.XMLHTTP",
  1367. "Microsoft.XMLHTTP"
  1368. ],
  1369. FILEFORMATS = {
  1370. "bmp": false,
  1371. "jpeg": true,
  1372. "jpg": true,
  1373. "png": true,
  1374. "tif": false,
  1375. "wdp": false
  1376. },
  1377. URLPARAMS = {};
  1378. (function() {
  1379. //A small auto-executing routine to determine the browser vendor,
  1380. //version and supporting feature sets.
  1381. var app = navigator.appName,
  1382. ver = navigator.appVersion,
  1383. ua = navigator.userAgent;
  1384. //console.error( 'appName: ' + navigator.appName );
  1385. //console.error( 'appVersion: ' + navigator.appVersion );
  1386. //console.error( 'userAgent: ' + navigator.userAgent );
  1387. switch( navigator.appName ){
  1388. case "Microsoft Internet Explorer":
  1389. if( !!window.attachEvent &&
  1390. !!window.ActiveXObject ) {
  1391. $.Browser.vendor = $.BROWSERS.IE;
  1392. $.Browser.version = parseFloat(
  1393. ua.substring(
  1394. ua.indexOf( "MSIE" ) + 5,
  1395. ua.indexOf( ";", ua.indexOf( "MSIE" ) ) )
  1396. );
  1397. }
  1398. break;
  1399. case "Netscape":
  1400. if( !!window.addEventListener ){
  1401. if ( ua.indexOf( "Firefox" ) >= 0 ) {
  1402. $.Browser.vendor = $.BROWSERS.FIREFOX;
  1403. $.Browser.version = parseFloat(
  1404. ua.substring( ua.indexOf( "Firefox" ) + 8 )
  1405. );
  1406. } else if ( ua.indexOf( "Safari" ) >= 0 ) {
  1407. $.Browser.vendor = ua.indexOf( "Chrome" ) >= 0 ?
  1408. $.BROWSERS.CHROME :
  1409. $.BROWSERS.SAFARI;
  1410. $.Browser.version = parseFloat(
  1411. ua.substring(
  1412. ua.substring( 0, ua.indexOf( "Safari" ) ).lastIndexOf( "/" ) + 1,
  1413. ua.indexOf( "Safari" )
  1414. )
  1415. );
  1416. }
  1417. }
  1418. break;
  1419. case "Opera":
  1420. $.Browser.vendor = $.BROWSERS.OPERA;
  1421. $.Browser.version = parseFloat( ver );
  1422. break;
  1423. }
  1424. // ignore '?' portion of query string
  1425. var query = window.location.search.substring( 1 ),
  1426. parts = query.split('&'),
  1427. part,
  1428. sep,
  1429. i;
  1430. for ( i = 0; i < parts.length; i++ ) {
  1431. part = parts[ i ];
  1432. sep = part.indexOf( '=' );
  1433. if ( sep > 0 ) {
  1434. URLPARAMS[ part.substring( 0, sep ) ] =
  1435. decodeURIComponent( part.substring( sep + 1 ) );
  1436. }
  1437. }
  1438. //determine if this browser supports image alpha transparency
  1439. $.Browser.alpha = !(
  1440. (
  1441. $.Browser.vendor == $.BROWSERS.IE &&
  1442. $.Browser.version < 9
  1443. ) || (
  1444. $.Browser.vendor == $.BROWSERS.CHROME &&
  1445. $.Browser.version < 2
  1446. )
  1447. );
  1448. //determine if this browser supports element.style.opacity
  1449. $.Browser.opacity = !(
  1450. $.Browser.vendor == $.BROWSERS.IE &&
  1451. $.Browser.version < 9
  1452. );
  1453. })();
  1454. //TODO: $.console is often used inside a try/catch block which generally
  1455. // prevents allowings errors to occur with detection until a debugger
  1456. // is attached. Although I've been guilty of the same anti-pattern
  1457. // I eventually was convinced that errors should naturally propogate in
  1458. // all but the most special cases.
  1459. /**
  1460. * A convenient alias for console when available, and a simple null
  1461. * function when console is unavailable.
  1462. * @static
  1463. * @private
  1464. */
  1465. var nullfunction = function( msg ){
  1466. //document.location.hash = msg;
  1467. };
  1468. $.console = window.console || {
  1469. log: nullfunction,
  1470. debug: nullfunction,
  1471. info: nullfunction,
  1472. warn: nullfunction,
  1473. error: nullfunction
  1474. };
  1475. // Adding support for HTML5's requestAnimationFrame as suggested by acdha.
  1476. // Implementation taken from matt synder's post here:
  1477. // http://mattsnider.com/cross-browser-and-legacy-supported-requestframeanimation/
  1478. (function( w ) {
  1479. // most browsers have an implementation
  1480. var requestAnimationFrame = w.requestAnimationFrame ||
  1481. w.mozRequestAnimationFrame ||
  1482. w.webkitRequestAnimationFrame ||
  1483. w.msRequestAnimationFrame;
  1484. var cancelAnimationFrame = w.cancelAnimationFrame ||
  1485. w.mozCancelAnimationFrame ||
  1486. w.webkitCancelAnimationFrame ||
  1487. w.msCancelAnimationFrame;
  1488. // polyfill, when necessary
  1489. if ( requestAnimationFrame && cancelAnimationFrame ) {
  1490. // We can't assign these window methods directly to $ because they
  1491. // expect their "this" to be "window", so we call them in wrappers.
  1492. $.requestAnimationFrame = function(){
  1493. return requestAnimationFrame.apply( w, arguments );
  1494. };
  1495. $.cancelAnimationFrame = function(){
  1496. return cancelAnimationFrame.apply( w, arguments );
  1497. };
  1498. } else {
  1499. var aAnimQueue = [],
  1500. processing = [],
  1501. iRequestId = 0,
  1502. iIntervalId;
  1503. // create a mock requestAnimationFrame function
  1504. $.requestAnimationFrame = function( callback ) {
  1505. aAnimQueue.push( [ ++iRequestId, callback ] );
  1506. if ( !iIntervalId ) {
  1507. iIntervalId = setInterval( function() {
  1508. if ( aAnimQueue.length ) {
  1509. var time = $.now();
  1510. // Process all of the currently outstanding frame
  1511. // requests, but none that get added during the
  1512. // processing.
  1513. // Swap the arrays so we don't have to create a new
  1514. // array every frame.
  1515. var temp = processing;
  1516. processing = aAnimQueue;
  1517. aAnimQueue = temp;
  1518. while ( processing.length ) {
  1519. processing.shift()[ 1 ]( time );
  1520. }
  1521. } else {
  1522. // don't continue the interval, if unnecessary
  1523. clearInterval( iIntervalId );
  1524. iIntervalId = undefined;
  1525. }
  1526. }, 1000 / 50); // estimating support for 50 frames per second
  1527. }
  1528. return iRequestId;
  1529. };
  1530. // create a mock cancelAnimationFrame function
  1531. $.cancelAnimationFrame = function( requestId ) {
  1532. // find the request ID and remove it
  1533. var i, j;
  1534. for ( i = 0, j = aAnimQueue.length; i < j; i += 1 ) {
  1535. if ( aAnimQueue[ i ][ 0 ] === requestId ) {
  1536. aAnimQueue.splice( i, 1 );
  1537. return;
  1538. }
  1539. }
  1540. // If it's not in the queue, it may be in the set we're currently
  1541. // processing (if cancelAnimationFrame is called from within a
  1542. // requestAnimationFrame callback).
  1543. for ( i = 0, j = processing.length; i < j; i += 1 ) {
  1544. if ( processing[ i ][ 0 ] === requestId ) {
  1545. processing.splice( i, 1 );
  1546. return;
  1547. }
  1548. }
  1549. };
  1550. }
  1551. })( window );
  1552. /**
  1553. * @private
  1554. * @inner
  1555. * @function
  1556. * @param {Element} element
  1557. * @param {Boolean} [isFixed]
  1558. * @returns {Element}
  1559. */
  1560. function getOffsetParent( element, isFixed ) {
  1561. if ( isFixed && element != document.body ) {
  1562. return document.body;
  1563. } else {
  1564. return element.offsetParent;
  1565. }
  1566. }
  1567. /**
  1568. * @private
  1569. * @inner
  1570. * @function
  1571. * @param {XMLHttpRequest} xhr
  1572. * @param {String} tilesUrl
  1573. * @deprecated
  1574. */
  1575. function processDZIResponse( xhr, tilesUrl ) {
  1576. var status,
  1577. statusText,
  1578. doc = null;
  1579. if ( !xhr ) {
  1580. throw new Error( $.getString( "Errors.Security" ) );
  1581. } else if ( xhr.status !== 200 && xhr.status !== 0 ) {
  1582. status = xhr.status;
  1583. statusText = ( status == 404 ) ?
  1584. "Not Found" :
  1585. xhr.statusText;
  1586. throw new Error( $.getString( "Errors.Status", status, statusText ) );
  1587. }
  1588. if ( xhr.responseXML && xhr.responseXML.documentElement ) {
  1589. doc = xhr.responseXML;
  1590. } else if ( xhr.responseText ) {
  1591. doc = $.parseXml( xhr.responseText );
  1592. }
  1593. return processDZIXml( doc, tilesUrl );
  1594. }
  1595. /**
  1596. * @private
  1597. * @inner
  1598. * @function
  1599. * @param {Document} xmlDoc
  1600. * @param {String} tilesUrl
  1601. * @deprecated
  1602. */
  1603. function processDZIXml( xmlDoc, tilesUrl ) {
  1604. if ( !xmlDoc || !xmlDoc.documentElement ) {
  1605. throw new Error( $.getString( "Errors.Xml" ) );
  1606. }
  1607. var root = xmlDoc.documentElement,
  1608. rootName = root.tagName;
  1609. if ( rootName == "Image" ) {
  1610. try {
  1611. return processDZI( root, tilesUrl );
  1612. } catch ( e ) {
  1613. throw (e instanceof Error) ?
  1614. e :
  1615. new Error( $.getString("Errors.Dzi") );
  1616. }
  1617. } else if ( rootName == "Collection" ) {
  1618. throw new Error( $.getString( "Errors.Dzc" ) );
  1619. } else if ( rootName == "Error" ) {
  1620. return $._processDZIError( root );
  1621. }
  1622. throw new Error( $.getString( "Errors.Dzi" ) );
  1623. }
  1624. /**
  1625. * @private
  1626. * @inner
  1627. * @function
  1628. * @param {Element} imageNode
  1629. * @param {String} tilesUrl
  1630. * @deprecated
  1631. */
  1632. function processDZI( imageNode, tilesUrl ) {
  1633. var fileFormat = imageNode.getAttribute( "Format" ),
  1634. sizeNode = imageNode.getElementsByTagName( "Size" )[ 0 ],
  1635. dispRectNodes = imageNode.getElementsByTagName( "DisplayRect" ),
  1636. width = parseInt( sizeNode.getAttribute( "Width" ), 10 ),
  1637. height = parseInt( sizeNode.getAttribute( "Height" ), 10 ),
  1638. tileSize = parseInt( imageNode.getAttribute( "TileSize" ), 10 ),
  1639. tileOverlap = parseInt( imageNode.getAttribute( "Overlap" ), 10 ),
  1640. dispRects = [],
  1641. dispRectNode,
  1642. rectNode,
  1643. i;
  1644. if ( !$.imageFormatSupported( fileFormat ) ) {
  1645. throw new Error(
  1646. $.getString( "Errors.ImageFormat", fileFormat.toUpperCase() )
  1647. );
  1648. }
  1649. for ( i = 0; i < dispRectNodes.length; i++ ) {
  1650. dispRectNode = dispRectNodes[ i ];
  1651. rectNode = dispRectNode.getElementsByTagName( "Rect" )[ 0 ];
  1652. dispRects.push( new $.DisplayRect(
  1653. parseInt( rectNode.getAttribute( "X" ), 10 ),
  1654. parseInt( rectNode.getAttribute( "Y" ), 10 ),
  1655. parseInt( rectNode.getAttribute( "Width" ), 10 ),
  1656. parseInt( rectNode.getAttribute( "Height" ), 10 ),
  1657. 0, // ignore MinLevel attribute, bug in Deep Zoom Composer
  1658. parseInt( dispRectNode.getAttribute( "MaxLevel" ), 10 )
  1659. ));
  1660. }
  1661. return new $.DziTileSource(
  1662. width,
  1663. height,
  1664. tileSize,
  1665. tileOverlap,
  1666. tilesUrl,
  1667. fileFormat,
  1668. dispRects
  1669. );
  1670. }
  1671. /**
  1672. * @private
  1673. * @inner
  1674. * @function
  1675. * @param {Element} imageNode
  1676. * @param {String} tilesUrl
  1677. * @deprecated
  1678. */
  1679. function processDZIJSON( imageData, tilesUrl ) {
  1680. var fileFormat = imageData.Format,
  1681. sizeData = imageData.Size,
  1682. dispRectData = imageData.DisplayRect || [],
  1683. width = parseInt( sizeData.Width, 10 ),
  1684. height = parseInt( sizeData.Height, 10 ),
  1685. tileSize = parseInt( imageData.TileSize, 10 ),
  1686. tileOverlap = parseInt( imageData.Overlap, 10 ),
  1687. dispRects = [],
  1688. rectData,
  1689. i;
  1690. if ( !$.imageFormatSupported( fileFormat ) ) {
  1691. throw new Error(
  1692. $.getString( "Errors.ImageFormat", fileFormat.toUpperCase() )
  1693. );
  1694. }
  1695. for ( i = 0; i < dispRectData.length; i++ ) {
  1696. rectData = dispRectData[ i ].Rect;
  1697. dispRects.push( new $.DisplayRect(
  1698. parseInt( rectData.X, 10 ),
  1699. parseInt( rectData.Y, 10 ),
  1700. parseInt( rectData.Width, 10 ),
  1701. parseInt( rectData.Height, 10 ),
  1702. 0, // ignore MinLevel attribute, bug in Deep Zoom Composer
  1703. parseInt( rectData.MaxLevel, 10 )
  1704. ));
  1705. }
  1706. return new $.DziTileSource(
  1707. width,
  1708. height,
  1709. tileSize,
  1710. tileOverlap,
  1711. tilesUrl,
  1712. fileFormat,
  1713. dispRects
  1714. );
  1715. }
  1716. /**
  1717. * @private
  1718. * @inner
  1719. * @function
  1720. * @param {Document} errorNode
  1721. * @throws {Error}
  1722. * @deprecated
  1723. */
  1724. $._processDZIError = function ( errorNode ) {
  1725. var messageNode = errorNode.getElementsByTagName( "Message" )[ 0 ],
  1726. message = messageNode.firstChild.nodeValue;
  1727. throw new Error(message);
  1728. };
  1729. }( OpenSeadragon ));
  1730. /*
  1731. * OpenSeadragon - full-screen support functions
  1732. *
  1733. * Copyright (C) 2009 CodePlex Foundation
  1734. * Copyright (C) 2010-2013 OpenSeadragon contributors
  1735. *
  1736. * Redistribution and use in source and binary forms, with or without
  1737. * modification, are permitted provided that the following conditions are
  1738. * met:
  1739. *
  1740. * - Redistributions of source code must retain the above copyright notice,
  1741. * this list of conditions and the following disclaimer.
  1742. *
  1743. * - Redistributions in binary form must reproduce the above copyright
  1744. * notice, this list of conditions and the following disclaimer in the
  1745. * documentation and/or other materials provided with the distribution.
  1746. *
  1747. * - Neither the name of CodePlex Foundation nor the names of its
  1748. * contributors may be used to endorse or promote products derived from
  1749. * this software without specific prior written permission.
  1750. *
  1751. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  1752. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  1753. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  1754. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  1755. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  1756. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  1757. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  1758. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  1759. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  1760. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  1761. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  1762. */
  1763. /*
  1764. * Implementation and research by John Dyer in:
  1765. * http://johndyer.name/native-fullscreen-javascript-api-plus-jquery-plugin/
  1766. * John Dyer has released this fullscreen code under the MIT license; see
  1767. * <https://github.com/openseadragon/openseadragon/issues/81>.
  1768. *
  1769. * Copyright (C) 2011 John Dyer
  1770. *
  1771. * Permission is hereby granted, free of charge, to any person obtaining a
  1772. * copy of this software and associated documentation files (the "Software"),
  1773. * to deal in the Software without restriction, including without limitation
  1774. * the rights to use, copy, modify, merge, publish, distribute, sublicense,
  1775. * and/or sell copies of the Software, and to permit persons to whom the
  1776. * Software is furnished to do so, subject to the following conditions:
  1777. *
  1778. * The above copyright notice and this permission notice shall be included
  1779. * in all copies or substantial portions of the Software.
  1780. *
  1781. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  1782. * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  1783. * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  1784. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  1785. * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
  1786. * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
  1787. * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  1788. */
  1789. /**
  1790. * Determines the appropriate level of native full screen support we can get
  1791. * from the browser.
  1792. * @name $.supportsFullScreen
  1793. */
  1794. (function( $ ) {
  1795. var fullScreenApi = {
  1796. supportsFullScreen: false,
  1797. isFullScreen: function() { return false; },
  1798. requestFullScreen: function() {},
  1799. cancelFullScreen: function() {},
  1800. fullScreenEventName: '',
  1801. prefix: ''
  1802. },
  1803. browserPrefixes = 'webkit moz o ms khtml'.split(' ');
  1804. // check for native support
  1805. if (typeof document.cancelFullScreen != 'undefined') {
  1806. fullScreenApi.supportsFullScreen = true;
  1807. } else {
  1808. // check for fullscreen support by vendor prefix
  1809. for (var i = 0, il = browserPrefixes.length; i < il; i++ ) {
  1810. fullScreenApi.prefix = browserPrefixes[i];
  1811. if (typeof document[fullScreenApi.prefix + 'CancelFullScreen' ] != 'undefined' ) {
  1812. fullScreenApi.supportsFullScreen = true;
  1813. break;
  1814. }
  1815. }
  1816. }
  1817. // update methods to do something useful
  1818. if (fullScreenApi.supportsFullScreen) {
  1819. fullScreenApi.fullScreenEventName = fullScreenApi.prefix + 'fullscreenchange';
  1820. fullScreenApi.isFullScreen = function() {
  1821. switch (this.prefix) {
  1822. case '':
  1823. return document.fullScreen;
  1824. case 'webkit':
  1825. return document.webkitIsFullScreen;
  1826. default:
  1827. return document[this.prefix + 'FullScreen'];
  1828. }
  1829. };
  1830. fullScreenApi.requestFullScreen = function( element ) {
  1831. return (this.prefix === '') ?
  1832. element.requestFullScreen() :
  1833. element[this.prefix + 'RequestFullScreen']();
  1834. };
  1835. fullScreenApi.cancelFullScreen = function() {
  1836. return (this.prefix === '') ?
  1837. document.cancelFullScreen() :
  1838. document[this.prefix + 'CancelFullScreen']();
  1839. };
  1840. } else if ( typeof window.ActiveXObject !== "undefined" ){
  1841. // Older IE. Support based on:
  1842. // http://stackoverflow.com/questions/1125084/how-to-make-in-javascript-full-screen-windows-stretching-all-over-the-screen/7525760
  1843. fullScreenApi.requestFullScreen = function(){
  1844. /* global ActiveXObject:true */
  1845. var wscript = new ActiveXObject("WScript.Shell");
  1846. if ( wscript !== null ) {
  1847. wscript.SendKeys("{F11}");
  1848. }
  1849. return false;
  1850. };
  1851. fullScreenApi.cancelFullScreen = fullScreenApi.requestFullScreen;
  1852. }
  1853. // export api
  1854. $.extend( $, fullScreenApi );
  1855. })( OpenSeadragon );
  1856. /*
  1857. * OpenSeadragon - EventSource
  1858. *
  1859. * Copyright (C) 2009 CodePlex Foundation
  1860. * Copyright (C) 2010-2013 OpenSeadragon contributors
  1861. *
  1862. * Redistribution and use in source and binary forms, with or without
  1863. * modification, are permitted provided that the following conditions are
  1864. * met:
  1865. *
  1866. * - Redistributions of source code must retain the above copyright notice,
  1867. * this list of conditions and the following disclaimer.
  1868. *
  1869. * - Redistributions in binary form must reproduce the above copyright
  1870. * notice, this list of conditions and the following disclaimer in the
  1871. * documentation and/or other materials provided with the distribution.
  1872. *
  1873. * - Neither the name of CodePlex Foundation nor the names of its
  1874. * contributors may be used to endorse or promote products derived from
  1875. * this software without specific prior written permission.
  1876. *
  1877. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  1878. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  1879. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  1880. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  1881. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  1882. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  1883. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  1884. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  1885. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  1886. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  1887. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  1888. */
  1889. (function($){
  1890. /**
  1891. * For use by classes which want to support custom, non-browser events.
  1892. * TODO: Add a method 'one' which automatically unbinds a listener after
  1893. * the first triggered event that matches.
  1894. * @class
  1895. */
  1896. $.EventSource = function() {
  1897. this.events = {};
  1898. };
  1899. $.EventSource.prototype = {
  1900. /**
  1901. * Add an event handler for a given event.
  1902. * @function
  1903. * @param {String} eventName - Name of event to register.
  1904. * @param {Function} handler - Function to call when event is triggered.
  1905. * @param {Object} [userData=null] - Arbitrary object to be passed unchanged to the handler.
  1906. */
  1907. addHandler: function ( eventName, handler, userData ) {
  1908. var events = this.events[ eventName ];
  1909. if ( !events ) {
  1910. this.events[ eventName ] = events = [];
  1911. }
  1912. if ( handler && $.isFunction( handler ) ) {
  1913. events[ events.length ] = { handler: handler, userData: userData || null };
  1914. }
  1915. },
  1916. /**
  1917. * Remove a specific event handler for a given event.
  1918. * @function
  1919. * @param {String} eventName - Name of event for which the handler is to be removed.
  1920. * @param {Function} handler - Function to be removed.
  1921. */
  1922. removeHandler: function ( eventName, handler ) {
  1923. var events = this.events[ eventName ],
  1924. handlers = [],
  1925. i;
  1926. if ( !events ) {
  1927. return;
  1928. }
  1929. if ( $.isArray( events ) ) {
  1930. for ( i = 0; i < events.length; i++ ) {
  1931. if ( events[i].handler !== handler ) {
  1932. handlers.push( events[ i ] );
  1933. }
  1934. }
  1935. this.events[ eventName ] = handlers;
  1936. }
  1937. },
  1938. /**
  1939. * Remove all event handlers for a given event type. If no type is given all
  1940. * event handlers for every event type are removed.
  1941. * @function
  1942. * @param {String} eventName - Name of event for which all handlers are to be removed.
  1943. */
  1944. removeAllHandlers: function( eventName ) {
  1945. if ( eventName ){
  1946. this.events[ eventName ] = [];
  1947. } else{
  1948. for ( var eventType in this.events ) {
  1949. this.events[ eventType ] = [];
  1950. }
  1951. }
  1952. },
  1953. /**
  1954. * Retrive the list of all handlers registered for a given event.
  1955. * @function
  1956. * @param {String} eventName - Name of event to get handlers for.
  1957. */
  1958. getHandler: function ( eventName ) {
  1959. var events = this.events[ eventName ];
  1960. if ( !events || !events.length ) {
  1961. return null;
  1962. }
  1963. events = events.length === 1 ?
  1964. [ events[ 0 ] ] :
  1965. Array.apply( null, events );
  1966. return function ( source, args ) {
  1967. var i,
  1968. length = events.length;
  1969. for ( i = 0; i < length; i++ ) {
  1970. if ( events[ i ] ) {
  1971. args.eventSource = source;
  1972. args.userData = events[ i ].userData;
  1973. events[ i ].handler( args );
  1974. }
  1975. }
  1976. };
  1977. },
  1978. /**
  1979. * Trigger an event, optionally passing additional information.
  1980. * @function
  1981. * @param {String} eventName - Name of event to register.
  1982. * @param {Function} handler - Function to call when event is triggered.
  1983. */
  1984. raiseEvent: function( eventName, eventArgs ) {
  1985. //uncomment if you want to get a log of all events
  1986. //$.console.log( eventName );
  1987. var handler = this.getHandler( eventName );
  1988. if ( handler ) {
  1989. if ( !eventArgs ) {
  1990. eventArgs = {};
  1991. }
  1992. handler( this, eventArgs );
  1993. }
  1994. }
  1995. };
  1996. }( OpenSeadragon ));
  1997. /*
  1998. * OpenSeadragon - MouseTracker
  1999. *
  2000. * Copyright (C) 2009 CodePlex Foundation
  2001. * Copyright (C) 2010-2013 OpenSeadragon contributors
  2002. *
  2003. * Redistribution and use in source and binary forms, with or without
  2004. * modification, are permitted provided that the following conditions are
  2005. * met:
  2006. *
  2007. * - Redistributions of source code must retain the above copyright notice,
  2008. * this list of conditions and the following disclaimer.
  2009. *
  2010. * - Redistributions in binary form must reproduce the above copyright
  2011. * notice, this list of conditions and the following disclaimer in the
  2012. * documentation and/or other materials provided with the distribution.
  2013. *
  2014. * - Neither the name of CodePlex Foundation nor the names of its
  2015. * contributors may be used to endorse or promote products derived from
  2016. * this software without specific prior written permission.
  2017. *
  2018. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  2019. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  2020. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  2021. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  2022. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  2023. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  2024. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  2025. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  2026. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  2027. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  2028. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  2029. */
  2030. (function ( $ ) {
  2031. // is any button currently being pressed while mouse events occur
  2032. var IS_BUTTON_DOWN = false,
  2033. // is any tracker currently capturing?
  2034. IS_CAPTURING = false,
  2035. // dictionary from hash to MouseTracker
  2036. ACTIVE = {},
  2037. // list of trackers interested in capture
  2038. CAPTURING = [],
  2039. // dictionary from hash to private properties
  2040. THIS = {};
  2041. /**
  2042. * The MouseTracker allows other classes to set handlers for common mouse
  2043. * events on a specific element like, 'enter', 'exit', 'press', 'release',
  2044. * 'scroll', 'click', and 'drag'.
  2045. * @class
  2046. * @param {Object} options
  2047. * Allows configurable properties to be entirely specified by passing
  2048. * an options object to the constructor. The constructor also supports
  2049. * the original positional arguments 'elements', 'clickTimeThreshold',
  2050. * and 'clickDistThreshold' in that order.
  2051. * @param {Element|String} options.element
  2052. * A reference to an element or an element id for which the mouse
  2053. * events will be monitored.
  2054. * @param {Number} options.clickTimeThreshold
  2055. * The number of milliseconds within which multiple mouse clicks
  2056. * will be treated as a single event.
  2057. * @param {Number} options.clickDistThreshold
  2058. * The distance between mouse click within multiple mouse clicks
  2059. * will be treated as a single event.
  2060. * @param {Number} options.stopDelay
  2061. * The number of milliseconds without mouse move before the mouse stop
  2062. * event is fired.
  2063. * @param {Function} options.enterHandler
  2064. * An optional handler for mouse enter.
  2065. * @param {Function} options.exitHandler
  2066. * An optional handler for mouse exit.
  2067. * @param {Function} options.pressHandler
  2068. * An optional handler for mouse press.
  2069. * @param {Function} options.releaseHandler
  2070. * An optional handler for mouse release.
  2071. * @param {Function} options.moveHandler
  2072. * An optional handler for mouse move.
  2073. * @param {Function} options.scrollHandler
  2074. * An optional handler for mouse scroll.
  2075. * @param {Function} options.clickHandler
  2076. * An optional handler for mouse click.
  2077. * @param {Function} options.dragHandler
  2078. * An optional handler for mouse drag.
  2079. * @param {Function} options.keyHandler
  2080. * An optional handler for keypress.
  2081. * @param {Function} options.focusHandler
  2082. * An optional handler for focus.
  2083. * @param {Function} options.blurHandler
  2084. * An optional handler for blur.
  2085. * @param {Object} [options.userData=null]
  2086. * Arbitrary object to be passed unchanged to any attached handler methods.
  2087. * @property {Number} hash
  2088. * An unique hash for this tracker.
  2089. * @property {Element} element
  2090. * The element for which mouse event are being monitored.
  2091. * @property {Number} clickTimeThreshold
  2092. * The number of milliseconds within which mutliple mouse clicks
  2093. * will be treated as a single event.
  2094. * @property {Number} clickDistThreshold
  2095. * The distance between mouse click within multiple mouse clicks
  2096. * will be treated as a single event.
  2097. */
  2098. $.MouseTracker = function ( options ) {
  2099. var args = arguments;
  2100. if ( !$.isPlainObject( options ) ) {
  2101. options = {
  2102. element: args[ 0 ],
  2103. clickTimeThreshold: args[ 1 ],
  2104. clickDistThreshold: args[ 2 ]
  2105. };
  2106. }
  2107. this.hash = Math.random();
  2108. this.element = $.getElement( options.element );
  2109. this.clickTimeThreshold = options.clickTimeThreshold;
  2110. this.clickDistThreshold = options.clickDistThreshold;
  2111. this.userData = options.userData || null;
  2112. this.stopDelay = options.stopDelay || 50;
  2113. this.enterHandler = options.enterHandler || null;
  2114. this.exitHandler = options.exitHandler || null;
  2115. this.pressHandler = options.pressHandler || null;
  2116. this.releaseHandler = options.releaseHandler || null;
  2117. this.moveHandler = options.moveHandler || null;
  2118. this.scrollHandler = options.scrollHandler || null;
  2119. this.clickHandler = options.clickHandler || null;
  2120. this.dragHandler = options.dragHandler || null;
  2121. this.stopHandler = options.stopHandler || null;
  2122. this.keyHandler = options.keyHandler || null;
  2123. this.focusHandler = options.focusHandler || null;
  2124. this.blurHandler = options.blurHandler || null;
  2125. //Store private properties in a scope sealed hash map
  2126. var _this = this;
  2127. /**
  2128. * @private
  2129. * @property {Boolean} tracking
  2130. * Are we currently tracking mouse events.
  2131. * @property {Boolean} capturing
  2132. * Are we curruently capturing mouse events.
  2133. * @property {Boolean} insideElementPressed
  2134. * True if the left mouse button is currently being pressed and was
  2135. * initiated inside the tracked element, otherwise false.
  2136. * @property {Boolean} insideElement
  2137. * Are we currently inside the screen area of the tracked element.
  2138. * @property {OpenSeadragon.Point} lastPoint
  2139. * Position of last mouse down/move
  2140. * @property {Number} lastMouseDownTime
  2141. * Time of last mouse down.
  2142. * @property {OpenSeadragon.Point} lastMouseDownPoint
  2143. * Position of last mouse down
  2144. */
  2145. THIS[ this.hash ] = {
  2146. mouseover: function ( event ) { onMouseOver( _this, event, false ); },
  2147. mouseout: function ( event ) { onMouseOut( _this, event, false ); },
  2148. mousedown: function ( event ) { onMouseDown( _this, event ); },
  2149. mouseup: function ( event ) { onMouseUp( _this, event, false ); },
  2150. mousemove: function ( event ) { onMouseMove( _this, event ); },
  2151. click: function ( event ) { onMouseClick( _this, event ); },
  2152. wheel: function ( event ) { onWheel( _this, event ); },
  2153. mousewheel: function ( event ) { onMouseWheel( _this, event ); },
  2154. DOMMouseScroll: function ( event ) { onMouseWheel( _this, event ); },
  2155. MozMousePixelScroll: function ( event ) { onMouseWheel( _this, event ); },
  2156. mouseupie: function ( event ) { onMouseUpIE( _this, event ); },
  2157. mousemovecapturedie: function ( event ) { onMouseMoveCapturedIE( _this, event ); },
  2158. mouseupcaptured: function ( event ) { onMouseUpCaptured( _this, event ); },
  2159. mousemovecaptured: function ( event ) { onMouseMoveCaptured( _this, event, false ); },
  2160. touchstart: function ( event ) { onTouchStart( _this, event ); },
  2161. touchmove: function ( event ) { onTouchMove( _this, event ); },
  2162. touchend: function ( event ) { onTouchEnd( _this, event ); },
  2163. keypress: function ( event ) { onKeyPress( _this, event ); },
  2164. focus: function ( event ) { onFocus( _this, event ); },
  2165. blur: function ( event ) { onBlur( _this, event ); },
  2166. tracking: false,
  2167. capturing: false,
  2168. insideElementPressed: false,
  2169. insideElement: false,
  2170. lastPoint: null,
  2171. lastMouseDownTime: null,
  2172. lastMouseDownPoint: null,
  2173. lastPinchDelta: 0
  2174. };
  2175. };
  2176. $.MouseTracker.prototype = {
  2177. /**
  2178. * Clean up any events or objects created by the mouse tracker.
  2179. * @function
  2180. */
  2181. destroy: function () {
  2182. stopTracking( this );
  2183. this.element = null;
  2184. },
  2185. /**
  2186. * Are we currently tracking events on this element.
  2187. * @deprecated Just use this.tracking
  2188. * @function
  2189. * @returns {Boolean} Are we currently tracking events on this element.
  2190. */
  2191. isTracking: function () {
  2192. return THIS[ this.hash ].tracking;
  2193. },
  2194. /**
  2195. * Enable or disable whether or not we are tracking events on this element.
  2196. * @function
  2197. * @param {Boolean} track True to start tracking, false to stop tracking.
  2198. * @returns {OpenSeadragon.MouseTracker} Chainable.
  2199. */
  2200. setTracking: function ( track ) {
  2201. if ( track ) {
  2202. startTracking( this );
  2203. } else {
  2204. stopTracking( this );
  2205. }
  2206. //chain
  2207. return this;
  2208. },
  2209. /**
  2210. * Implement or assign implementation to these handlers during or after
  2211. * calling the constructor.
  2212. * @function
  2213. * @param {Object} event
  2214. * @param {OpenSeadragon.MouseTracker} event.eventSource
  2215. * A reference to the tracker instance.
  2216. * @param {OpenSeadragon.Point} event.position
  2217. * The position of the event relative to the tracked element.
  2218. * @param {Boolean} event.insideElementPressed
  2219. * True if the left mouse button is currently being pressed and was
  2220. * initiated inside the tracked element, otherwise false.
  2221. * @param {Boolean} event.buttonDownAny
  2222. * Was the button down anywhere in the screen during the event.
  2223. * @param {Boolean} event.isTouchEvent
  2224. * True if the original event is a touch event, otherwise false.
  2225. * @param {Object} event.originalEvent
  2226. * The original event object.
  2227. * @param {Object} event.userData
  2228. * Arbitrary user-defined object.
  2229. */
  2230. enterHandler: function () { },
  2231. /**
  2232. * Implement or assign implementation to these handlers during or after
  2233. * calling the constructor.
  2234. * @function
  2235. * @param {Object} event
  2236. * @param {OpenSeadragon.MouseTracker} event.eventSource
  2237. * A reference to the tracker instance.
  2238. * @param {OpenSeadragon.Point} event.position
  2239. * The position of the event relative to the tracked element.
  2240. * @param {Boolean} event.insideElementPressed
  2241. * True if the left mouse button is currently being pressed and was
  2242. * initiated inside the tracked element, otherwise false.
  2243. * @param {Boolean} event.buttonDownAny
  2244. * Was the button down anywhere in the screen during the event.
  2245. * @param {Boolean} event.isTouchEvent
  2246. * True if the original event is a touch event, otherwise false.
  2247. * @param {Object} event.originalEvent
  2248. * The original event object.
  2249. * @param {Object} event.userData
  2250. * Arbitrary user-defined object.
  2251. */
  2252. exitHandler: function () { },
  2253. /**
  2254. * Implement or assign implementation to these handlers during or after
  2255. * calling the constructor.
  2256. * @function
  2257. * @param {Object} event
  2258. * @param {OpenSeadragon.MouseTracker} event.eventSource
  2259. * A reference to the tracker instance.
  2260. * @param {OpenSeadragon.Point} event.position
  2261. * The position of the event relative to the tracked element.
  2262. * @param {Boolean} event.isTouchEvent
  2263. * True if the original event is a touch event, otherwise false.
  2264. * @param {Object} event.originalEvent
  2265. * The original event object.
  2266. * @param {Object} event.userData
  2267. * Arbitrary user-defined object.
  2268. */
  2269. pressHandler: function () { },
  2270. /**
  2271. * Implement or assign implementation to these handlers during or after
  2272. * calling the constructor.
  2273. * @function
  2274. * @param {Object} event
  2275. * @param {OpenSeadragon.MouseTracker} event.eventSource
  2276. * A reference to the tracker instance.
  2277. * @param {OpenSeadragon.Point} event.position
  2278. * The position of the event relative to the tracked element.
  2279. * @param {Boolean} event.insideElementPressed
  2280. * True if the left mouse button is currently being pressed and was
  2281. * initiated inside the tracked element, otherwise false.
  2282. * @param {Boolean} event.insideElementReleased
  2283. * True if the cursor still inside the tracked element when the button was released.
  2284. * @param {Boolean} event.isTouchEvent
  2285. * True if the original event is a touch event, otherwise false.
  2286. * @param {Object} event.originalEvent
  2287. * The original event object.
  2288. * @param {Object} event.userData
  2289. * Arbitrary user-defined object.
  2290. */
  2291. releaseHandler: function () { },
  2292. /**
  2293. * Implement or assign implementation to these handlers during or after
  2294. * calling the constructor.
  2295. * @function
  2296. * @param {Object} event
  2297. * @param {OpenSeadragon.MouseTracker} event.eventSource
  2298. * A reference to the tracker instance.
  2299. * @param {OpenSeadragon.Point} event.position
  2300. * The position of the event relative to the tracked element.
  2301. * @param {Boolean} event.isTouchEvent
  2302. * True if the original event is a touch event, otherwise false.
  2303. * @param {Object} event.originalEvent
  2304. * The original event object.
  2305. * @param {Object} event.userData
  2306. * Arbitrary user-defined object.
  2307. */
  2308. moveHandler: function () { },
  2309. /**
  2310. * Implement or assign implementation to these handlers during or after
  2311. * calling the constructor.
  2312. * @function
  2313. * @param {Object} event
  2314. * @param {OpenSeadragon.MouseTracker} event.eventSource
  2315. * A reference to the tracker instance.
  2316. * @param {OpenSeadragon.Point} event.position
  2317. * The position of the event relative to the tracked element.
  2318. * @param {Number} event.scroll
  2319. * The scroll delta for the event.
  2320. * @param {Boolean} event.shift
  2321. * True if the shift key was pressed during this event.
  2322. * @param {Boolean} event.isTouchEvent
  2323. * True if the original event is a touch event, otherwise false.
  2324. * @param {Object} event.originalEvent
  2325. * The original event object.
  2326. * @param {Object} event.userData
  2327. * Arbitrary user-defined object.
  2328. */
  2329. scrollHandler: function () { },
  2330. /**
  2331. * Implement or assign implementation to these handlers during or after
  2332. * calling the constructor.
  2333. * @function
  2334. * @param {Object} event
  2335. * @param {OpenSeadragon.MouseTracker} event.eventSource
  2336. * A reference to the tracker instance.
  2337. * @param {OpenSeadragon.Point} event.position
  2338. * The position of the event relative to the tracked element.
  2339. * @param {Number} event.quick
  2340. * True only if the clickDistThreshold and clickDeltaThreshold are both pased. Useful for ignoring events.
  2341. * @param {Boolean} event.shift
  2342. * True if the shift key was pressed during this event.
  2343. * @param {Boolean} event.isTouchEvent
  2344. * True if the original event is a touch event, otherwise false.
  2345. * @param {Object} event.originalEvent
  2346. * The original event object.
  2347. * @param {Object} event.userData
  2348. * Arbitrary user-defined object.
  2349. */
  2350. clickHandler: function () { },
  2351. /**
  2352. * Implement or assign implementation to these handlers during or after
  2353. * calling the constructor.
  2354. * @function
  2355. * @param {Object} event
  2356. * @param {OpenSeadragon.MouseTracker} event.eventSource
  2357. * A reference to the tracker instance.
  2358. * @param {OpenSeadragon.Point} event.position
  2359. * The position of the event relative to the tracked element.
  2360. * @param {OpenSeadragon.Point} event.delta
  2361. * The x,y components of the difference between start drag and end drag. Usefule for ignoring or weighting the events.
  2362. * @param {Boolean} event.shift
  2363. * True if the shift key was pressed during this event.
  2364. * @param {Boolean} event.isTouchEvent
  2365. * True if the original event is a touch event, otherwise false.
  2366. * @param {Object} event.originalEvent
  2367. * The original event object.
  2368. * @param {Object} event.userData
  2369. * Arbitrary user-defined object.
  2370. */
  2371. dragHandler: function () { },
  2372. /**
  2373. * Implement or assign implementation to these handlers during or after
  2374. * calling the constructor.
  2375. * @function
  2376. * @param {Object} event
  2377. * @param {OpenSeadragon.MouseTracker} event.eventSource
  2378. * A reference to the tracker instance.
  2379. * @param {OpenSeadragon.Point} event.position
  2380. * The position of the event relative to the tracked element.
  2381. * @param {Boolean} event.isTouchEvent
  2382. * True if the original event is a touch event, otherwise false.
  2383. * @param {Object} event.originalEvent
  2384. * The original event object.
  2385. * @param {Object} event.userData
  2386. * Arbitrary user-defined object.
  2387. */
  2388. stopHandler: function () { },
  2389. /**
  2390. * Implement or assign implementation to these handlers during or after
  2391. * calling the constructor.
  2392. * @function
  2393. * @param {Object} event
  2394. * @param {OpenSeadragon.MouseTracker} event.eventSource
  2395. * A reference to the tracker instance.
  2396. * @param {Number} event.keyCode
  2397. * The key code that was pressed.
  2398. * @param {Boolean} event.shift
  2399. * True if the shift key was pressed during this event.
  2400. * @param {Object} event.originalEvent
  2401. * The original event object.
  2402. * @param {Object} event.userData
  2403. * Arbitrary user-defined object.
  2404. */
  2405. keyHandler: function () { },
  2406. /**
  2407. * Implement or assign implementation to these handlers during or after
  2408. * calling the constructor.
  2409. * @function
  2410. * @param {Object} event
  2411. * @param {OpenSeadragon.MouseTracker} event.eventSource
  2412. * A reference to the tracker instance.
  2413. * @param {Object} event.originalEvent
  2414. * The original event object.
  2415. * @param {Object} event.userData
  2416. * Arbitrary user-defined object.
  2417. */
  2418. focusHandler: function () { },
  2419. /**
  2420. * Implement or assign implementation to these handlers during or after
  2421. * calling the constructor.
  2422. * @function
  2423. * @param {Object} event
  2424. * @param {OpenSeadragon.MouseTracker} event.eventSource
  2425. * A reference to the tracker instance.
  2426. * @param {Object} event.originalEvent
  2427. * The original event object.
  2428. * @param {Object} event.userData
  2429. * Arbitrary user-defined object.
  2430. */
  2431. blurHandler: function () { }
  2432. };
  2433. /**
  2434. * Detect available mouse wheel event.
  2435. */
  2436. $.MouseTracker.wheelEventName = ( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version > 8 ) ||
  2437. ( 'onwheel' in document.createElement( 'div' ) ) ? 'wheel' : // Modern browsers support 'wheel'
  2438. document.onmousewheel !== undefined ? 'mousewheel' : // Webkit and IE support at least 'mousewheel'
  2439. 'DOMMouseScroll'; // Assume old Firefox
  2440. /**
  2441. * Starts tracking mouse events on this element.
  2442. * @private
  2443. * @inner
  2444. */
  2445. function startTracking( tracker ) {
  2446. var events = [
  2447. "mouseover", "mouseout", "mousedown", "mouseup", "mousemove",
  2448. "click",
  2449. $.MouseTracker.wheelEventName,
  2450. "touchstart", "touchmove", "touchend",
  2451. "keypress",
  2452. "focus", "blur"
  2453. ],
  2454. delegate = THIS[ tracker.hash ],
  2455. event,
  2456. i;
  2457. // Add 'MozMousePixelScroll' event handler for older Firefox
  2458. if( $.MouseTracker.wheelEventName == "DOMMouseScroll" ) {
  2459. events.push( "MozMousePixelScroll" );
  2460. }
  2461. if ( !delegate.tracking ) {
  2462. for ( i = 0; i < events.length; i++ ) {
  2463. event = events[ i ];
  2464. $.addEvent(
  2465. tracker.element,
  2466. event,
  2467. delegate[ event ],
  2468. false
  2469. );
  2470. }
  2471. delegate.tracking = true;
  2472. ACTIVE[ tracker.hash ] = tracker;
  2473. }
  2474. }
  2475. /**
  2476. * Stops tracking mouse events on this element.
  2477. * @private
  2478. * @inner
  2479. */
  2480. function stopTracking( tracker ) {
  2481. var events = [
  2482. "mouseover", "mouseout", "mousedown", "mouseup", "mousemove",
  2483. "click",
  2484. $.MouseTracker.wheelEventName,
  2485. "touchstart", "touchmove", "touchend",
  2486. "keypress",
  2487. "focus", "blur"
  2488. ],
  2489. delegate = THIS[ tracker.hash ],
  2490. event,
  2491. i;
  2492. // Remove 'MozMousePixelScroll' event handler for older Firefox
  2493. if( $.MouseTracker.wheelEventName == "DOMMouseScroll" ) {
  2494. events.push( "MozMousePixelScroll" );
  2495. }
  2496. if ( delegate.tracking ) {
  2497. for ( i = 0; i < events.length; i++ ) {
  2498. event = events[ i ];
  2499. $.removeEvent(
  2500. tracker.element,
  2501. event,
  2502. delegate[ event ],
  2503. false
  2504. );
  2505. }
  2506. releaseMouse( tracker );
  2507. delegate.tracking = false;
  2508. delete ACTIVE[ tracker.hash ];
  2509. }
  2510. }
  2511. /**
  2512. * @private
  2513. * @inner
  2514. */
  2515. function hasMouse( tracker ) {
  2516. return THIS[ tracker.hash ].insideElement;
  2517. }
  2518. /**
  2519. * Begin capturing mouse events on this element.
  2520. * @private
  2521. * @inner
  2522. */
  2523. function captureMouse( tracker ) {
  2524. var delegate = THIS[ tracker.hash ];
  2525. if ( !delegate.capturing ) {
  2526. if ( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 9 ) {
  2527. $.removeEvent(
  2528. tracker.element,
  2529. "mouseup",
  2530. delegate.mouseup,
  2531. false
  2532. );
  2533. $.addEvent(
  2534. tracker.element,
  2535. "mouseup",
  2536. delegate.mouseupie,
  2537. true
  2538. );
  2539. $.addEvent(
  2540. tracker.element,
  2541. "mousemove",
  2542. delegate.mousemovecapturedie,
  2543. true
  2544. );
  2545. } else {
  2546. $.addEvent(
  2547. window,
  2548. "mouseup",
  2549. delegate.mouseupcaptured,
  2550. true
  2551. );
  2552. $.addEvent(
  2553. window,
  2554. "mousemove",
  2555. delegate.mousemovecaptured,
  2556. true
  2557. );
  2558. }
  2559. delegate.capturing = true;
  2560. }
  2561. }
  2562. /**
  2563. * Stop capturing mouse events on this element.
  2564. * @private
  2565. * @inner
  2566. */
  2567. function releaseMouse( tracker ) {
  2568. var delegate = THIS[ tracker.hash ];
  2569. if ( delegate.capturing ) {
  2570. if ( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 9 ) {
  2571. $.removeEvent(
  2572. tracker.element,
  2573. "mousemove",
  2574. delegate.mousemovecapturedie,
  2575. true
  2576. );
  2577. $.removeEvent(
  2578. tracker.element,
  2579. "mouseup",
  2580. delegate.mouseupie,
  2581. true
  2582. );
  2583. $.addEvent(
  2584. tracker.element,
  2585. "mouseup",
  2586. delegate.mouseup,
  2587. false
  2588. );
  2589. } else {
  2590. $.removeEvent(
  2591. window,
  2592. "mousemove",
  2593. delegate.mousemovecaptured,
  2594. true
  2595. );
  2596. $.removeEvent(
  2597. window,
  2598. "mouseup",
  2599. delegate.mouseupcaptured,
  2600. true
  2601. );
  2602. }
  2603. delegate.capturing = false;
  2604. }
  2605. }
  2606. /**
  2607. * @private
  2608. * @inner
  2609. */
  2610. function triggerOthers( tracker, handler, event, isTouch ) {
  2611. var otherHash;
  2612. for ( otherHash in ACTIVE ) {
  2613. if ( ACTIVE.hasOwnProperty( otherHash ) && tracker.hash != otherHash ) {
  2614. handler( ACTIVE[ otherHash ], event, isTouch );
  2615. }
  2616. }
  2617. }
  2618. /**
  2619. * @private
  2620. * @inner
  2621. */
  2622. function onFocus( tracker, event ) {
  2623. //console.log( "focus %s", event );
  2624. var propagate;
  2625. if ( tracker.focusHandler ) {
  2626. propagate = tracker.focusHandler(
  2627. {
  2628. eventSource: tracker,
  2629. originalEvent: event,
  2630. userData: tracker.userData
  2631. }
  2632. );
  2633. if ( propagate === false ) {
  2634. $.cancelEvent( event );
  2635. }
  2636. }
  2637. }
  2638. /**
  2639. * @private
  2640. * @inner
  2641. */
  2642. function onBlur( tracker, event ) {
  2643. //console.log( "blur %s", event );
  2644. var propagate;
  2645. if ( tracker.blurHandler ) {
  2646. propagate = tracker.blurHandler(
  2647. {
  2648. eventSource: tracker,
  2649. originalEvent: event,
  2650. userData: tracker.userData
  2651. }
  2652. );
  2653. if ( propagate === false ) {
  2654. $.cancelEvent( event );
  2655. }
  2656. }
  2657. }
  2658. /**
  2659. * @private
  2660. * @inner
  2661. */
  2662. function onKeyPress( tracker, event ) {
  2663. //console.log( "keypress %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey );
  2664. var propagate;
  2665. if ( tracker.keyHandler ) {
  2666. propagate = tracker.keyHandler(
  2667. {
  2668. eventSource: tracker,
  2669. position: getMouseRelative( event, tracker.element ),
  2670. keyCode: event.keyCode ? event.keyCode : event.charCode,
  2671. shift: event.shiftKey,
  2672. originalEvent: event,
  2673. userData: tracker.userData
  2674. }
  2675. );
  2676. if ( !propagate ) {
  2677. $.cancelEvent( event );
  2678. }
  2679. }
  2680. }
  2681. /**
  2682. * @private
  2683. * @inner
  2684. */
  2685. function onMouseOver( tracker, event, isTouch ) {
  2686. var delegate = THIS[ tracker.hash ],
  2687. propagate;
  2688. isTouch = isTouch || false;
  2689. event = $.getEvent( event );
  2690. if ( !isTouch ) {
  2691. if ( $.Browser.vendor == $.BROWSERS.IE &&
  2692. $.Browser.version < 9 &&
  2693. delegate.capturing &&
  2694. !isChild( event.srcElement, tracker.element ) ) {
  2695. triggerOthers( tracker, onMouseOver, event, isTouch );
  2696. }
  2697. var to = event.target ?
  2698. event.target :
  2699. event.srcElement,
  2700. from = event.relatedTarget ?
  2701. event.relatedTarget :
  2702. event.fromElement;
  2703. if ( !isChild( tracker.element, to ) ||
  2704. isChild( tracker.element, from ) ) {
  2705. return;
  2706. }
  2707. }
  2708. delegate.insideElement = true;
  2709. if ( tracker.enterHandler ) {
  2710. propagate = tracker.enterHandler(
  2711. {
  2712. eventSource: tracker,
  2713. position: getMouseRelative( isTouch ? event.changedTouches[ 0 ] : event, tracker.element ),
  2714. insideElementPressed: delegate.insideElementPressed,
  2715. buttonDownAny: IS_BUTTON_DOWN,
  2716. isTouchEvent: isTouch,
  2717. originalEvent: event,
  2718. userData: tracker.userData
  2719. }
  2720. );
  2721. if ( propagate === false ) {
  2722. $.cancelEvent( event );
  2723. }
  2724. }
  2725. }
  2726. /**
  2727. * @private
  2728. * @inner
  2729. */
  2730. function onMouseOut( tracker, event, isTouch ) {
  2731. var delegate = THIS[ tracker.hash ],
  2732. propagate;
  2733. isTouch = isTouch || false;
  2734. event = $.getEvent( event );
  2735. if ( !isTouch ) {
  2736. if ( $.Browser.vendor == $.BROWSERS.IE &&
  2737. $.Browser.version < 9 &&
  2738. delegate.capturing &&
  2739. !isChild( event.srcElement, tracker.element ) ) {
  2740. triggerOthers( tracker, onMouseOut, event, isTouch );
  2741. }
  2742. var from = event.target ?
  2743. event.target :
  2744. event.srcElement,
  2745. to = event.relatedTarget ?
  2746. event.relatedTarget :
  2747. event.toElement;
  2748. if ( !isChild( tracker.element, from ) ||
  2749. isChild( tracker.element, to ) ) {
  2750. return;
  2751. }
  2752. }
  2753. delegate.insideElement = false;
  2754. if ( tracker.exitHandler ) {
  2755. propagate = tracker.exitHandler(
  2756. {
  2757. eventSource: tracker,
  2758. position: getMouseRelative( isTouch ? event.changedTouches[ 0 ] : event, tracker.element ),
  2759. insideElementPressed: delegate.insideElementPressed,
  2760. buttonDownAny: IS_BUTTON_DOWN,
  2761. isTouchEvent: isTouch,
  2762. originalEvent: event,
  2763. userData: tracker.userData
  2764. }
  2765. );
  2766. if ( propagate === false ) {
  2767. $.cancelEvent( event );
  2768. }
  2769. }
  2770. }
  2771. /**
  2772. * @private
  2773. * @inner
  2774. */
  2775. function onMouseDown( tracker, event, noCapture, isTouch ) {
  2776. var delegate = THIS[ tracker.hash ],
  2777. propagate;
  2778. isTouch = isTouch || false;
  2779. event = $.getEvent(event);
  2780. var eventOrTouchPoint = isTouch ? event.touches[ 0 ] : event;
  2781. if ( event.button == 2 ) {
  2782. return;
  2783. }
  2784. delegate.insideElementPressed = true;
  2785. delegate.lastPoint = getMouseAbsolute( eventOrTouchPoint );
  2786. delegate.lastMouseDownPoint = delegate.lastPoint;
  2787. delegate.lastMouseDownTime = $.now();
  2788. if ( tracker.pressHandler ) {
  2789. propagate = tracker.pressHandler(
  2790. {
  2791. eventSource: tracker,
  2792. position: getMouseRelative( eventOrTouchPoint, tracker.element ),
  2793. isTouchEvent: isTouch,
  2794. originalEvent: event,
  2795. userData: tracker.userData
  2796. }
  2797. );
  2798. if ( propagate === false ) {
  2799. $.cancelEvent( event );
  2800. }
  2801. }
  2802. if ( tracker.pressHandler || tracker.dragHandler ) {
  2803. $.cancelEvent( event );
  2804. }
  2805. if ( noCapture ) {
  2806. return;
  2807. }
  2808. if ( isTouch ||
  2809. !( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 9 ) ||
  2810. !IS_CAPTURING ) {
  2811. captureMouse( tracker );
  2812. IS_CAPTURING = true;
  2813. // reset to empty & add us
  2814. CAPTURING = [ tracker ];
  2815. } else if ( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 9 ) {
  2816. // add us to the list
  2817. CAPTURING.push( tracker );
  2818. }
  2819. }
  2820. /**
  2821. * @private
  2822. * @inner
  2823. */
  2824. function onTouchStart( tracker, event ) {
  2825. var touchA,
  2826. touchB;
  2827. if ( event.touches.length == 1 &&
  2828. event.targetTouches.length == 1 &&
  2829. event.changedTouches.length == 1 ) {
  2830. THIS[ tracker.hash ].lastTouch = event.touches[ 0 ];
  2831. onMouseOver( tracker, event, true );
  2832. // call with no capture as the onMouseMoveCaptured will
  2833. // be triggered by onTouchMove
  2834. onMouseDown( tracker, event, true, true );
  2835. }
  2836. if ( event.touches.length == 2 ) {
  2837. touchA = getMouseAbsolute( event.touches[ 0 ] );
  2838. touchB = getMouseAbsolute( event.touches[ 1 ] );
  2839. THIS[ tracker.hash ].lastPinchDelta =
  2840. Math.abs( touchA.x - touchB.x ) +
  2841. Math.abs( touchA.y - touchB.y );
  2842. THIS[ tracker.hash ].pinchMidpoint = new $.Point(
  2843. ( touchA.x + touchB.x ) / 2,
  2844. ( touchA.y + touchB.y ) / 2
  2845. );
  2846. //$.console.debug("pinch start : "+THIS[ tracker.hash ].lastPinchDelta);
  2847. }
  2848. event.preventDefault();
  2849. }
  2850. /**
  2851. * @private
  2852. * @inner
  2853. */
  2854. function onMouseUp( tracker, event, isTouch ) {
  2855. var delegate = THIS[ tracker.hash ],
  2856. //were we inside the tracked element when we were pressed
  2857. insideElementPressed = delegate.insideElementPressed,
  2858. //are we still inside the tracked element when we released
  2859. insideElementReleased = delegate.insideElement,
  2860. propagate;
  2861. isTouch = isTouch || false;
  2862. event = $.getEvent(event);
  2863. if ( event.button == 2 ) {
  2864. return;
  2865. }
  2866. delegate.insideElementPressed = false;
  2867. if ( tracker.releaseHandler ) {
  2868. propagate = tracker.releaseHandler(
  2869. {
  2870. eventSource: tracker,
  2871. position: getMouseRelative( isTouch ? event.changedTouches[ 0 ] : event, tracker.element ),
  2872. insideElementPressed: insideElementPressed,
  2873. insideElementReleased: insideElementReleased,
  2874. isTouchEvent: isTouch,
  2875. originalEvent: event,
  2876. userData: tracker.userData
  2877. }
  2878. );
  2879. if ( propagate === false ) {
  2880. $.cancelEvent( event );
  2881. }
  2882. }
  2883. if ( insideElementPressed && insideElementReleased ) {
  2884. handleMouseClick( tracker, event, isTouch );
  2885. }
  2886. }
  2887. /**
  2888. * @private
  2889. * @inner
  2890. */
  2891. function onTouchEnd( tracker, event ) {
  2892. if ( event.touches.length === 0 &&
  2893. event.targetTouches.length === 0 &&
  2894. event.changedTouches.length == 1 ) {
  2895. THIS[ tracker.hash ].lastTouch = null;
  2896. // call with no release, as the mouse events are
  2897. // not registered in onTouchStart
  2898. onMouseUpCaptured( tracker, event, true, true );
  2899. onMouseOut( tracker, event, true );
  2900. }
  2901. if ( event.touches.length + event.changedTouches.length == 2 ) {
  2902. THIS[ tracker.hash ].lastPinchDelta = null;
  2903. THIS[ tracker.hash ].pinchMidpoint = null;
  2904. //$.console.debug("pinch end");
  2905. }
  2906. event.preventDefault();
  2907. }
  2908. /**
  2909. * Only triggered once by the deepest element that initially received
  2910. * the mouse down event. We want to make sure THIS event doesn't bubble.
  2911. * Instead, we want to trigger the elements that initially received the
  2912. * mouse down event (including this one) only if the mouse is no longer
  2913. * inside them. Then, we want to release capture, and emulate a regular
  2914. * mouseup on the event that this event was meant for.
  2915. * @private
  2916. * @inner
  2917. */
  2918. function onMouseUpIE( tracker, event ) {
  2919. var othertracker,
  2920. i;
  2921. event = $.getEvent( event );
  2922. if ( event.button == 2 ) {
  2923. return;
  2924. }
  2925. for ( i = 0; i < CAPTURING.length; i++ ) {
  2926. othertracker = CAPTURING[ i ];
  2927. if ( !hasMouse( othertracker ) ) {
  2928. onMouseUp( othertracker, event, false );
  2929. }
  2930. }
  2931. releaseMouse( tracker );
  2932. IS_CAPTURING = false;
  2933. event.srcElement.fireEvent(
  2934. "on" + event.type,
  2935. document.createEventObject( event )
  2936. );
  2937. $.stopEvent( event );
  2938. }
  2939. /**
  2940. * Only triggered in W3C browsers by elements within which the mouse was
  2941. * initially pressed, since they are now listening to the window for
  2942. * mouseup during the capture phase. We shouldn't handle the mouseup
  2943. * here if the mouse is still inside this element, since the regular
  2944. * mouseup handler will still fire.
  2945. * @private
  2946. * @inner
  2947. */
  2948. function onMouseUpCaptured( tracker, event, noRelease, isTouch ) {
  2949. isTouch = isTouch || false;
  2950. if ( !THIS[ tracker.hash ].insideElement || isTouch ) {
  2951. onMouseUp( tracker, event, isTouch );
  2952. }
  2953. if ( noRelease ) {
  2954. return;
  2955. }
  2956. releaseMouse( tracker );
  2957. }
  2958. /**
  2959. * @private
  2960. * @inner
  2961. */
  2962. function onMouseMove( tracker, event ) {
  2963. if ( tracker.moveHandler ) {
  2964. event = $.getEvent( event );
  2965. var propagate = tracker.moveHandler(
  2966. {
  2967. eventSource: tracker,
  2968. position: getMouseRelative( event, tracker.element ),
  2969. isTouchEvent: false,
  2970. originalEvent: event,
  2971. userData: tracker.userData
  2972. }
  2973. );
  2974. if ( propagate === false ) {
  2975. $.cancelEvent( event );
  2976. }
  2977. }
  2978. if ( tracker.stopHandler ) {
  2979. clearTimeout( tracker.stopTimeOut );
  2980. tracker.stopTimeOut = setTimeout( function() {
  2981. onMouseStop( tracker, event );
  2982. }, tracker.stopDelay );
  2983. }
  2984. }
  2985. /**
  2986. * @private
  2987. * @inner
  2988. */
  2989. function onMouseStop( tracker, originalMoveEvent ) {
  2990. if ( tracker.stopHandler ) {
  2991. tracker.stopHandler( {
  2992. eventSource: tracker,
  2993. position: getMouseRelative( originalMoveEvent, tracker.element ),
  2994. isTouchEvent: false,
  2995. originalEvent: originalMoveEvent,
  2996. userData: tracker.userData
  2997. } );
  2998. }
  2999. }
  3000. /**
  3001. * @private
  3002. * @inner
  3003. */
  3004. function onMouseClick( tracker, event ) {
  3005. if ( tracker.clickHandler ) {
  3006. $.cancelEvent( event );
  3007. }
  3008. }
  3009. /**
  3010. * Handler for 'wheel' events
  3011. *
  3012. * @private
  3013. * @inner
  3014. */
  3015. function onWheel( tracker, event ) {
  3016. handleWheelEvent( tracker, event, event, false );
  3017. }
  3018. /**
  3019. * Handler for 'mousewheel', 'DOMMouseScroll', and 'MozMousePixelScroll' events
  3020. *
  3021. * @private
  3022. * @inner
  3023. */
  3024. function onMouseWheel( tracker, event ) {
  3025. // For legacy IE, access the global (window) event object
  3026. event = event || window.event;
  3027. // Simulate a 'wheel' event
  3028. var simulatedEvent = {
  3029. target: event.target || event.srcElement,
  3030. type: "wheel",
  3031. shiftKey: event.shiftKey || false,
  3032. clientX: event.clientX,
  3033. clientY: event.clientY,
  3034. pageX: event.pageX ? event.pageX : event.clientX,
  3035. pageY: event.pageY ? event.pageY : event.clientY,
  3036. deltaMode: event.type == "MozMousePixelScroll" ? 0 : 1, // 0=pixel, 1=line, 2=page
  3037. deltaX: 0,
  3038. deltaZ: 0
  3039. };
  3040. // Calculate deltaY
  3041. if ( $.MouseTracker.wheelEventName == "mousewheel" ) {
  3042. simulatedEvent.deltaY = - 1 / $.DEFAULT_SETTINGS.pixelsPerWheelLine * event.wheelDelta;
  3043. } else {
  3044. simulatedEvent.deltaY = event.detail;
  3045. }
  3046. handleWheelEvent( tracker, simulatedEvent, event, false );
  3047. }
  3048. /**
  3049. * Handles 'wheel' events.
  3050. * The event may be simulated by the legacy mouse wheel event handler (onMouseWheel()) or onTouchMove().
  3051. *
  3052. * @private
  3053. * @inner
  3054. */
  3055. function handleWheelEvent( tracker, event, originalEvent, isTouch ) {
  3056. var nDelta = 0,
  3057. propagate;
  3058. isTouch = isTouch || false;
  3059. // The nDelta variable is gated to provide smooth z-index scrolling
  3060. // since the mouse wheel allows for substantial deltas meant for rapid
  3061. // y-index scrolling.
  3062. // event.deltaMode: 0=pixel, 1=line, 2=page
  3063. // TODO: Deltas in pixel mode should be accumulated then a scroll value computed after $.DEFAULT_SETTINGS.pixelsPerWheelLine threshold reached
  3064. nDelta = event.deltaY < 0 ? 1 : -1;
  3065. if ( tracker.scrollHandler ) {
  3066. propagate = tracker.scrollHandler(
  3067. {
  3068. eventSource: tracker,
  3069. position: getMouseRelative( event, tracker.element ),
  3070. scroll: nDelta,
  3071. shift: event.shiftKey,
  3072. isTouchEvent: isTouch,
  3073. originalEvent: originalEvent,
  3074. userData: tracker.userData
  3075. }
  3076. );
  3077. if ( propagate === false ) {
  3078. $.cancelEvent( originalEvent );
  3079. }
  3080. }
  3081. }
  3082. /**
  3083. * @private
  3084. * @inner
  3085. */
  3086. function handleMouseClick( tracker, event, isTouch ) {
  3087. var delegate = THIS[ tracker.hash ],
  3088. propagate;
  3089. isTouch = isTouch || false;
  3090. event = $.getEvent( event );
  3091. var eventOrTouchPoint = isTouch ? event.changedTouches[ 0 ] : event;
  3092. if ( event.button == 2 ) {
  3093. return;
  3094. }
  3095. var time = $.now() - delegate.lastMouseDownTime,
  3096. point = getMouseAbsolute( eventOrTouchPoint ),
  3097. distance = delegate.lastMouseDownPoint.distanceTo( point ),
  3098. quick = time <= tracker.clickTimeThreshold &&
  3099. distance <= tracker.clickDistThreshold;
  3100. if ( tracker.clickHandler ) {
  3101. propagate = tracker.clickHandler(
  3102. {
  3103. eventSource: tracker,
  3104. position: getMouseRelative( eventOrTouchPoint, tracker.element ),
  3105. quick: quick,
  3106. shift: event.shiftKey,
  3107. isTouchEvent: isTouch,
  3108. originalEvent: event,
  3109. userData: tracker.userData
  3110. }
  3111. );
  3112. if ( propagate === false ) {
  3113. $.cancelEvent( event );
  3114. }
  3115. }
  3116. }
  3117. /**
  3118. * @private
  3119. * @inner
  3120. */
  3121. function onMouseMoveCaptured( tracker, event, isTouch ) {
  3122. var delegate = THIS[ tracker.hash ],
  3123. delta,
  3124. propagate,
  3125. point;
  3126. isTouch = isTouch || false;
  3127. event = $.getEvent(event);
  3128. var eventOrTouchPoint = isTouch ? event.touches[ 0 ] : event;
  3129. point = getMouseAbsolute( eventOrTouchPoint );
  3130. delta = point.minus( delegate.lastPoint );
  3131. delegate.lastPoint = point;
  3132. if ( tracker.dragHandler ) {
  3133. propagate = tracker.dragHandler(
  3134. {
  3135. eventSource: tracker,
  3136. position: getMouseRelative( eventOrTouchPoint, tracker.element ),
  3137. delta: delta,
  3138. shift: event.shiftKey,
  3139. isTouchEvent: isTouch,
  3140. originalEvent: event,
  3141. userData: tracker.userData
  3142. }
  3143. );
  3144. if ( propagate === false ) {
  3145. $.cancelEvent( event );
  3146. }
  3147. }
  3148. }
  3149. /**
  3150. * @private
  3151. * @inner
  3152. */
  3153. function onTouchMove( tracker, event ) {
  3154. var touchA,
  3155. touchB,
  3156. pinchDelta;
  3157. if ( !THIS[ tracker.hash ].lastTouch ) {
  3158. return;
  3159. }
  3160. if ( event.touches.length === 1 &&
  3161. event.targetTouches.length === 1 &&
  3162. event.changedTouches.length === 1 &&
  3163. THIS[ tracker.hash ].lastTouch.identifier === event.touches[ 0 ].identifier ) {
  3164. onMouseMoveCaptured( tracker, event, true );
  3165. } else if ( event.touches.length === 2 ) {
  3166. touchA = getMouseAbsolute( event.touches[ 0 ] );
  3167. touchB = getMouseAbsolute( event.touches[ 1 ] );
  3168. pinchDelta =
  3169. Math.abs( touchA.x - touchB.x ) +
  3170. Math.abs( touchA.y - touchB.y );
  3171. //TODO: make the 75px pinch threshold configurable
  3172. if ( Math.abs( THIS[ tracker.hash ].lastPinchDelta - pinchDelta ) > 75 ) {
  3173. //$.console.debug( "pinch delta : " + pinchDelta + " | previous : " + THIS[ tracker.hash ].lastPinchDelta);
  3174. // Simulate a 'wheel' event
  3175. var simulatedEvent = {
  3176. target: event.target || event.srcElement,
  3177. type: "wheel",
  3178. shiftKey: event.shiftKey || false,
  3179. clientX: THIS[ tracker.hash ].pinchMidpoint.x,
  3180. clientY: THIS[ tracker.hash ].pinchMidpoint.y,
  3181. pageX: THIS[ tracker.hash ].pinchMidpoint.x,
  3182. pageY: THIS[ tracker.hash ].pinchMidpoint.y,
  3183. deltaMode: 1, // 0=pixel, 1=line, 2=page
  3184. deltaX: 0,
  3185. deltaY: ( THIS[ tracker.hash ].lastPinchDelta > pinchDelta ) ? 1 : -1,
  3186. deltaZ: 0
  3187. };
  3188. handleWheelEvent( tracker, simulatedEvent, event, true );
  3189. THIS[ tracker.hash ].lastPinchDelta = pinchDelta;
  3190. }
  3191. }
  3192. event.preventDefault();
  3193. }
  3194. /**
  3195. * Only triggered once by the deepest element that initially received
  3196. * the mouse down event. Since no other element has captured the mouse,
  3197. * we want to trigger the elements that initially received the mouse
  3198. * down event (including this one). The the param tracker isn't used
  3199. * but for consistency with the other event handlers we include it.
  3200. * @private
  3201. * @inner
  3202. */
  3203. function onMouseMoveCapturedIE( tracker, event ) {
  3204. var i;
  3205. for ( i = 0; i < CAPTURING.length; i++ ) {
  3206. onMouseMoveCaptured( CAPTURING[ i ], event, false );
  3207. }
  3208. $.stopEvent( event );
  3209. }
  3210. /**
  3211. * @private
  3212. * @inner
  3213. */
  3214. function getMouseAbsolute( event ) {
  3215. return $.getMousePosition( event );
  3216. }
  3217. /**
  3218. * @private
  3219. * @inner
  3220. */
  3221. function getMouseRelative( event, element ) {
  3222. var mouse = $.getMousePosition( event ),
  3223. offset = $.getElementOffset( element );
  3224. return mouse.minus( offset );
  3225. }
  3226. /**
  3227. * @private
  3228. * @inner
  3229. * Returns true if elementB is a child node of elementA, or if they're equal.
  3230. */
  3231. function isChild( elementA, elementB ) {
  3232. var body = document.body;
  3233. while ( elementB && elementA != elementB && body != elementB ) {
  3234. try {
  3235. elementB = elementB.parentNode;
  3236. } catch ( e ) {
  3237. return false;
  3238. }
  3239. }
  3240. return elementA == elementB;
  3241. }
  3242. /**
  3243. * @private
  3244. * @inner
  3245. */
  3246. function onGlobalMouseDown() {
  3247. IS_BUTTON_DOWN = true;
  3248. }
  3249. /**
  3250. * @private
  3251. * @inner
  3252. */
  3253. function onGlobalMouseUp() {
  3254. IS_BUTTON_DOWN = false;
  3255. }
  3256. (function () {
  3257. if ( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 9 ) {
  3258. $.addEvent( document, "mousedown", onGlobalMouseDown, false );
  3259. $.addEvent( document, "mouseup", onGlobalMouseUp, false );
  3260. } else {
  3261. $.addEvent( window, "mousedown", onGlobalMouseDown, true );
  3262. $.addEvent( window, "mouseup", onGlobalMouseUp, true );
  3263. }
  3264. } )();
  3265. } ( OpenSeadragon ) );
  3266. /*
  3267. * OpenSeadragon - Control
  3268. *
  3269. * Copyright (C) 2009 CodePlex Foundation
  3270. * Copyright (C) 2010-2013 OpenSeadragon contributors
  3271. *
  3272. * Redistribution and use in source and binary forms, with or without
  3273. * modification, are permitted provided that the following conditions are
  3274. * met:
  3275. *
  3276. * - Redistributions of source code must retain the above copyright notice,
  3277. * this list of conditions and the following disclaimer.
  3278. *
  3279. * - Redistributions in binary form must reproduce the above copyright
  3280. * notice, this list of conditions and the following disclaimer in the
  3281. * documentation and/or other materials provided with the distribution.
  3282. *
  3283. * - Neither the name of CodePlex Foundation nor the names of its
  3284. * contributors may be used to endorse or promote products derived from
  3285. * this software without specific prior written permission.
  3286. *
  3287. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  3288. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  3289. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  3290. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  3291. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  3292. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  3293. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  3294. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  3295. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  3296. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  3297. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  3298. */
  3299. (function( $ ){
  3300. /**
  3301. * An enumeration of supported locations where controls can be anchored,
  3302. * including NONE, TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT, and BOTTOM_LEFT.
  3303. * The anchoring is always relative to the container
  3304. * @static
  3305. */
  3306. $.ControlAnchor = {
  3307. NONE: 0,
  3308. TOP_LEFT: 1,
  3309. TOP_RIGHT: 2,
  3310. BOTTOM_RIGHT: 3,
  3311. BOTTOM_LEFT: 4
  3312. };
  3313. /**
  3314. * A Control represents any interface element which is meant to allow the user
  3315. * to interact with the zoomable interface. Any control can be anchored to any
  3316. * element.
  3317. * @class
  3318. * @param {Element} element - the control element to be anchored in the container.
  3319. * @param {Object } options - All required and optional settings for configuring a control element.
  3320. * @param {OpenSeadragon.ControlAnchor} [options.anchor=OpenSeadragon.ControlAnchor.NONE] - the position of the control
  3321. * relative to the container.
  3322. * @param {Boolean} [options.attachToViewer=true] - Whether the control should be added directly to the viewer, or
  3323. * directly to the container
  3324. * @param {Boolean} [options.autoFade=true] - Whether the control should have the autofade behavior
  3325. * @param {Element} container - the element to control will be anchored too.
  3326. *
  3327. * @property {Element} element - the element providing the user interface with
  3328. * some type of control. Eg a zoom-in button
  3329. * @property {OpenSeadragon.ControlAnchor} anchor - the position of the control
  3330. * relative to the container.
  3331. * @property {Boolean} autoFade - Whether the control should have the autofade behavior
  3332. * @property {Element} container - the element within with the control is
  3333. * positioned.
  3334. * @property {Element} wrapper - a neutral element surrounding the control
  3335. * element.
  3336. */
  3337. $.Control = function ( element, options, container ) {
  3338. var parent = element.parentNode;
  3339. if (typeof options === 'number')
  3340. {
  3341. $.console.error("Passing an anchor directly into the OpenSeadragon.Control constructor is deprecated; " +
  3342. "please use an options object instead. " +
  3343. "Support for this deprecated variant is scheduled for removal in December 2013");
  3344. options = {anchor: options};
  3345. }
  3346. options.attachToViewer = (typeof options.attachToViewer === 'undefined') ? true : options.attachToViewer;
  3347. this.autoFade = (typeof options.autoFade === 'undefined') ? true : options.autoFade;
  3348. this.element = element;
  3349. this.anchor = options.anchor;
  3350. this.container = container;
  3351. this.wrapper = $.makeNeutralElement( "span" );
  3352. this.wrapper.style.display = "inline-block";
  3353. this.wrapper.appendChild( this.element );
  3354. if ( this.anchor == $.ControlAnchor.NONE ) {
  3355. // IE6 fix
  3356. this.wrapper.style.width = this.wrapper.style.height = "100%";
  3357. }
  3358. if (options.attachToViewer ) {
  3359. if ( this.anchor == $.ControlAnchor.TOP_RIGHT ||
  3360. this.anchor == $.ControlAnchor.BOTTOM_RIGHT ) {
  3361. this.container.insertBefore(
  3362. this.wrapper,
  3363. this.container.firstChild
  3364. );
  3365. } else {
  3366. this.container.appendChild( this.wrapper );
  3367. }
  3368. } else {
  3369. parent.appendChild( this.wrapper );
  3370. }
  3371. };
  3372. $.Control.prototype = {
  3373. /**
  3374. * Removes the control from the container.
  3375. * @function
  3376. */
  3377. destroy: function() {
  3378. this.wrapper.removeChild( this.element );
  3379. this.container.removeChild( this.wrapper );
  3380. },
  3381. /**
  3382. * Determines if the control is currently visible.
  3383. * @function
  3384. * @return {Boolean} true if currenly visible, false otherwise.
  3385. */
  3386. isVisible: function() {
  3387. return this.wrapper.style.display != "none";
  3388. },
  3389. /**
  3390. * Toggles the visibility of the control.
  3391. * @function
  3392. * @param {Boolean} visible - true to make visible, false to hide.
  3393. */
  3394. setVisible: function( visible ) {
  3395. this.wrapper.style.display = visible ?
  3396. "inline-block" :
  3397. "none";
  3398. },
  3399. /**
  3400. * Sets the opacity level for the control.
  3401. * @function
  3402. * @param {Number} opactiy - a value between 1 and 0 inclusively.
  3403. */
  3404. setOpacity: function( opacity ) {
  3405. if ( this.element[ $.SIGNAL ] && $.Browser.vendor == $.BROWSERS.IE ) {
  3406. $.setElementOpacity( this.element, opacity, true );
  3407. } else {
  3408. $.setElementOpacity( this.wrapper, opacity, true );
  3409. }
  3410. }
  3411. };
  3412. }( OpenSeadragon ));
  3413. /*
  3414. * OpenSeadragon - ControlDock
  3415. *
  3416. * Copyright (C) 2009 CodePlex Foundation
  3417. * Copyright (C) 2010-2013 OpenSeadragon contributors
  3418. *
  3419. * Redistribution and use in source and binary forms, with or without
  3420. * modification, are permitted provided that the following conditions are
  3421. * met:
  3422. *
  3423. * - Redistributions of source code must retain the above copyright notice,
  3424. * this list of conditions and the following disclaimer.
  3425. *
  3426. * - Redistributions in binary form must reproduce the above copyright
  3427. * notice, this list of conditions and the following disclaimer in the
  3428. * documentation and/or other materials provided with the distribution.
  3429. *
  3430. * - Neither the name of CodePlex Foundation nor the names of its
  3431. * contributors may be used to endorse or promote products derived from
  3432. * this software without specific prior written permission.
  3433. *
  3434. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  3435. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  3436. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  3437. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  3438. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  3439. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  3440. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  3441. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  3442. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  3443. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  3444. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  3445. */
  3446. (function( $ ){
  3447. /**
  3448. * @class
  3449. */
  3450. $.ControlDock = function( options ){
  3451. var layouts = [ 'topleft', 'topright', 'bottomright', 'bottomleft'],
  3452. layout,
  3453. i;
  3454. $.extend( true, this, {
  3455. id: 'controldock-'+$.now()+'-'+Math.floor(Math.random()*1000000),
  3456. container: $.makeNeutralElement('form'),
  3457. controls: []
  3458. }, options );
  3459. // Disable the form's submit; otherwise button clicks and return keys
  3460. // can trigger it.
  3461. this.container.onsubmit = function() {
  3462. return false;
  3463. };
  3464. if( this.element ){
  3465. this.element = $.getElement( this.element );
  3466. this.element.appendChild( this.container );
  3467. this.element.style.position = 'relative';
  3468. this.container.style.width = '100%';
  3469. this.container.style.height = '100%';
  3470. }
  3471. for( i = 0; i < layouts.length; i++ ){
  3472. layout = layouts[ i ];
  3473. this.controls[ layout ] = $.makeNeutralElement( "div" );
  3474. this.controls[ layout ].style.position = 'absolute';
  3475. if ( layout.match( 'left' ) ){
  3476. this.controls[ layout ].style.left = '0px';
  3477. }
  3478. if ( layout.match( 'right' ) ){
  3479. this.controls[ layout ].style.right = '0px';
  3480. }
  3481. if ( layout.match( 'top' ) ){
  3482. this.controls[ layout ].style.top = '0px';
  3483. }
  3484. if ( layout.match( 'bottom' ) ){
  3485. this.controls[ layout ].style.bottom = '0px';
  3486. }
  3487. }
  3488. this.container.appendChild( this.controls.topleft );
  3489. this.container.appendChild( this.controls.topright );
  3490. this.container.appendChild( this.controls.bottomright );
  3491. this.container.appendChild( this.controls.bottomleft );
  3492. };
  3493. $.ControlDock.prototype = {
  3494. /**
  3495. * @function
  3496. */
  3497. addControl: function ( element, controlOptions ) {
  3498. element = $.getElement( element );
  3499. var div = null;
  3500. if ( getControlIndex( this, element ) >= 0 ) {
  3501. return; // they're trying to add a duplicate control
  3502. }
  3503. switch ( controlOptions.anchor ) {
  3504. case $.ControlAnchor.TOP_RIGHT:
  3505. div = this.controls.topright;
  3506. element.style.position = "relative";
  3507. element.style.paddingRight = "0px";
  3508. element.style.paddingTop = "0px";
  3509. break;
  3510. case $.ControlAnchor.BOTTOM_RIGHT:
  3511. div = this.controls.bottomright;
  3512. element.style.position = "relative";
  3513. element.style.paddingRight = "0px";
  3514. element.style.paddingBottom = "0px";
  3515. break;
  3516. case $.ControlAnchor.BOTTOM_LEFT:
  3517. div = this.controls.bottomleft;
  3518. element.style.position = "relative";
  3519. element.style.paddingLeft = "0px";
  3520. element.style.paddingBottom = "0px";
  3521. break;
  3522. case $.ControlAnchor.TOP_LEFT:
  3523. div = this.controls.topleft;
  3524. element.style.position = "relative";
  3525. element.style.paddingLeft = "0px";
  3526. element.style.paddingTop = "0px";
  3527. break;
  3528. default:
  3529. case $.ControlAnchor.NONE:
  3530. div = this.container;
  3531. element.style.margin = "0px";
  3532. element.style.padding = "0px";
  3533. break;
  3534. }
  3535. this.controls.push(
  3536. new $.Control( element, controlOptions, div )
  3537. );
  3538. element.style.display = "inline-block";
  3539. },
  3540. /**
  3541. * @function
  3542. * @return {OpenSeadragon.ControlDock} Chainable.
  3543. */
  3544. removeControl: function ( element ) {
  3545. element = $.getElement( element );
  3546. var i = getControlIndex( this, element );
  3547. if ( i >= 0 ) {
  3548. this.controls[ i ].destroy();
  3549. this.controls.splice( i, 1 );
  3550. }
  3551. return this;
  3552. },
  3553. /**
  3554. * @function
  3555. * @return {OpenSeadragon.ControlDock} Chainable.
  3556. */
  3557. clearControls: function () {
  3558. while ( this.controls.length > 0 ) {
  3559. this.controls.pop().destroy();
  3560. }
  3561. return this;
  3562. },
  3563. /**
  3564. * @function
  3565. * @return {Boolean}
  3566. */
  3567. areControlsEnabled: function () {
  3568. var i;
  3569. for ( i = this.controls.length - 1; i >= 0; i-- ) {
  3570. if ( this.controls[ i ].isVisible() ) {
  3571. return true;
  3572. }
  3573. }
  3574. return false;
  3575. },
  3576. /**
  3577. * @function
  3578. * @return {OpenSeadragon.ControlDock} Chainable.
  3579. */
  3580. setControlsEnabled: function( enabled ) {
  3581. var i;
  3582. for ( i = this.controls.length - 1; i >= 0; i-- ) {
  3583. this.controls[ i ].setVisible( enabled );
  3584. }
  3585. return this;
  3586. }
  3587. };
  3588. ///////////////////////////////////////////////////////////////////////////////
  3589. // Utility methods
  3590. ///////////////////////////////////////////////////////////////////////////////
  3591. function getControlIndex( dock, element ) {
  3592. var controls = dock.controls,
  3593. i;
  3594. for ( i = controls.length - 1; i >= 0; i-- ) {
  3595. if ( controls[ i ].element == element ) {
  3596. return i;
  3597. }
  3598. }
  3599. return -1;
  3600. }
  3601. }( OpenSeadragon ));
  3602. /*
  3603. * OpenSeadragon - Viewer
  3604. *
  3605. * Copyright (C) 2009 CodePlex Foundation
  3606. * Copyright (C) 2010-2013 OpenSeadragon contributors
  3607. *
  3608. * Redistribution and use in source and binary forms, with or without
  3609. * modification, are permitted provided that the following conditions are
  3610. * met:
  3611. *
  3612. * - Redistributions of source code must retain the above copyright notice,
  3613. * this list of conditions and the following disclaimer.
  3614. *
  3615. * - Redistributions in binary form must reproduce the above copyright
  3616. * notice, this list of conditions and the following disclaimer in the
  3617. * documentation and/or other materials provided with the distribution.
  3618. *
  3619. * - Neither the name of CodePlex Foundation nor the names of its
  3620. * contributors may be used to endorse or promote products derived from
  3621. * this software without specific prior written permission.
  3622. *
  3623. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  3624. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  3625. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  3626. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  3627. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  3628. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  3629. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  3630. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  3631. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  3632. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  3633. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  3634. */
  3635. (function( $ ){
  3636. // dictionary from hash to private properties
  3637. var THIS = {},
  3638. // We keep a list of viewers so we can 'wake-up' each viewer on
  3639. // a page after toggling between fullpage modes
  3640. VIEWERS = {};
  3641. /**
  3642. *
  3643. * The main point of entry into creating a zoomable image on the page.
  3644. *
  3645. * We have provided an idiomatic javascript constructor which takes
  3646. * a single object, but still support the legacy positional arguments.
  3647. *
  3648. * The options below are given in order that they appeared in the constructor
  3649. * as arguments and we translate a positional call into an idiomatic call.
  3650. *
  3651. * @class
  3652. * @extends OpenSeadragon.EventSource
  3653. * @extends OpenSeadragon.ControlDock
  3654. * @param {Object} options
  3655. * @param {String} options.element Id of Element to attach to,
  3656. * @param {String} options.xmlPath Xpath ( TODO: not sure! ),
  3657. * @param {String} options.prefixUrl Url used to prepend to paths, eg button
  3658. * images, etc.
  3659. * @param {OpenSeadragon.Control[]} options.controls Array of OpenSeadragon.Control,
  3660. * @param {OpenSeadragon.Overlay[]} options.overlays Array of OpenSeadragon.Overlay,
  3661. * @param {OpenSeadragon.Control[]} options.overlayControls An Array of ( TODO:
  3662. * not sure! )
  3663. * @property {OpenSeadragon.Viewport} viewport The viewer's viewport, where you
  3664. * can access zoom, pan, etc.
  3665. *
  3666. **/
  3667. $.Viewer = function( options ) {
  3668. var args = arguments,
  3669. _this = this,
  3670. i;
  3671. //backward compatibility for positional args while prefering more
  3672. //idiomatic javascript options object as the only argument
  3673. if( !$.isPlainObject( options ) ){
  3674. options = {
  3675. id: args[ 0 ],
  3676. xmlPath: args.length > 1 ? args[ 1 ] : undefined,
  3677. prefixUrl: args.length > 2 ? args[ 2 ] : undefined,
  3678. controls: args.length > 3 ? args[ 3 ] : undefined,
  3679. overlays: args.length > 4 ? args[ 4 ] : undefined,
  3680. overlayControls: args.length > 5 ? args[ 5 ] : undefined
  3681. };
  3682. }
  3683. //options.config and the general config argument are deprecated
  3684. //in favor of the more direct specification of optional settings
  3685. //being pass directly on the options object
  3686. if ( options.config ){
  3687. $.extend( true, options, options.config );
  3688. delete options.config;
  3689. }
  3690. //Public properties
  3691. //Allow the options object to override global defaults
  3692. $.extend( true, this, {
  3693. //internal state and dom identifiers
  3694. id: options.id,
  3695. hash: options.hash || options.id,
  3696. //dom nodes
  3697. element: null,
  3698. canvas: null,
  3699. container: null,
  3700. //TODO: not sure how to best describe these
  3701. overlays: [],
  3702. overlayControls:[],
  3703. //private state properties
  3704. previousBody: [],
  3705. //This was originally initialized in the constructor and so could never
  3706. //have anything in it. now it can because we allow it to be specified
  3707. //in the options and is only empty by default if not specified. Also
  3708. //this array was returned from get_controls which I find confusing
  3709. //since this object has a controls property which is treated in other
  3710. //functions like clearControls. I'm removing the accessors.
  3711. customControls: [],
  3712. //These are originally not part options but declared as members
  3713. //in initialize. Its still considered idiomatic to put them here
  3714. source: null,
  3715. drawer: null,
  3716. drawers: [],
  3717. viewport: null,
  3718. navigator: null,
  3719. //A collection viewport is a seperate viewport used to provide
  3720. //simultanious rendering of sets of tiless
  3721. collectionViewport: null,
  3722. collectionDrawer: null,
  3723. //UI image resources
  3724. //TODO: rename navImages to uiImages
  3725. navImages: null,
  3726. //interface button controls
  3727. buttons: null,
  3728. //TODO: this is defunct so safely remove it
  3729. profiler: null
  3730. }, $.DEFAULT_SETTINGS, options );
  3731. if ( typeof( this.hash) === "undefined" ) {
  3732. throw new Error("A hash must be defined, either by specifying options.id or options.hash.");
  3733. }
  3734. if ( typeof( THIS[ this.hash ] ) !== "undefined" ) {
  3735. // We don't want to throw an error here, as the user might have discarded
  3736. // the previous viewer with the same hash and now want to recreate it.
  3737. $.console.warn("Hash " + this.hash + " has already been used.");
  3738. }
  3739. //Private state properties
  3740. THIS[ this.hash ] = {
  3741. "fsBoundsDelta": new $.Point( 1, 1 ),
  3742. "prevContainerSize": null,
  3743. "animating": false,
  3744. "forceRedraw": false,
  3745. "mouseInside": false,
  3746. "group": null,
  3747. // whether we should be continuously zooming
  3748. "zooming": false,
  3749. // how much we should be continuously zooming by
  3750. "zoomFactor": null,
  3751. "lastZoomTime": null,
  3752. // did we decide this viewer has a sequence of tile sources
  3753. "sequenced": false,
  3754. "sequence": 0,
  3755. "fullPage": false,
  3756. "onfullscreenchange": null
  3757. };
  3758. this._updateRequestId = null;
  3759. //Inherit some behaviors and properties
  3760. $.EventSource.call( this );
  3761. this.addHandler( 'open-failed', function ( event ) {
  3762. var msg = $.getString( "Errors.OpenFailed", event.eventSource, event.message);
  3763. _this._showMessage( msg );
  3764. });
  3765. $.ControlDock.call( this, options );
  3766. //Deal with tile sources
  3767. var initialTileSource;
  3768. if ( this.xmlPath ){
  3769. //Deprecated option. Now it is preferred to use the tileSources option
  3770. this.tileSources = [ this.xmlPath ];
  3771. }
  3772. if ( this.tileSources ){
  3773. // tileSources is a complex option...
  3774. //
  3775. // It can be a string, object, or an array of any of strings and objects.
  3776. // At this point we only care about if it is an Array or not.
  3777. //
  3778. if( $.isArray( this.tileSources ) ){
  3779. //must be a sequence of tileSource since the first item
  3780. //is a legacy tile source
  3781. if( this.tileSources.length > 1 ){
  3782. THIS[ this.hash ].sequenced = true;
  3783. }
  3784. //Keeps the initial page within bounds
  3785. if ( this.initialPage > this.tileSources.length - 1 ){
  3786. this.initialPage = this.tileSources.length - 1;
  3787. }
  3788. initialTileSource = this.tileSources[ this.initialPage ];
  3789. //Update the sequence (aka currrent page) property
  3790. THIS[ this.hash ].sequence = this.initialPage;
  3791. } else {
  3792. initialTileSource = this.tileSources;
  3793. }
  3794. }
  3795. this.element = this.element || document.getElementById( this.id );
  3796. this.canvas = $.makeNeutralElement( "div" );
  3797. this.keyboardCommandArea = $.makeNeutralElement( "textarea" );
  3798. this.canvas.className = "openseadragon-canvas";
  3799. (function( style ){
  3800. style.width = "100%";
  3801. style.height = "100%";
  3802. style.overflow = "hidden";
  3803. style.position = "absolute";
  3804. style.top = "0px";
  3805. style.left = "0px";
  3806. }( this.canvas.style ));
  3807. //the container is created through applying the ControlDock constructor above
  3808. this.container.className = "openseadragon-container";
  3809. (function( style ){
  3810. style.width = "100%";
  3811. style.height = "100%";
  3812. style.position = "relative";
  3813. style.overflow = "hidden";
  3814. style.left = "0px";
  3815. style.top = "0px";
  3816. style.textAlign = "left"; // needed to protect against
  3817. }( this.container.style ));
  3818. this.keyboardCommandArea.className = "keyboard-command-area";
  3819. (function( style ){
  3820. style.width = "100%";
  3821. style.height = "100%";
  3822. style.overflow = "hidden";
  3823. style.position = "absolute";
  3824. style.top = "0px";
  3825. style.left = "0px";
  3826. style.resize = "none";
  3827. }( this.keyboardCommandArea.style ));
  3828. this.container.insertBefore( this.canvas, this.container.firstChild );
  3829. this.container.insertBefore( this.keyboardCommandArea, this.container.firstChild );
  3830. this.element.appendChild( this.container );
  3831. //Used for toggling between fullscreen and default container size
  3832. //TODO: these can be closure private and shared across Viewer
  3833. // instances.
  3834. this.bodyWidth = document.body.style.width;
  3835. this.bodyHeight = document.body.style.height;
  3836. this.bodyOverflow = document.body.style.overflow;
  3837. this.docOverflow = document.documentElement.style.overflow;
  3838. this.keyboardCommandArea.innerTracker = new $.MouseTracker({
  3839. _this : this,
  3840. element: this.keyboardCommandArea,
  3841. focusHandler: function(){
  3842. var point = $.getElementPosition( this.element );
  3843. window.scrollTo( 0, point.y );
  3844. },
  3845. keyHandler: function( event ){
  3846. switch( event.keyCode ){
  3847. case 61://=|+
  3848. _this.viewport.zoomBy(1.1);
  3849. _this.viewport.applyConstraints();
  3850. return false;
  3851. case 45://-|_
  3852. _this.viewport.zoomBy(0.9);
  3853. _this.viewport.applyConstraints();
  3854. return false;
  3855. case 48://0|)
  3856. _this.viewport.goHome();
  3857. _this.viewport.applyConstraints();
  3858. return false;
  3859. case 119://w
  3860. case 87://W
  3861. case 38://up arrow
  3862. if ( event.shift ) {
  3863. _this.viewport.zoomBy(1.1);
  3864. } else {
  3865. _this.viewport.panBy(new $.Point(0, -0.05));
  3866. }
  3867. _this.viewport.applyConstraints();
  3868. return false;
  3869. case 115://s
  3870. case 83://S
  3871. case 40://down arrow
  3872. if ( event.shift ) {
  3873. _this.viewport.zoomBy(0.9);
  3874. } else {
  3875. _this.viewport.panBy(new $.Point(0, 0.05));
  3876. }
  3877. _this.viewport.applyConstraints();
  3878. return false;
  3879. case 97://a
  3880. case 37://left arrow
  3881. _this.viewport.panBy(new $.Point(-0.05, 0));
  3882. _this.viewport.applyConstraints();
  3883. return false;
  3884. case 100://d
  3885. case 39://right arrow
  3886. _this.viewport.panBy(new $.Point(0.05, 0));
  3887. _this.viewport.applyConstraints();
  3888. return false;
  3889. default:
  3890. //console.log( 'navigator keycode %s', event.keyCode );
  3891. return true;
  3892. }
  3893. }
  3894. }).setTracking( true ); // default state
  3895. this.innerTracker = new $.MouseTracker({
  3896. element: this.canvas,
  3897. clickTimeThreshold: this.clickTimeThreshold,
  3898. clickDistThreshold: this.clickDistThreshold,
  3899. clickHandler: $.delegate( this, onCanvasClick ),
  3900. dragHandler: $.delegate( this, onCanvasDrag ),
  3901. releaseHandler: $.delegate( this, onCanvasRelease ),
  3902. scrollHandler: $.delegate( this, onCanvasScroll )
  3903. }).setTracking( this.mouseNavEnabled ? true : false ); // default state
  3904. this.outerTracker = new $.MouseTracker({
  3905. element: this.container,
  3906. clickTimeThreshold: this.clickTimeThreshold,
  3907. clickDistThreshold: this.clickDistThreshold,
  3908. enterHandler: $.delegate( this, onContainerEnter ),
  3909. exitHandler: $.delegate( this, onContainerExit ),
  3910. releaseHandler: $.delegate( this, onContainerRelease )
  3911. }).setTracking( this.mouseNavEnabled ? true : false ); // always tracking
  3912. if( this.toolbar ){
  3913. this.toolbar = new $.ControlDock({ element: this.toolbar });
  3914. }
  3915. this.bindStandardControls();
  3916. this.bindSequenceControls();
  3917. if ( initialTileSource ) {
  3918. this.open( initialTileSource );
  3919. if ( this.tileSources.length > 1 ) {
  3920. this._updateSequenceButtons( this.initialPage );
  3921. }
  3922. }
  3923. for ( i = 0; i < this.customControls.length; i++ ) {
  3924. this.addControl(
  3925. this.customControls[ i ].id,
  3926. {anchor: this.customControls[ i ].anchor}
  3927. );
  3928. }
  3929. $.requestAnimationFrame( function(){
  3930. beginControlsAutoHide( _this );
  3931. } ); // initial fade out
  3932. };
  3933. $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, {
  3934. /**
  3935. * @function
  3936. * @name OpenSeadragon.Viewer.prototype.isOpen
  3937. * @return {Boolean}
  3938. */
  3939. isOpen: function () {
  3940. return !!this.source;
  3941. },
  3942. /**
  3943. * A deprecated function, renamed to 'open' to match event name and
  3944. * match current 'close' method.
  3945. * @function
  3946. * @name OpenSeadragon.Viewer.prototype.openDzi
  3947. * @param {String} dzi xml string or the url to a DZI xml document.
  3948. * @return {OpenSeadragon.Viewer} Chainable.
  3949. *
  3950. * @deprecated - use 'open' instead.
  3951. */
  3952. openDzi: function ( dzi ) {
  3953. return this.open( dzi );
  3954. },
  3955. /**
  3956. * A deprecated function, renamed to 'open' to match event name and
  3957. * match current 'close' method.
  3958. * @function
  3959. * @name OpenSeadragon.Viewer.prototype.openTileSource
  3960. * @param {String|Object|Function} See OpenSeadragon.Viewer.prototype.open
  3961. * @return {OpenSeadragon.Viewer} Chainable.
  3962. *
  3963. * @deprecated - use 'open' instead.
  3964. */
  3965. openTileSource: function ( tileSource ) {
  3966. return this.open( tileSource );
  3967. },
  3968. /**
  3969. * Open a TileSource object into the viewer.
  3970. *
  3971. * tileSources is a complex option...
  3972. *
  3973. * It can be a string, object, function, or an array of any of these:
  3974. *
  3975. * - A String implies a url used to determine the tileSource implementation
  3976. * based on the file extension of url. JSONP is implied by *.js,
  3977. * otherwise the url is retrieved as text and the resulting text is
  3978. * introspected to determine if its json, xml, or text and parsed.
  3979. * - An Object implies an inline configuration which has a single
  3980. * property sufficient for being able to determine tileSource
  3981. * implementation. If the object has a property which is a function
  3982. * named 'getTileUrl', it is treated as a custom TileSource.
  3983. * @function
  3984. * @name OpenSeadragon.Viewer.prototype.open
  3985. * @param {String|Object|Function}
  3986. * @return {OpenSeadragon.Viewer} Chainable.
  3987. */
  3988. open: function ( tileSource ) {
  3989. var _this = this,
  3990. customTileSource,
  3991. readySource,
  3992. $TileSource,
  3993. options;
  3994. _this._hideMessage();
  3995. //allow plain xml strings or json strings to be parsed here
  3996. if( $.type( tileSource ) == 'string' ){
  3997. if( tileSource.match(/\s*<.*/) ){
  3998. tileSource = $.parseXml( tileSource );
  3999. }else if( tileSource.match(/\s*[\{\[].*/) ){
  4000. /*jshint evil:true*/
  4001. tileSource = eval( '('+tileSource+')' );
  4002. }
  4003. }
  4004. setTimeout(function(){
  4005. if ( $.type( tileSource ) == 'string') {
  4006. //If its still a string it means it must be a url at this point
  4007. tileSource = new $.TileSource( tileSource, function( event ){
  4008. openTileSource( _this, event.tileSource );
  4009. });
  4010. tileSource.addHandler( 'open-failed', function ( event ) {
  4011. _this.raiseEvent( 'open-failed', event );
  4012. });
  4013. } else if ( $.isPlainObject( tileSource ) || tileSource.nodeType ){
  4014. if( $.isFunction( tileSource.getTileUrl ) ){
  4015. //Custom tile source
  4016. customTileSource = new $.TileSource(tileSource);
  4017. customTileSource.getTileUrl = tileSource.getTileUrl;
  4018. openTileSource( _this, customTileSource );
  4019. } else {
  4020. //inline configuration
  4021. $TileSource = $.TileSource.determineType( _this, tileSource );
  4022. if ( !$TileSource ) {
  4023. _this.raiseEvent( 'open-failed', {
  4024. message: "Unable to load TileSource",
  4025. source: tileSource
  4026. });
  4027. return;
  4028. }
  4029. options = $TileSource.prototype.configure.apply( _this, [ tileSource ]);
  4030. readySource = new $TileSource( options );
  4031. openTileSource( _this, readySource );
  4032. }
  4033. } else {
  4034. //can assume it's already a tile source implementation
  4035. openTileSource( _this, tileSource );
  4036. }
  4037. }, 1);
  4038. return this;
  4039. },
  4040. /**
  4041. * @function
  4042. * @name OpenSeadragon.Viewer.prototype.close
  4043. * @return {OpenSeadragon.Viewer} Chainable.
  4044. */
  4045. close: function ( ) {
  4046. if ( this._updateRequestId !== null ) {
  4047. $.cancelAnimationFrame( this._updateRequestId );
  4048. this._updateRequestId = null;
  4049. }
  4050. if ( this.navigator ) {
  4051. this.navigator.close();
  4052. }
  4053. if ( this.drawer ) {
  4054. this.drawer.clearOverlays();
  4055. }
  4056. this.source = null;
  4057. this.drawer = null;
  4058. this.viewport = this.preserveViewport ? this.viewport : null;
  4059. //this.profiler = null;
  4060. if (this.canvas){
  4061. this.canvas.innerHTML = "";
  4062. }
  4063. VIEWERS[ this.hash ] = null;
  4064. delete VIEWERS[ this.hash ];
  4065. this.raiseEvent( 'close' );
  4066. return this;
  4067. },
  4068. /**
  4069. * Function to destroy the viewer and clean up everything created by
  4070. * OpenSeadragon.
  4071. * @function
  4072. * @name OpenSeadragon.Viewer.prototype.destroy
  4073. */
  4074. destroy: function( ) {
  4075. this.close();
  4076. this.removeAllHandlers();
  4077. // Go through top element (passed to us) and remove all children
  4078. // Use removeChild to make sure it handles SVG or any non-html
  4079. // also it performs better - http://jsperf.com/innerhtml-vs-removechild/15
  4080. if (this.element){
  4081. while (this.element.firstChild) {
  4082. this.element.removeChild(this.element.firstChild);
  4083. }
  4084. }
  4085. // destroy the mouse trackers
  4086. if (this.keyboardCommandArea){
  4087. this.keyboardCommandArea.innerTracker.destroy();
  4088. }
  4089. if (this.innerTracker){
  4090. this.innerTracker.destroy();
  4091. }
  4092. if (this.outerTracker){
  4093. this.outerTracker.destroy();
  4094. }
  4095. // clear all our references to dom objects
  4096. this.canvas = null;
  4097. this.keyboardCommandArea = null;
  4098. this.container = null;
  4099. // clear our reference to the main element - they will need to pass it in again, creating a new viewer
  4100. this.element = null;
  4101. },
  4102. /**
  4103. * @function
  4104. * @name OpenSeadragon.Viewer.prototype.isMouseNavEnabled
  4105. * @return {Boolean}
  4106. */
  4107. isMouseNavEnabled: function () {
  4108. return this.innerTracker.isTracking();
  4109. },
  4110. /**
  4111. * @function
  4112. * @name OpenSeadragon.Viewer.prototype.setMouseNavEnabled
  4113. * @return {OpenSeadragon.Viewer} Chainable.
  4114. */
  4115. setMouseNavEnabled: function( enabled ){
  4116. this.innerTracker.setTracking( enabled );
  4117. this.raiseEvent( 'mouse-enabled', { enabled: enabled } );
  4118. return this;
  4119. },
  4120. /**
  4121. * @function
  4122. * @name OpenSeadragon.Viewer.prototype.areControlsEnabled
  4123. * @return {Boolean}
  4124. */
  4125. areControlsEnabled: function () {
  4126. var enabled = this.controls.length,
  4127. i;
  4128. for( i = 0; i < this.controls.length; i++ ){
  4129. enabled = enabled && this.controls[ i ].isVisibile();
  4130. }
  4131. return enabled;
  4132. },
  4133. /**
  4134. * Shows or hides the controls (e.g. the default navigation buttons).
  4135. *
  4136. * @function
  4137. * @name OpenSeadragon.Viewer.prototype.setControlsEnabled
  4138. * @param {Boolean} true to show, false to hide.
  4139. * @return {OpenSeadragon.Viewer} Chainable.
  4140. */
  4141. setControlsEnabled: function( enabled ) {
  4142. if( enabled ){
  4143. abortControlsAutoHide( this );
  4144. } else {
  4145. beginControlsAutoHide( this );
  4146. }
  4147. this.raiseEvent( 'controls-enabled', { enabled: enabled } );
  4148. return this;
  4149. },
  4150. /**
  4151. * @function
  4152. * @name OpenSeadragon.Viewer.prototype.isFullPage
  4153. * @return {Boolean}
  4154. */
  4155. isFullPage: function () {
  4156. return THIS[ this.hash ].fullPage;
  4157. },
  4158. /**
  4159. * Toggle full page mode.
  4160. * @function
  4161. * @name OpenSeadragon.Viewer.prototype.setFullPage
  4162. * @param {Boolean} fullPage
  4163. * If true, enter full page mode. If false, exit full page mode.
  4164. * @return {OpenSeadragon.Viewer} Chainable.
  4165. */
  4166. setFullPage: function( fullPage ) {
  4167. var body = document.body,
  4168. bodyStyle = body.style,
  4169. docStyle = document.documentElement.style,
  4170. canvasStyle = this.canvas.style,
  4171. _this = this,
  4172. oldBounds,
  4173. newBounds,
  4174. viewer,
  4175. hash,
  4176. nodes,
  4177. i;
  4178. //dont bother modifying the DOM if we are already in full page mode.
  4179. if ( fullPage == this.isFullPage() ) {
  4180. return;
  4181. }
  4182. if ( fullPage ) {
  4183. this.bodyOverflow = bodyStyle.overflow;
  4184. this.docOverflow = docStyle.overflow;
  4185. bodyStyle.overflow = "hidden";
  4186. docStyle.overflow = "hidden";
  4187. this.bodyWidth = bodyStyle.width;
  4188. this.bodyHeight = bodyStyle.height;
  4189. bodyStyle.width = "100%";
  4190. bodyStyle.height = "100%";
  4191. //when entering full screen on the ipad it wasnt sufficient to leave
  4192. //the body intact as only only the top half of the screen would
  4193. //respond to touch events on the canvas, while the bottom half treated
  4194. //them as touch events on the document body. Thus we remove and store
  4195. //the bodies elements and replace them when we leave full screen.
  4196. this.previousBody = [];
  4197. THIS[ this.hash ].prevElementParent = this.element.parentNode;
  4198. THIS[ this.hash ].prevNextSibling = this.element.nextSibling;
  4199. THIS[ this.hash ].prevElementWidth = this.element.style.width;
  4200. THIS[ this.hash ].prevElementHeight = this.element.style.height;
  4201. nodes = body.childNodes.length;
  4202. for ( i = 0; i < nodes; i ++ ){
  4203. this.previousBody.push( body.childNodes[ 0 ] );
  4204. body.removeChild( body.childNodes[ 0 ] );
  4205. }
  4206. //If we've got a toolbar, we need to enable the user to use css to
  4207. //preserve it in fullpage mode
  4208. if( this.toolbar && this.toolbar.element ){
  4209. //save a reference to the parent so we can put it back
  4210. //in the long run we need a better strategy
  4211. this.toolbar.parentNode = this.toolbar.element.parentNode;
  4212. this.toolbar.nextSibling = this.toolbar.element.nextSibling;
  4213. body.appendChild( this.toolbar.element );
  4214. //Make sure the user has some ability to style the toolbar based
  4215. //on the mode
  4216. $.addClass( this.toolbar.element, 'fullpage' );
  4217. }
  4218. $.addClass( this.element, 'fullpage' );
  4219. body.appendChild( this.element );
  4220. if( $.supportsFullScreen ){
  4221. THIS[ this.hash ].onfullscreenchange = function() {
  4222. /*
  4223. fullscreenchange events don't include the new fullscreen status so we need to
  4224. retrieve the current status from the fullscreen API. See:
  4225. https://developer.mozilla.org/en-US/docs/Web/Reference/Events/fullscreenchange
  4226. */
  4227. if( $.isFullScreen() ){
  4228. _this.setFullPage( true );
  4229. } else {
  4230. _this.setFullPage( false );
  4231. }
  4232. };
  4233. $.requestFullScreen( document.body );
  4234. // The target of the event is always the document,
  4235. // but it is possible to retrieve the fullscreen element through the API
  4236. // Note that the API is still vendor-prefixed in browsers implementing it
  4237. document.addEventListener(
  4238. $.fullScreenEventName,
  4239. THIS[ this.hash ].onfullscreenchange
  4240. );
  4241. this.element.style.height = '100%';
  4242. this.element.style.width = '100%';
  4243. }else{
  4244. this.element.style.height = $.getWindowSize().y + 'px';
  4245. this.element.style.width = $.getWindowSize().x + 'px';
  4246. }
  4247. if( this.toolbar && this.toolbar.element ){
  4248. this.element.style.height = (
  4249. $.getElementSize( this.element ).y - $.getElementSize( this.toolbar.element ).y
  4250. ) + 'px';
  4251. }
  4252. THIS[ this.hash ].fullPage = true;
  4253. // mouse will be inside container now
  4254. $.delegate( this, onContainerEnter )( {} );
  4255. } else {
  4256. if( $.supportsFullScreen ){
  4257. document.removeEventListener(
  4258. $.fullScreenEventName,
  4259. THIS[ this.hash ].onfullscreenchange
  4260. );
  4261. $.cancelFullScreen( document );
  4262. }
  4263. bodyStyle.overflow = this.bodyOverflow;
  4264. docStyle.overflow = this.docOverflow;
  4265. bodyStyle.width = this.bodyWidth;
  4266. bodyStyle.height = this.bodyHeight;
  4267. canvasStyle.backgroundColor = "";
  4268. canvasStyle.color = "";
  4269. body.removeChild( this.element );
  4270. nodes = this.previousBody.length;
  4271. for ( i = 0; i < nodes; i++ ){
  4272. body.appendChild( this.previousBody.shift() );
  4273. }
  4274. $.removeClass( this.element, 'fullpage' );
  4275. THIS[ this.hash ].prevElementParent.insertBefore(
  4276. this.element,
  4277. THIS[ this.hash ].prevNextSibling
  4278. );
  4279. //If we've got a toolbar, we need to enable the user to use css to
  4280. //reset it to its original state
  4281. if( this.toolbar && this.toolbar.element ){
  4282. body.removeChild( this.toolbar.element );
  4283. //Make sure the user has some ability to style the toolbar based
  4284. //on the mode
  4285. $.removeClass( this.toolbar.element, 'fullpage' );
  4286. //this.toolbar.element.style.position = 'relative';
  4287. this.toolbar.parentNode.insertBefore(
  4288. this.toolbar.element,
  4289. this.toolbar.nextSibling
  4290. );
  4291. delete this.toolbar.parentNode;
  4292. delete this.toolbar.nextSibling;
  4293. //this.container.style.top = 'auto';
  4294. }
  4295. this.element.style.width = THIS[ this.hash ].prevElementWidth;
  4296. this.element.style.height = THIS[ this.hash ].prevElementHeight;
  4297. THIS[ this.hash ].fullPage = false;
  4298. // mouse will likely be outside now
  4299. $.delegate( this, onContainerExit )( {} );
  4300. }
  4301. this.raiseEvent( 'fullpage', { fullpage: fullPage } );
  4302. if ( this.viewport ) {
  4303. oldBounds = this.viewport.getBounds();
  4304. this.viewport.resize( THIS[ this.hash ].prevContainerSize );
  4305. newBounds = this.viewport.getBounds();
  4306. if ( fullPage ) {
  4307. THIS[ this.hash ].fsBoundsDelta = new $.Point(
  4308. newBounds.width / oldBounds.width,
  4309. newBounds.height / oldBounds.height
  4310. );
  4311. } else {
  4312. this.viewport.update();
  4313. this.viewport.zoomBy(
  4314. Math.max(
  4315. THIS[ this.hash ].fsBoundsDelta.x,
  4316. THIS[ this.hash ].fsBoundsDelta.y
  4317. ),
  4318. null,
  4319. true
  4320. );
  4321. //Ensures that if multiple viewers are on a page, the viewers that
  4322. //were hidden during fullpage are 'reopened'
  4323. for( hash in VIEWERS ){
  4324. viewer = VIEWERS[ hash ];
  4325. if( viewer !== this && viewer != this.navigator ){
  4326. viewer.open( viewer.source );
  4327. if( viewer.navigator ){
  4328. viewer.navigator.open( viewer.source );
  4329. }
  4330. }
  4331. }
  4332. }
  4333. THIS[ this.hash ].forceRedraw = true;
  4334. updateOnce( this );
  4335. }
  4336. return this;
  4337. },
  4338. /**
  4339. * @function
  4340. * @name OpenSeadragon.Viewer.prototype.isVisible
  4341. * @return {Boolean}
  4342. */
  4343. isVisible: function () {
  4344. return this.container.style.visibility != "hidden";
  4345. },
  4346. /**
  4347. * @function
  4348. * @name OpenSeadragon.Viewer.prototype.setVisible
  4349. * @return {OpenSeadragon.Viewer} Chainable.
  4350. */
  4351. setVisible: function( visible ){
  4352. this.container.style.visibility = visible ? "" : "hidden";
  4353. this.raiseEvent( 'visible', { visible: visible } );
  4354. return this;
  4355. },
  4356. /**
  4357. * @function
  4358. * @name OpenSeadragon.Viewer.prototype.bindSequenceControls
  4359. * @return {OpenSeadragon.Viewer} Chainable.
  4360. */
  4361. bindSequenceControls: function(){
  4362. //////////////////////////////////////////////////////////////////////////
  4363. // Image Sequence Controls
  4364. //////////////////////////////////////////////////////////////////////////
  4365. var onFocusHandler = $.delegate( this, onFocus ),
  4366. onBlurHandler = $.delegate( this, onBlur ),
  4367. onNextHandler = $.delegate( this, onNext ),
  4368. onPreviousHandler = $.delegate( this, onPrevious ),
  4369. navImages = this.navImages,
  4370. useGroup = true ;
  4371. if( this.showSequenceControl && THIS[ this.hash ].sequenced ){
  4372. if( this.previousButton || this.nextButton ){
  4373. //if we are binding to custom buttons then layout and
  4374. //grouping is the responsibility of the page author
  4375. useGroup = false;
  4376. }
  4377. this.previousButton = new $.Button({
  4378. element: this.previousButton ? $.getElement( this.previousButton ) : null,
  4379. clickTimeThreshold: this.clickTimeThreshold,
  4380. clickDistThreshold: this.clickDistThreshold,
  4381. tooltip: $.getString( "Tooltips.PreviousPage" ),
  4382. srcRest: resolveUrl( this.prefixUrl, navImages.previous.REST ),
  4383. srcGroup: resolveUrl( this.prefixUrl, navImages.previous.GROUP ),
  4384. srcHover: resolveUrl( this.prefixUrl, navImages.previous.HOVER ),
  4385. srcDown: resolveUrl( this.prefixUrl, navImages.previous.DOWN ),
  4386. onRelease: onPreviousHandler,
  4387. onFocus: onFocusHandler,
  4388. onBlur: onBlurHandler
  4389. });
  4390. this.nextButton = new $.Button({
  4391. element: this.nextButton ? $.getElement( this.nextButton ) : null,
  4392. clickTimeThreshold: this.clickTimeThreshold,
  4393. clickDistThreshold: this.clickDistThreshold,
  4394. tooltip: $.getString( "Tooltips.NextPage" ),
  4395. srcRest: resolveUrl( this.prefixUrl, navImages.next.REST ),
  4396. srcGroup: resolveUrl( this.prefixUrl, navImages.next.GROUP ),
  4397. srcHover: resolveUrl( this.prefixUrl, navImages.next.HOVER ),
  4398. srcDown: resolveUrl( this.prefixUrl, navImages.next.DOWN ),
  4399. onRelease: onNextHandler,
  4400. onFocus: onFocusHandler,
  4401. onBlur: onBlurHandler
  4402. });
  4403. if( !this.navPrevNextWrap ){
  4404. this.previousButton.disable();
  4405. }
  4406. if( useGroup ){
  4407. this.paging = new $.ButtonGroup({
  4408. buttons: [
  4409. this.previousButton,
  4410. this.nextButton
  4411. ],
  4412. clickTimeThreshold: this.clickTimeThreshold,
  4413. clickDistThreshold: this.clickDistThreshold
  4414. });
  4415. this.pagingControl = this.paging.element;
  4416. if( this.toolbar ){
  4417. this.toolbar.addControl(
  4418. this.pagingControl,
  4419. {anchor: $.ControlAnchor.BOTTOM_RIGHT}
  4420. );
  4421. }else{
  4422. this.addControl(
  4423. this.pagingControl,
  4424. {anchor: $.ControlAnchor.TOP_LEFT}
  4425. );
  4426. }
  4427. }
  4428. }
  4429. return this;
  4430. },
  4431. /**
  4432. * @function
  4433. * @name OpenSeadragon.Viewer.prototype.bindStandardControls
  4434. * @return {OpenSeadragon.Viewer} Chainable.
  4435. */
  4436. bindStandardControls: function(){
  4437. //////////////////////////////////////////////////////////////////////////
  4438. // Navigation Controls
  4439. //////////////////////////////////////////////////////////////////////////
  4440. var beginZoomingInHandler = $.delegate( this, beginZoomingIn ),
  4441. endZoomingHandler = $.delegate( this, endZooming ),
  4442. doSingleZoomInHandler = $.delegate( this, doSingleZoomIn ),
  4443. beginZoomingOutHandler = $.delegate( this, beginZoomingOut ),
  4444. doSingleZoomOutHandler = $.delegate( this, doSingleZoomOut ),
  4445. onHomeHandler = $.delegate( this, onHome ),
  4446. onFullPageHandler = $.delegate( this, onFullPage ),
  4447. onFocusHandler = $.delegate( this, onFocus ),
  4448. onBlurHandler = $.delegate( this, onBlur ),
  4449. navImages = this.navImages,
  4450. buttons = [],
  4451. useGroup = true ;
  4452. if( this.showNavigationControl ){
  4453. if( this.zoomInButton || this.zoomOutButton || this.homeButton || this.fullPageButton ){
  4454. //if we are binding to custom buttons then layout and
  4455. //grouping is the responsibility of the page author
  4456. useGroup = false;
  4457. }
  4458. buttons.push( this.zoomInButton = new $.Button({
  4459. element: this.zoomInButton ? $.getElement( this.zoomInButton ) : null,
  4460. clickTimeThreshold: this.clickTimeThreshold,
  4461. clickDistThreshold: this.clickDistThreshold,
  4462. tooltip: $.getString( "Tooltips.ZoomIn" ),
  4463. srcRest: resolveUrl( this.prefixUrl, navImages.zoomIn.REST ),
  4464. srcGroup: resolveUrl( this.prefixUrl, navImages.zoomIn.GROUP ),
  4465. srcHover: resolveUrl( this.prefixUrl, navImages.zoomIn.HOVER ),
  4466. srcDown: resolveUrl( this.prefixUrl, navImages.zoomIn.DOWN ),
  4467. onPress: beginZoomingInHandler,
  4468. onRelease: endZoomingHandler,
  4469. onClick: doSingleZoomInHandler,
  4470. onEnter: beginZoomingInHandler,
  4471. onExit: endZoomingHandler,
  4472. onFocus: onFocusHandler,
  4473. onBlur: onBlurHandler
  4474. }));
  4475. buttons.push( this.zoomOutButton = new $.Button({
  4476. element: this.zoomOutButton ? $.getElement( this.zoomOutButton ) : null,
  4477. clickTimeThreshold: this.clickTimeThreshold,
  4478. clickDistThreshold: this.clickDistThreshold,
  4479. tooltip: $.getString( "Tooltips.ZoomOut" ),
  4480. srcRest: resolveUrl( this.prefixUrl, navImages.zoomOut.REST ),
  4481. srcGroup: resolveUrl( this.prefixUrl, navImages.zoomOut.GROUP ),
  4482. srcHover: resolveUrl( this.prefixUrl, navImages.zoomOut.HOVER ),
  4483. srcDown: resolveUrl( this.prefixUrl, navImages.zoomOut.DOWN ),
  4484. onPress: beginZoomingOutHandler,
  4485. onRelease: endZoomingHandler,
  4486. onClick: doSingleZoomOutHandler,
  4487. onEnter: beginZoomingOutHandler,
  4488. onExit: endZoomingHandler,
  4489. onFocus: onFocusHandler,
  4490. onBlur: onBlurHandler
  4491. }));
  4492. buttons.push( this.homeButton = new $.Button({
  4493. element: this.homeButton ? $.getElement( this.homeButton ) : null,
  4494. clickTimeThreshold: this.clickTimeThreshold,
  4495. clickDistThreshold: this.clickDistThreshold,
  4496. tooltip: $.getString( "Tooltips.Home" ),
  4497. srcRest: resolveUrl( this.prefixUrl, navImages.home.REST ),
  4498. srcGroup: resolveUrl( this.prefixUrl, navImages.home.GROUP ),
  4499. srcHover: resolveUrl( this.prefixUrl, navImages.home.HOVER ),
  4500. srcDown: resolveUrl( this.prefixUrl, navImages.home.DOWN ),
  4501. onRelease: onHomeHandler,
  4502. onFocus: onFocusHandler,
  4503. onBlur: onBlurHandler
  4504. }));
  4505. buttons.push( this.fullPageButton = new $.Button({
  4506. element: this.fullPageButton ? $.getElement( this.fullPageButton ) : null,
  4507. clickTimeThreshold: this.clickTimeThreshold,
  4508. clickDistThreshold: this.clickDistThreshold,
  4509. tooltip: $.getString( "Tooltips.FullPage" ),
  4510. srcRest: resolveUrl( this.prefixUrl, navImages.fullpage.REST ),
  4511. srcGroup: resolveUrl( this.prefixUrl, navImages.fullpage.GROUP ),
  4512. srcHover: resolveUrl( this.prefixUrl, navImages.fullpage.HOVER ),
  4513. srcDown: resolveUrl( this.prefixUrl, navImages.fullpage.DOWN ),
  4514. onRelease: onFullPageHandler,
  4515. onFocus: onFocusHandler,
  4516. onBlur: onBlurHandler
  4517. }));
  4518. if( useGroup ){
  4519. this.buttons = new $.ButtonGroup({
  4520. buttons: buttons,
  4521. clickTimeThreshold: this.clickTimeThreshold,
  4522. clickDistThreshold: this.clickDistThreshold
  4523. });
  4524. this.navControl = this.buttons.element;
  4525. this.addHandler( 'open', $.delegate( this, lightUp ) );
  4526. if( this.toolbar ){
  4527. this.toolbar.addControl(
  4528. this.navControl,
  4529. {anchor: $.ControlAnchor.TOP_LEFT}
  4530. );
  4531. }else{
  4532. this.addControl(
  4533. this.navControl,
  4534. {anchor: $.ControlAnchor.TOP_LEFT}
  4535. );
  4536. }
  4537. }
  4538. }
  4539. return this;
  4540. },
  4541. /**
  4542. * Gets the active page of a sequence
  4543. * @function
  4544. * @name OpenSeadragon.Viewer.prototype.currentPage
  4545. * @return {Number}
  4546. */
  4547. currentPage: function () {
  4548. return THIS[ this.hash ].sequence;
  4549. },
  4550. /**
  4551. * @function
  4552. * @name OpenSeadragon.Viewer.prototype.goToPage
  4553. * @return {OpenSeadragon.Viewer} Chainable.
  4554. */
  4555. goToPage: function( page ){
  4556. //page is a 1 based index so normalize now
  4557. //page = page;
  4558. this.raiseEvent( 'page', { page: page } );
  4559. if( this.tileSources.length > page ){
  4560. THIS[ this.hash ].sequence = page;
  4561. this._updateSequenceButtons( page );
  4562. this.open( this.tileSources[ page ] );
  4563. }
  4564. if( $.isFunction( this.onPageChange ) ){
  4565. this.onPageChange({
  4566. page: page,
  4567. viewer: this
  4568. });
  4569. }
  4570. if( this.referenceStrip ){
  4571. this.referenceStrip.setFocus( page );
  4572. }
  4573. return this;
  4574. },
  4575. /**
  4576. * Updates the sequence buttons.
  4577. * @function
  4578. * @private
  4579. * @param {Number} Sequence Value
  4580. */
  4581. _updateSequenceButtons: function (page) {
  4582. if( this.nextButton ){
  4583. if( ( this.tileSources.length - 1 ) === page ){
  4584. //Disable next button
  4585. if(!this.navPrevNextWrap){
  4586. this.nextButton.disable();
  4587. }
  4588. } else {
  4589. this.nextButton.enable();
  4590. }
  4591. }
  4592. if( this.previousButton ){
  4593. if( page > 0 ){
  4594. //Enable previous button
  4595. this.previousButton.enable();
  4596. } else {
  4597. if(!this.navPrevNextWrap){
  4598. this.previousButton.disable();
  4599. }
  4600. }
  4601. }
  4602. },
  4603. /**
  4604. * Display a message in the viewport
  4605. * @function
  4606. * @private
  4607. * @param {String} text message
  4608. */
  4609. _showMessage: function ( message ) {
  4610. this._hideMessage();
  4611. var div = $.makeNeutralElement( "div" );
  4612. div.appendChild( document.createTextNode( message ) );
  4613. this.messageDiv = $.makeCenteredNode( div );
  4614. $.addClass(this.messageDiv, "openseadragon-message");
  4615. this.container.appendChild( this.messageDiv );
  4616. },
  4617. /**
  4618. * Hide any currently displayed viewport message
  4619. * @function
  4620. * @private
  4621. */
  4622. _hideMessage: function () {
  4623. var div = this.messageDiv;
  4624. if (div) {
  4625. div.parentNode.removeChild(div);
  4626. delete this.messageDiv;
  4627. }
  4628. }
  4629. });
  4630. /**
  4631. * _getSafeElemSize is like getElementSize(), but refuses to return 0 for x or y,
  4632. * which was causing some calling operations in updateOnce and openTileSource to
  4633. * return NaN.
  4634. * @returns {Point}
  4635. * @private
  4636. */
  4637. function _getSafeElemSize (oElement) {
  4638. oElement = $.getElement( oElement );
  4639. return new $.Point(
  4640. (oElement.clientWidth === 0 ? 1 : oElement.clientWidth),
  4641. (oElement.clientHeight === 0 ? 1 : oElement.clientHeight)
  4642. );
  4643. }
  4644. /**
  4645. * @function
  4646. * @private
  4647. */
  4648. function openTileSource( viewer, source ) {
  4649. var _this = viewer,
  4650. overlay,
  4651. i;
  4652. if ( _this.source ) {
  4653. _this.close( );
  4654. }
  4655. _this.canvas.innerHTML = "";
  4656. THIS[ _this.hash ].prevContainerSize = _getSafeElemSize( _this.container );
  4657. if( _this.collectionMode ){
  4658. _this.source = new $.TileSourceCollection({
  4659. rows: _this.collectionRows,
  4660. layout: _this.collectionLayout,
  4661. tileSize: _this.collectionTileSize,
  4662. tileSources: _this.tileSources,
  4663. tileMargin: _this.collectionTileMargin
  4664. });
  4665. _this.viewport = _this.viewport ? _this.viewport : new $.Viewport({
  4666. collectionMode: true,
  4667. collectionTileSource: _this.source,
  4668. containerSize: THIS[ _this.hash ].prevContainerSize,
  4669. contentSize: _this.source.dimensions,
  4670. springStiffness: _this.springStiffness,
  4671. animationTime: _this.animationTime,
  4672. showNavigator: false,
  4673. minZoomImageRatio: 1,
  4674. maxZoomPixelRatio: 1,
  4675. viewer: _this //,
  4676. //TODO: figure out how to support these in a way that makes sense
  4677. //minZoomLevel: this.minZoomLevel,
  4678. //maxZoomLevel: this.maxZoomLevel
  4679. });
  4680. }else{
  4681. if( source ){
  4682. _this.source = source;
  4683. }
  4684. _this.viewport = _this.viewport ? _this.viewport : new $.Viewport({
  4685. containerSize: THIS[ _this.hash ].prevContainerSize,
  4686. contentSize: _this.source.dimensions,
  4687. springStiffness: _this.springStiffness,
  4688. animationTime: _this.animationTime,
  4689. minZoomImageRatio: _this.minZoomImageRatio,
  4690. maxZoomPixelRatio: _this.maxZoomPixelRatio,
  4691. visibilityRatio: _this.visibilityRatio,
  4692. wrapHorizontal: _this.wrapHorizontal,
  4693. wrapVertical: _this.wrapVertical,
  4694. defaultZoomLevel: _this.defaultZoomLevel,
  4695. minZoomLevel: _this.minZoomLevel,
  4696. maxZoomLevel: _this.maxZoomLevel,
  4697. viewer: _this
  4698. });
  4699. }
  4700. if( _this.preserveViewport ){
  4701. _this.viewport.resetContentSize( _this.source.dimensions );
  4702. }
  4703. _this.source.overlays = _this.source.overlays || [];
  4704. _this.drawer = new $.Drawer({
  4705. viewer: _this,
  4706. source: _this.source,
  4707. viewport: _this.viewport,
  4708. element: _this.canvas,
  4709. overlays: [].concat( _this.overlays ).concat( _this.source.overlays ),
  4710. maxImageCacheCount: _this.maxImageCacheCount,
  4711. imageLoaderLimit: _this.imageLoaderLimit,
  4712. minZoomImageRatio: _this.minZoomImageRatio,
  4713. wrapHorizontal: _this.wrapHorizontal,
  4714. wrapVertical: _this.wrapVertical,
  4715. immediateRender: _this.immediateRender,
  4716. blendTime: _this.blendTime,
  4717. alwaysBlend: _this.alwaysBlend,
  4718. minPixelRatio: _this.collectionMode ? 0 : _this.minPixelRatio,
  4719. timeout: _this.timeout,
  4720. debugMode: _this.debugMode,
  4721. debugGridColor: _this.debugGridColor
  4722. });
  4723. //Instantiate a navigator if configured
  4724. if ( _this.showNavigator && !_this.collectionMode ){
  4725. // Note: By passing the fully parsed source, the navigator doesn't
  4726. // have to load it again.
  4727. if ( _this.navigator ) {
  4728. _this.navigator.open( source );
  4729. } else {
  4730. _this.navigator = new $.Navigator({
  4731. id: _this.navigatorId,
  4732. position: _this.navigatorPosition,
  4733. sizeRatio: _this.navigatorSizeRatio,
  4734. height: _this.navigatorHeight,
  4735. width: _this.navigatorWidth,
  4736. tileSources: source,
  4737. tileHost: _this.tileHost,
  4738. prefixUrl: _this.prefixUrl,
  4739. overlays: _this.overlays,
  4740. viewer: _this
  4741. });
  4742. }
  4743. }
  4744. //Instantiate a referencestrip if configured
  4745. if ( _this.showReferenceStrip && !_this.referenceStrip ){
  4746. _this.referenceStrip = new $.ReferenceStrip({
  4747. id: _this.referenceStripElement,
  4748. position: _this.referenceStripPosition,
  4749. sizeRatio: _this.referenceStripSizeRatio,
  4750. scroll: _this.referenceStripScroll,
  4751. height: _this.referenceStripHeight,
  4752. width: _this.referenceStripWidth,
  4753. tileSources: _this.tileSources,
  4754. tileHost: _this.tileHost,
  4755. prefixUrl: _this.prefixUrl,
  4756. overlays: _this.overlays,
  4757. viewer: _this
  4758. });
  4759. }
  4760. //this.profiler = new $.Profiler();
  4761. THIS[ _this.hash ].animating = false;
  4762. THIS[ _this.hash ].forceRedraw = true;
  4763. _this._updateRequestId = scheduleUpdate( _this, updateMulti );
  4764. //Assuming you had programatically created a bunch of overlays
  4765. //and added them via configuration
  4766. for ( i = 0; i < _this.overlayControls.length; i++ ) {
  4767. overlay = _this.overlayControls[ i ];
  4768. if ( overlay.point ) {
  4769. _this.drawer.addOverlay(
  4770. overlay.id,
  4771. new $.Point(
  4772. overlay.point.X,
  4773. overlay.point.Y
  4774. ),
  4775. $.OverlayPlacement.TOP_LEFT
  4776. );
  4777. } else {
  4778. _this.drawer.addOverlay(
  4779. overlay.id,
  4780. new $.Rect(
  4781. overlay.rect.Point.X,
  4782. overlay.rect.Point.Y,
  4783. overlay.rect.Width,
  4784. overlay.rect.Height
  4785. ),
  4786. overlay.placement
  4787. );
  4788. }
  4789. }
  4790. VIEWERS[ _this.hash ] = _this;
  4791. _this.raiseEvent( 'open', { source: source } );
  4792. return _this;
  4793. }
  4794. ///////////////////////////////////////////////////////////////////////////////
  4795. // Schedulers provide the general engine for animation
  4796. ///////////////////////////////////////////////////////////////////////////////
  4797. function scheduleUpdate( viewer, updateFunc ){
  4798. return $.requestAnimationFrame( function(){
  4799. updateFunc( viewer );
  4800. } );
  4801. }
  4802. //provides a sequence in the fade animation
  4803. function scheduleControlsFade( viewer ) {
  4804. $.requestAnimationFrame( function(){
  4805. updateControlsFade( viewer );
  4806. });
  4807. }
  4808. //initiates an animation to hide the controls
  4809. function beginControlsAutoHide( viewer ) {
  4810. if ( !viewer.autoHideControls ) {
  4811. return;
  4812. }
  4813. viewer.controlsShouldFade = true;
  4814. viewer.controlsFadeBeginTime =
  4815. $.now() +
  4816. viewer.controlsFadeDelay;
  4817. window.setTimeout( function(){
  4818. scheduleControlsFade( viewer );
  4819. }, viewer.controlsFadeDelay );
  4820. }
  4821. //determines if fade animation is done or continues the animation
  4822. function updateControlsFade( viewer ) {
  4823. var currentTime,
  4824. deltaTime,
  4825. opacity,
  4826. i;
  4827. if ( viewer.controlsShouldFade ) {
  4828. currentTime = $.now();
  4829. deltaTime = currentTime - viewer.controlsFadeBeginTime;
  4830. opacity = 1.0 - deltaTime / viewer.controlsFadeLength;
  4831. opacity = Math.min( 1.0, opacity );
  4832. opacity = Math.max( 0.0, opacity );
  4833. for ( i = viewer.controls.length - 1; i >= 0; i--) {
  4834. if (viewer.controls[ i ].autoFade) {
  4835. viewer.controls[ i ].setOpacity( opacity );
  4836. }
  4837. }
  4838. if ( opacity > 0 ) {
  4839. // fade again
  4840. scheduleControlsFade( viewer );
  4841. }
  4842. }
  4843. }
  4844. //stop the fade animation on the controls and show them
  4845. function abortControlsAutoHide( viewer ) {
  4846. var i;
  4847. viewer.controlsShouldFade = false;
  4848. for ( i = viewer.controls.length - 1; i >= 0; i-- ) {
  4849. viewer.controls[ i ].setOpacity( 1.0 );
  4850. }
  4851. }
  4852. ///////////////////////////////////////////////////////////////////////////////
  4853. // Default view event handlers.
  4854. ///////////////////////////////////////////////////////////////////////////////
  4855. function onFocus(){
  4856. abortControlsAutoHide( this );
  4857. }
  4858. function onBlur(){
  4859. beginControlsAutoHide( this );
  4860. }
  4861. function onCanvasClick( event ) {
  4862. var zoomPerClick,
  4863. factor;
  4864. if ( this.viewport && event.quick ) { // ignore clicks where mouse moved
  4865. zoomPerClick = this.zoomPerClick;
  4866. factor = event.shift ? 1.0 / zoomPerClick : zoomPerClick;
  4867. this.viewport.zoomBy(
  4868. factor,
  4869. this.viewport.pointFromPixel( event.position, true )
  4870. );
  4871. this.viewport.applyConstraints();
  4872. }
  4873. this.raiseEvent( 'canvas-click', {
  4874. tracker: event.eventSource,
  4875. position: event.position,
  4876. quick: event.quick,
  4877. shift: event.shift,
  4878. originalEvent: event.originalEvent
  4879. });
  4880. }
  4881. function onCanvasDrag( event ) {
  4882. if ( this.viewport ) {
  4883. if( !this.panHorizontal ){
  4884. event.delta.x = 0;
  4885. }
  4886. if( !this.panVertical ){
  4887. event.delta.y = 0;
  4888. }
  4889. this.viewport.panBy(
  4890. this.viewport.deltaPointsFromPixels(
  4891. event.delta.negate()
  4892. )
  4893. );
  4894. if( this.constrainDuringPan ){
  4895. this.viewport.applyConstraints();
  4896. }
  4897. }
  4898. this.raiseEvent( 'canvas-drag', {
  4899. tracker: event.eventSource,
  4900. position: event.position,
  4901. delta: event.delta,
  4902. shift: event.shift,
  4903. originalEvent: event.originalEvent
  4904. });
  4905. }
  4906. function onCanvasRelease( event ) {
  4907. if ( event.insideElementPressed && this.viewport ) {
  4908. this.viewport.applyConstraints();
  4909. }
  4910. this.raiseEvent( 'canvas-release', {
  4911. tracker: event.eventSource,
  4912. position: event.position,
  4913. insideElementPressed: event.insideElementPressed,
  4914. insideElementReleased: event.insideElementReleased,
  4915. originalEvent: event.originalEvent
  4916. });
  4917. }
  4918. function onCanvasScroll( event ) {
  4919. var factor;
  4920. if ( this.viewport ) {
  4921. factor = Math.pow( this.zoomPerScroll, event.scroll );
  4922. this.viewport.zoomBy(
  4923. factor,
  4924. this.viewport.pointFromPixel( event.position, true )
  4925. );
  4926. this.viewport.applyConstraints();
  4927. }
  4928. this.raiseEvent( 'canvas-scroll', {
  4929. tracker: event.eventSource,
  4930. position: event.position,
  4931. scroll: event.scroll,
  4932. shift: event.shift,
  4933. originalEvent: event.originalEvent
  4934. });
  4935. //cancels event
  4936. return false;
  4937. }
  4938. function onContainerExit( event ) {
  4939. if ( !event.insideElementPressed ) {
  4940. THIS[ this.hash ].mouseInside = false;
  4941. if ( !THIS[ this.hash ].animating ) {
  4942. beginControlsAutoHide( this );
  4943. }
  4944. }
  4945. this.raiseEvent( 'container-exit', {
  4946. tracker: event.eventSource,
  4947. position: event.position,
  4948. insideElementPressed: event.insideElementPressed,
  4949. buttonDownAny: event.buttonDownAny,
  4950. originalEvent: event.originalEvent
  4951. });
  4952. }
  4953. function onContainerRelease( event ) {
  4954. if ( !event.insideElementReleased ) {
  4955. THIS[ this.hash ].mouseInside = false;
  4956. if ( !THIS[ this.hash ].animating ) {
  4957. beginControlsAutoHide( this );
  4958. }
  4959. }
  4960. this.raiseEvent( 'container-release', {
  4961. tracker: event.eventSource,
  4962. position: event.position,
  4963. insideElementPressed: event.insideElementPressed,
  4964. insideElementReleased: event.insideElementReleased,
  4965. originalEvent: event.originalEvent
  4966. });
  4967. }
  4968. function onContainerEnter( event ) {
  4969. THIS[ this.hash ].mouseInside = true;
  4970. abortControlsAutoHide( this );
  4971. this.raiseEvent( 'container-enter', {
  4972. tracker: event.eventSource,
  4973. position: event.position,
  4974. insideElementPressed: event.insideElementPressed,
  4975. buttonDownAny: event.buttonDownAny,
  4976. originalEvent: event.originalEvent
  4977. });
  4978. }
  4979. ///////////////////////////////////////////////////////////////////////////////
  4980. // Page update routines ( aka Views - for future reference )
  4981. ///////////////////////////////////////////////////////////////////////////////
  4982. function updateMulti( viewer ) {
  4983. if ( !viewer.source ) {
  4984. viewer._updateRequestId = null;
  4985. return;
  4986. }
  4987. updateOnce( viewer );
  4988. // Request the next frame, unless we've been closed during the updateOnce()
  4989. if ( viewer.source ) {
  4990. viewer._updateRequestId = scheduleUpdate( viewer, updateMulti );
  4991. }
  4992. }
  4993. function updateOnce( viewer ) {
  4994. var containerSize,
  4995. animated;
  4996. if ( !viewer.source ) {
  4997. return;
  4998. }
  4999. //viewer.profiler.beginUpdate();
  5000. containerSize = _getSafeElemSize( viewer.container );
  5001. if ( !containerSize.equals( THIS[ viewer.hash ].prevContainerSize ) ) {
  5002. // maintain image position
  5003. viewer.viewport.resize( containerSize, true );
  5004. THIS[ viewer.hash ].prevContainerSize = containerSize;
  5005. }
  5006. animated = viewer.viewport.update();
  5007. if( viewer.referenceStrip ){
  5008. animated = viewer.referenceStrip.update( viewer.viewport ) || animated;
  5009. }
  5010. if ( !THIS[ viewer.hash ].animating && animated ) {
  5011. viewer.raiseEvent( "animation-start" );
  5012. abortControlsAutoHide( viewer );
  5013. }
  5014. if ( animated ) {
  5015. viewer.drawer.update();
  5016. if( viewer.navigator ){
  5017. viewer.navigator.update( viewer.viewport );
  5018. }
  5019. viewer.raiseEvent( "animation" );
  5020. } else if ( THIS[ viewer.hash ].forceRedraw || viewer.drawer.needsUpdate() ) {
  5021. viewer.drawer.update();
  5022. if( viewer.navigator ){
  5023. viewer.navigator.update( viewer.viewport );
  5024. }
  5025. THIS[ viewer.hash ].forceRedraw = false;
  5026. }
  5027. if ( THIS[ viewer.hash ].animating && !animated ) {
  5028. viewer.raiseEvent( "animation-finish" );
  5029. if ( !THIS[ viewer.hash ].mouseInside ) {
  5030. beginControlsAutoHide( viewer );
  5031. }
  5032. }
  5033. THIS[ viewer.hash ].animating = animated;
  5034. //viewer.profiler.endUpdate();
  5035. }
  5036. ///////////////////////////////////////////////////////////////////////////////
  5037. // Navigation Controls
  5038. ///////////////////////////////////////////////////////////////////////////////
  5039. function resolveUrl( prefix, url ) {
  5040. return prefix ? prefix + url : url;
  5041. }
  5042. function beginZoomingIn() {
  5043. THIS[ this.hash ].lastZoomTime = $.now();
  5044. THIS[ this.hash ].zoomFactor = this.zoomPerSecond;
  5045. THIS[ this.hash ].zooming = true;
  5046. scheduleZoom( this );
  5047. }
  5048. function beginZoomingOut() {
  5049. THIS[ this.hash ].lastZoomTime = $.now();
  5050. THIS[ this.hash ].zoomFactor = 1.0 / this.zoomPerSecond;
  5051. THIS[ this.hash ].zooming = true;
  5052. scheduleZoom( this );
  5053. }
  5054. function endZooming() {
  5055. THIS[ this.hash ].zooming = false;
  5056. }
  5057. function scheduleZoom( viewer ) {
  5058. $.requestAnimationFrame( $.delegate( viewer, doZoom ) );
  5059. }
  5060. function doZoom() {
  5061. var currentTime,
  5062. deltaTime,
  5063. adjustedFactor;
  5064. if ( THIS[ this.hash ].zooming && this.viewport) {
  5065. currentTime = $.now();
  5066. deltaTime = currentTime - THIS[ this.hash ].lastZoomTime;
  5067. adjustedFactor = Math.pow( THIS[ this.hash ].zoomFactor, deltaTime / 1000 );
  5068. this.viewport.zoomBy( adjustedFactor );
  5069. this.viewport.applyConstraints();
  5070. THIS[ this.hash ].lastZoomTime = currentTime;
  5071. scheduleZoom( this );
  5072. }
  5073. }
  5074. function doSingleZoomIn() {
  5075. if ( this.viewport ) {
  5076. THIS[ this.hash ].zooming = false;
  5077. this.viewport.zoomBy(
  5078. this.zoomPerClick / 1.0
  5079. );
  5080. this.viewport.applyConstraints();
  5081. }
  5082. }
  5083. function doSingleZoomOut() {
  5084. if ( this.viewport ) {
  5085. THIS[ this.hash ].zooming = false;
  5086. this.viewport.zoomBy(
  5087. 1.0 / this.zoomPerClick
  5088. );
  5089. this.viewport.applyConstraints();
  5090. }
  5091. }
  5092. function lightUp() {
  5093. this.buttons.emulateEnter();
  5094. this.buttons.emulateExit();
  5095. }
  5096. function onHome() {
  5097. if ( this.viewport ) {
  5098. this.viewport.goHome();
  5099. }
  5100. }
  5101. function onFullPage() {
  5102. this.setFullPage( !this.isFullPage() );
  5103. // correct for no mouseout event on change
  5104. if( this.buttons ){
  5105. this.buttons.emulateExit();
  5106. }
  5107. this.fullPageButton.element.focus();
  5108. if ( this.viewport ) {
  5109. this.viewport.applyConstraints();
  5110. }
  5111. }
  5112. function onPrevious(){
  5113. var previous = THIS[ this.hash ].sequence - 1;
  5114. if(this.navPrevNextWrap && previous < 0){
  5115. previous += this.tileSources.length;
  5116. }
  5117. this.goToPage( previous );
  5118. }
  5119. function onNext(){
  5120. var next = THIS[ this.hash ].sequence + 1;
  5121. if(this.navPrevNextWrap && next >= this.tileSources.length){
  5122. next = 0;
  5123. }
  5124. this.goToPage( next );
  5125. }
  5126. }( OpenSeadragon ));
  5127. /*
  5128. * OpenSeadragon - Navigator
  5129. *
  5130. * Copyright (C) 2009 CodePlex Foundation
  5131. * Copyright (C) 2010-2013 OpenSeadragon contributors
  5132. *
  5133. * Redistribution and use in source and binary forms, with or without
  5134. * modification, are permitted provided that the following conditions are
  5135. * met:
  5136. *
  5137. * - Redistributions of source code must retain the above copyright notice,
  5138. * this list of conditions and the following disclaimer.
  5139. *
  5140. * - Redistributions in binary form must reproduce the above copyright
  5141. * notice, this list of conditions and the following disclaimer in the
  5142. * documentation and/or other materials provided with the distribution.
  5143. *
  5144. * - Neither the name of CodePlex Foundation nor the names of its
  5145. * contributors may be used to endorse or promote products derived from
  5146. * this software without specific prior written permission.
  5147. *
  5148. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  5149. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  5150. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  5151. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  5152. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  5153. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  5154. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  5155. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  5156. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  5157. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  5158. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  5159. */
  5160. (function( $ ){
  5161. /**
  5162. * The Navigator provides a small view of the current image as fixed
  5163. * while representing the viewport as a moving box serving as a frame
  5164. * of reference in the larger viewport as to which portion of the image
  5165. * is currently being examined. The navigator's viewport can be interacted
  5166. * with using the keyboard or the mouse.
  5167. * @class
  5168. * @name OpenSeadragon.Navigator
  5169. * @extends OpenSeadragon.Viewer
  5170. * @extends OpenSeadragon.EventSource
  5171. * @param {Object} options
  5172. * @param {String} options.viewerId
  5173. */
  5174. $.Navigator = function( options ){
  5175. var viewer = options.viewer,
  5176. viewerSize = $.getElementSize( viewer.element),
  5177. unneededElement;
  5178. //We may need to create a new element and id if they did not
  5179. //provide the id for the existing element
  5180. if( !options.id ){
  5181. options.id = 'navigator-' + $.now();
  5182. this.element = $.makeNeutralElement( "div" );
  5183. options.controlOptions = {
  5184. anchor: $.ControlAnchor.TOP_RIGHT,
  5185. attachToViewer: true,
  5186. autoFade: true
  5187. };
  5188. if( options.position ){
  5189. if( 'BOTTOM_RIGHT' == options.position ){
  5190. options.controlOptions.anchor = $.ControlAnchor.BOTTOM_RIGHT;
  5191. } else if( 'BOTTOM_LEFT' == options.position ){
  5192. options.controlOptions.anchor = $.ControlAnchor.BOTTOM_LEFT;
  5193. } else if( 'TOP_RIGHT' == options.position ){
  5194. options.controlOptions.anchor = $.ControlAnchor.TOP_RIGHT;
  5195. } else if( 'TOP_LEFT' == options.position ){
  5196. options.controlOptions.anchor = $.ControlAnchor.TOP_LEFT;
  5197. }
  5198. }
  5199. } else {
  5200. this.element = document.getElementById( options.id );
  5201. options.controlOptions = {
  5202. anchor: $.ControlAnchor.NONE,
  5203. attachToViewer: false,
  5204. autoFade: false
  5205. };
  5206. }
  5207. this.element.id = options.id;
  5208. this.element.className += ' navigator';
  5209. options = $.extend( true, {
  5210. sizeRatio: $.DEFAULT_SETTINGS.navigatorSizeRatio
  5211. }, options, {
  5212. element: this.element,
  5213. //These need to be overridden to prevent recursion since
  5214. //the navigator is a viewer and a viewer has a navigator
  5215. showNavigator: false,
  5216. mouseNavEnabled: false,
  5217. showNavigationControl: false,
  5218. showSequenceControl: false,
  5219. immediateRender: true,
  5220. blendTime: 0,
  5221. animationTime: 0
  5222. });
  5223. options.minPixelRatio = this.minPixelRatio = viewer.minPixelRatio;
  5224. this.viewerSizeInPoints = viewer.viewport.deltaPointsFromPixels(viewerSize);
  5225. this.borderWidth = 2;
  5226. //At some browser magnification levels the display regions lines up correctly, but at some there appears to
  5227. //be a one pixel gap.
  5228. this.fudge = new $.Point(1, 1);
  5229. this.totalBorderWidths = new $.Point(this.borderWidth*2, this.borderWidth*2).minus(this.fudge);
  5230. (function( style, borderWidth ){
  5231. style.margin = '0px';
  5232. style.border = borderWidth + 'px solid #555';
  5233. style.padding = '0px';
  5234. style.background = '#000';
  5235. style.opacity = 0.8;
  5236. style.overflow = 'hidden';
  5237. }( this.element.style, this.borderWidth));
  5238. this.displayRegion = $.makeNeutralElement( "div" );
  5239. this.displayRegion.id = this.element.id + '-displayregion';
  5240. this.displayRegion.className = 'displayregion';
  5241. (function( style, borderWidth ){
  5242. style.position = 'relative';
  5243. style.top = '0px';
  5244. style.left = '0px';
  5245. style.fontSize = '0px';
  5246. style.overflow = 'hidden';
  5247. style.border = borderWidth + 'px solid #900';
  5248. style.margin = '0px';
  5249. style.padding = '0px';
  5250. //TODO: IE doesnt like this property being set
  5251. //try{ style.outline = '2px auto #909'; }catch(e){/*ignore*/}
  5252. style.background = 'transparent';
  5253. // We use square bracket notation on the statement below, because float is a keyword.
  5254. // This is important for the Google Closure compiler, if nothing else.
  5255. /*jshint sub:true */
  5256. style['float'] = 'left'; //Webkit
  5257. style.cssFloat = 'left'; //Firefox
  5258. style.styleFloat = 'left'; //IE
  5259. style.zIndex = 999999999;
  5260. style.cursor = 'default';
  5261. }( this.displayRegion.style, this.borderWidth ));
  5262. this.element.innerTracker = new $.MouseTracker({
  5263. element: this.element,
  5264. dragHandler: $.delegate( this, onCanvasDrag ),
  5265. clickHandler: $.delegate( this, onCanvasClick ),
  5266. releaseHandler: $.delegate( this, onCanvasRelease ),
  5267. scrollHandler: function(){
  5268. //dont scroll the page up and down if the user is scrolling
  5269. //in the navigator
  5270. return false;
  5271. }
  5272. }).setTracking( true );
  5273. /*this.displayRegion.outerTracker = new $.MouseTracker({
  5274. element: this.container,
  5275. clickTimeThreshold: this.clickTimeThreshold,
  5276. clickDistThreshold: this.clickDistThreshold,
  5277. enterHandler: $.delegate( this, onContainerEnter ),
  5278. exitHandler: $.delegate( this, onContainerExit ),
  5279. releaseHandler: $.delegate( this, onContainerRelease )
  5280. }).setTracking( this.mouseNavEnabled ? true : false ); // always tracking*/
  5281. viewer.addControl(
  5282. this.element,
  5283. options.controlOptions
  5284. );
  5285. if( options.width && options.height ){
  5286. this.element.style.width = options.width + 'px';
  5287. this.element.style.height = options.height + 'px';
  5288. } else {
  5289. this.element.style.width = ( viewerSize.x * options.sizeRatio ) + 'px';
  5290. this.element.style.height = ( viewerSize.y * options.sizeRatio ) + 'px';
  5291. }
  5292. $.Viewer.apply( this, [ options ] );
  5293. this.element.getElementsByTagName('form')[0].appendChild( this.displayRegion );
  5294. unneededElement = this.element.getElementsByTagName('textarea')[0];
  5295. if (unneededElement) {
  5296. unneededElement.parentNode.removeChild(unneededElement);
  5297. }
  5298. };
  5299. $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, {
  5300. /**
  5301. * @function
  5302. * @name OpenSeadragon.Navigator.prototype.update
  5303. */
  5304. update: function( viewport ){
  5305. var bounds,
  5306. topleft,
  5307. bottomright;
  5308. if( viewport && this.viewport ){
  5309. bounds = viewport.getBounds( true );
  5310. topleft = this.viewport.pixelFromPoint( bounds.getTopLeft());
  5311. bottomright = this.viewport.pixelFromPoint( bounds.getBottomRight()).minus(this.totalBorderWidths);
  5312. //update style for navigator-box
  5313. (function(style) {
  5314. style.top = topleft.y + 'px';
  5315. style.left = topleft.x + 'px';
  5316. var width = Math.abs( topleft.x - bottomright.x );
  5317. var height = Math.abs( topleft.y - bottomright.y );
  5318. // make sure width and height are non-negative so IE doesn't throw
  5319. style.width = Math.max( width, 0 ) + 'px';
  5320. style.height = Math.max( height, 0 ) + 'px';
  5321. }( this.displayRegion.style ));
  5322. }
  5323. },
  5324. open: function( source ){
  5325. var containerSize = this.viewer.viewport.containerSize.times( this.sizeRatio );
  5326. if( source.tileSize > containerSize.x ||
  5327. source.tileSize > containerSize.y ){
  5328. this.minPixelRatio = Math.min(
  5329. containerSize.x,
  5330. containerSize.y
  5331. ) / source.tileSize;
  5332. } else {
  5333. this.minPixelRatio = this.viewer.minPixelRatio;
  5334. }
  5335. return $.Viewer.prototype.open.apply( this, [ source ] );
  5336. }
  5337. });
  5338. /**
  5339. * @private
  5340. * @inner
  5341. * @function
  5342. */
  5343. function onCanvasClick( event ) {
  5344. var newBounds,
  5345. viewerPosition,
  5346. dimensions;
  5347. if (! this.drag) {
  5348. if ( this.viewer.viewport ) {
  5349. viewerPosition = this.viewport.deltaPointsFromPixels( event.position );
  5350. dimensions = this.viewer.viewport.getBounds().getSize();
  5351. newBounds = new $.Rect(
  5352. viewerPosition.x - dimensions.x/2,
  5353. viewerPosition.y - dimensions.y/2,
  5354. dimensions.x,
  5355. dimensions.y
  5356. );
  5357. if (this.viewer.source.aspectRatio > this.viewer.viewport.getAspectRatio()) {
  5358. newBounds.y = newBounds.y - ((this.viewerSizeInPoints.y - (1/this.viewer.source.aspectRatio)) /2 );
  5359. }
  5360. else {
  5361. newBounds.x = newBounds.x - ((this.viewerSizeInPoints.x -1) /2 );
  5362. }
  5363. this.viewer.viewport.fitBounds(newBounds);
  5364. this.viewer.viewport.applyConstraints();
  5365. }
  5366. }
  5367. else {
  5368. this.drag = false;
  5369. }
  5370. }
  5371. /**
  5372. * @private
  5373. * @inner
  5374. * @function
  5375. */
  5376. function onCanvasDrag( event ) {
  5377. if ( this.viewer.viewport ) {
  5378. this.drag = true;
  5379. if( !this.panHorizontal ){
  5380. event.delta.x = 0;
  5381. }
  5382. if( !this.panVertical ){
  5383. event.delta.y = 0;
  5384. }
  5385. this.viewer.viewport.panBy(
  5386. this.viewport.deltaPointsFromPixels(
  5387. event.delta
  5388. )
  5389. );
  5390. }
  5391. }
  5392. /**
  5393. * @private
  5394. * @inner
  5395. * @function
  5396. */
  5397. function onCanvasRelease( event ) {
  5398. if ( event.insideElementPressed && this.viewer.viewport ) {
  5399. this.viewer.viewport.applyConstraints();
  5400. }
  5401. }
  5402. /**
  5403. * @private
  5404. * @inner
  5405. * @function
  5406. */
  5407. function onCanvasScroll( event ) {
  5408. var factor;
  5409. if ( this.viewer.viewport ) {
  5410. factor = Math.pow( this.zoomPerScroll, event.scroll );
  5411. this.viewer.viewport.zoomBy(
  5412. factor,
  5413. this.viewport.getCenter()
  5414. );
  5415. this.viewer.viewport.applyConstraints();
  5416. }
  5417. //cancels event
  5418. return false;
  5419. }
  5420. }( OpenSeadragon ));
  5421. /*
  5422. * OpenSeadragon - getString/setString
  5423. *
  5424. * Copyright (C) 2009 CodePlex Foundation
  5425. * Copyright (C) 2010-2013 OpenSeadragon contributors
  5426. *
  5427. * Redistribution and use in source and binary forms, with or without
  5428. * modification, are permitted provided that the following conditions are
  5429. * met:
  5430. *
  5431. * - Redistributions of source code must retain the above copyright notice,
  5432. * this list of conditions and the following disclaimer.
  5433. *
  5434. * - Redistributions in binary form must reproduce the above copyright
  5435. * notice, this list of conditions and the following disclaimer in the
  5436. * documentation and/or other materials provided with the distribution.
  5437. *
  5438. * - Neither the name of CodePlex Foundation nor the names of its
  5439. * contributors may be used to endorse or promote products derived from
  5440. * this software without specific prior written permission.
  5441. *
  5442. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  5443. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  5444. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  5445. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  5446. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  5447. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  5448. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  5449. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  5450. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  5451. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  5452. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  5453. */
  5454. (function( $ ){
  5455. //TODO: I guess this is where the i18n needs to be reimplemented. I'll look
  5456. // into existing patterns for i18n in javascript but i think that mimicking
  5457. // pythons gettext might be a reasonable approach.
  5458. var I18N = {
  5459. Errors: {
  5460. Dzc: "Sorry, we don't support Deep Zoom Collections!",
  5461. Dzi: "Hmm, this doesn't appear to be a valid Deep Zoom Image.",
  5462. Xml: "Hmm, this doesn't appear to be a valid Deep Zoom Image.",
  5463. ImageFormat: "Sorry, we don't support {0}-based Deep Zoom Images.",
  5464. Security: "It looks like a security restriction stopped us from " +
  5465. "loading this Deep Zoom Image.",
  5466. Status: "This space unintentionally left blank ({0} {1}).",
  5467. OpenFailed: "Unable to open {0}: {1}"
  5468. },
  5469. Tooltips: {
  5470. FullPage: "Toggle full page",
  5471. Home: "Go home",
  5472. ZoomIn: "Zoom in",
  5473. ZoomOut: "Zoom out",
  5474. NextPage: "Next page",
  5475. PreviousPage: "Previous page"
  5476. }
  5477. };
  5478. $.extend( $, {
  5479. /**
  5480. * @function
  5481. * @name OpenSeadragon.getString
  5482. * @param {String} property
  5483. */
  5484. getString: function( prop ) {
  5485. var props = prop.split('.'),
  5486. string = null,
  5487. args = arguments,
  5488. container = I18N,
  5489. i;
  5490. for ( i = 0; i < props.length-1; i++ ) {
  5491. // in case not a subproperty
  5492. container = container[ props[ i ] ] || {};
  5493. }
  5494. string = container[ props[ i ] ];
  5495. if ( typeof( string ) != "string" ) {
  5496. $.console.debug( "Untranslated source string:", prop );
  5497. string = ""; // FIXME: this breaks gettext()-style convention, which would return source
  5498. }
  5499. return string.replace(/\{\d+\}/g, function(capture) {
  5500. var i = parseInt( capture.match( /\d+/ ), 10 ) + 1;
  5501. return i < args.length ?
  5502. args[ i ] :
  5503. "";
  5504. });
  5505. },
  5506. /**
  5507. * @function
  5508. * @name OpenSeadragon.setString
  5509. * @param {String} property
  5510. * @param {*} value
  5511. */
  5512. setString: function( prop, value ) {
  5513. var props = prop.split('.'),
  5514. container = I18N,
  5515. i;
  5516. for ( i = 0; i < props.length - 1; i++ ) {
  5517. if ( !container[ props[ i ] ] ) {
  5518. container[ props[ i ] ] = {};
  5519. }
  5520. container = container[ props[ i ] ];
  5521. }
  5522. container[ props[ i ] ] = value;
  5523. }
  5524. });
  5525. }( OpenSeadragon ));
  5526. /*
  5527. * OpenSeadragon - Point
  5528. *
  5529. * Copyright (C) 2009 CodePlex Foundation
  5530. * Copyright (C) 2010-2013 OpenSeadragon contributors
  5531. *
  5532. * Redistribution and use in source and binary forms, with or without
  5533. * modification, are permitted provided that the following conditions are
  5534. * met:
  5535. *
  5536. * - Redistributions of source code must retain the above copyright notice,
  5537. * this list of conditions and the following disclaimer.
  5538. *
  5539. * - Redistributions in binary form must reproduce the above copyright
  5540. * notice, this list of conditions and the following disclaimer in the
  5541. * documentation and/or other materials provided with the distribution.
  5542. *
  5543. * - Neither the name of CodePlex Foundation nor the names of its
  5544. * contributors may be used to endorse or promote products derived from
  5545. * this software without specific prior written permission.
  5546. *
  5547. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  5548. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  5549. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  5550. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  5551. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  5552. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  5553. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  5554. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  5555. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  5556. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  5557. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  5558. */
  5559. (function( $ ){
  5560. /**
  5561. * A Point is really used as a 2-dimensional vector, equally useful for
  5562. * representing a point on a plane, or the height and width of a plane
  5563. * not requiring any other frame of reference.
  5564. * @class
  5565. * @param {Number} [x] The vector component 'x'. Defaults to the origin at 0.
  5566. * @param {Number} [y] The vector component 'y'. Defaults to the origin at 0.
  5567. * @property {Number} [x] The vector component 'x'.
  5568. * @property {Number} [y] The vector component 'y'.
  5569. */
  5570. $.Point = function( x, y ) {
  5571. this.x = typeof ( x ) == "number" ? x : 0;
  5572. this.y = typeof ( y ) == "number" ? y : 0;
  5573. };
  5574. $.Point.prototype = {
  5575. /**
  5576. * Add another Point to this point and return a new Point.
  5577. * @function
  5578. * @param {OpenSeadragon.Point} point The point to add vector components.
  5579. * @returns {OpenSeadragon.Point} A new point representing the sum of the
  5580. * vector components
  5581. */
  5582. plus: function( point ) {
  5583. return new $.Point(
  5584. this.x + point.x,
  5585. this.y + point.y
  5586. );
  5587. },
  5588. /**
  5589. * Add another Point to this point and return a new Point.
  5590. * @function
  5591. * @param {OpenSeadragon.Point} point The point to add vector components.
  5592. * @returns {OpenSeadragon.Point} A new point representing the sum of the
  5593. * vector components
  5594. */
  5595. minus: function( point ) {
  5596. return new $.Point(
  5597. this.x - point.x,
  5598. this.y - point.y
  5599. );
  5600. },
  5601. /**
  5602. * Add another Point to this point and return a new Point.
  5603. * @function
  5604. * @param {OpenSeadragon.Point} point The point to add vector components.
  5605. * @returns {OpenSeadragon.Point} A new point representing the sum of the
  5606. * vector components
  5607. */
  5608. times: function( factor ) {
  5609. return new $.Point(
  5610. this.x * factor,
  5611. this.y * factor
  5612. );
  5613. },
  5614. /**
  5615. * Add another Point to this point and return a new Point.
  5616. * @function
  5617. * @param {OpenSeadragon.Point} point The point to add vector components.
  5618. * @returns {OpenSeadragon.Point} A new point representing the sum of the
  5619. * vector components
  5620. */
  5621. divide: function( factor ) {
  5622. return new $.Point(
  5623. this.x / factor,
  5624. this.y / factor
  5625. );
  5626. },
  5627. /**
  5628. * Add another Point to this point and return a new Point.
  5629. * @function
  5630. * @param {OpenSeadragon.Point} point The point to add vector components.
  5631. * @returns {OpenSeadragon.Point} A new point representing the sum of the
  5632. * vector components
  5633. */
  5634. negate: function() {
  5635. return new $.Point( -this.x, -this.y );
  5636. },
  5637. /**
  5638. * Add another Point to this point and return a new Point.
  5639. * @function
  5640. * @param {OpenSeadragon.Point} point The point to add vector components.
  5641. * @returns {OpenSeadragon.Point} A new point representing the sum of the
  5642. * vector components
  5643. */
  5644. distanceTo: function( point ) {
  5645. return Math.sqrt(
  5646. Math.pow( this.x - point.x, 2 ) +
  5647. Math.pow( this.y - point.y, 2 )
  5648. );
  5649. },
  5650. /**
  5651. * Add another Point to this point and return a new Point.
  5652. * @function
  5653. * @param {OpenSeadragon.Point} point The point to add vector components.
  5654. * @returns {OpenSeadragon.Point} A new point representing the sum of the
  5655. * vector components
  5656. */
  5657. apply: function( func ) {
  5658. return new $.Point( func( this.x ), func( this.y ) );
  5659. },
  5660. /**
  5661. * Add another Point to this point and return a new Point.
  5662. * @function
  5663. * @param {OpenSeadragon.Point} point The point to add vector components.
  5664. * @returns {OpenSeadragon.Point} A new point representing the sum of the
  5665. * vector components
  5666. */
  5667. equals: function( point ) {
  5668. return (
  5669. point instanceof $.Point
  5670. ) && (
  5671. this.x === point.x
  5672. ) && (
  5673. this.y === point.y
  5674. );
  5675. },
  5676. /**
  5677. * Rotates the point around the specified pivot
  5678. * From http://stackoverflow.com/questions/4465931/rotate-rectangle-around-a-point
  5679. * @function
  5680. * @param {Number} degress to rotate around the pivot.
  5681. * @param {OpenSeadragon.Point} pivot Point about which to rotate.
  5682. * @returns {OpenSeadragon.Point}. A new point representing the point rotated around the specified pivot
  5683. */
  5684. rotate: function ( degrees, pivot ) {
  5685. var angle = degrees * Math.PI / 180.0,
  5686. x = Math.cos( angle ) * ( this.x - pivot.x ) - Math.sin( angle ) * ( this.y - pivot.y ) + pivot.x,
  5687. y = Math.sin( angle ) * ( this.x - pivot.x ) + Math.cos( angle ) * ( this.y - pivot.y ) + pivot.y;
  5688. return new $.Point( x, y );
  5689. },
  5690. /**
  5691. * Add another Point to this point and return a new Point.
  5692. * @function
  5693. * @param {OpenSeadragon.Point} point The point to add vector components.
  5694. * @returns {OpenSeadragon.Point} A new point representing the sum of the
  5695. * vector components
  5696. */
  5697. toString: function() {
  5698. return "(" + Math.round(this.x) + "," + Math.round(this.y) + ")";
  5699. }
  5700. };
  5701. }( OpenSeadragon ));
  5702. /*
  5703. * OpenSeadragon - TileSource
  5704. *
  5705. * Copyright (C) 2009 CodePlex Foundation
  5706. * Copyright (C) 2010-2013 OpenSeadragon contributors
  5707. *
  5708. * Redistribution and use in source and binary forms, with or without
  5709. * modification, are permitted provided that the following conditions are
  5710. * met:
  5711. *
  5712. * - Redistributions of source code must retain the above copyright notice,
  5713. * this list of conditions and the following disclaimer.
  5714. *
  5715. * - Redistributions in binary form must reproduce the above copyright
  5716. * notice, this list of conditions and the following disclaimer in the
  5717. * documentation and/or other materials provided with the distribution.
  5718. *
  5719. * - Neither the name of CodePlex Foundation nor the names of its
  5720. * contributors may be used to endorse or promote products derived from
  5721. * this software without specific prior written permission.
  5722. *
  5723. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  5724. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  5725. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  5726. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  5727. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  5728. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  5729. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  5730. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  5731. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  5732. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  5733. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  5734. */
  5735. (function( $ ){
  5736. /**
  5737. * The TileSource contains the most basic implementation required to create a
  5738. * smooth transition between layer in an image pyramid. It has only a single key
  5739. * interface that must be implemented to complete it key functionality:
  5740. * 'getTileUrl'. It also has several optional interfaces that can be
  5741. * implemented if a new TileSource wishes to support configuration via a simple
  5742. * object or array ('configure') and if the tile source supports or requires
  5743. * configuration via retreival of a document on the network ala AJAX or JSONP,
  5744. * ('getImageInfo').
  5745. * <br/>
  5746. * By default the image pyramid is split into N layers where the images longest
  5747. * side in M (in pixels), where N is the smallest integer which satisfies
  5748. * <strong>2^(N+1) >= M</strong>.
  5749. * @class
  5750. * @extends OpenSeadragon.EventSource
  5751. * @param {Number|Object|Array|String} width
  5752. * If more than a single argument is supplied, the traditional use of
  5753. * positional parameters is supplied and width is expected to be the width
  5754. * source image at it's max resolution in pixels. If a single argument is supplied and
  5755. * it is an Object or Array, the construction is assumed to occur through
  5756. * the extending classes implementation of 'configure'. Finally if only a
  5757. * single argument is supplied and it is a String, the extending class is
  5758. * expected to implement 'getImageInfo' and 'configure'.
  5759. * @param {Number} height
  5760. * Width of the source image at max resolution in pixels.
  5761. * @param {Number} tileSize
  5762. * The size of the tiles to assumed to make up each pyramid layer in pixels.
  5763. * Tile size determines the point at which the image pyramid must be
  5764. * divided into a matrix of smaller images.
  5765. * @param {Number} tileOverlap
  5766. * The number of pixels each tile is expected to overlap touching tiles.
  5767. * @param {Number} minLevel
  5768. * The minimum level to attempt to load.
  5769. * @param {Number} maxLevel
  5770. * The maximum level to attempt to load.
  5771. * @property {Number} aspectRatio
  5772. * Ratio of width to height
  5773. * @property {OpenSeadragon.Point} dimensions
  5774. * Vector storing x and y dimensions ( width and height respectively ).
  5775. * @property {Number} tileSize
  5776. * The size of the image tiles used to compose the image.
  5777. * @property {Number} tileOverlap
  5778. * The overlap in pixels each tile shares with it's adjacent neighbors.
  5779. * @property {Number} minLevel
  5780. * The minimum pyramid level this tile source supports or should attempt to load.
  5781. * @property {Number} maxLevel
  5782. * The maximum pyramid level this tile source supports or should attempt to load.
  5783. */
  5784. $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLevel ) {
  5785. var callback = null,
  5786. args = arguments,
  5787. options,
  5788. i;
  5789. if( $.isPlainObject( width ) ){
  5790. options = width;
  5791. }else{
  5792. options = {
  5793. width: args[0],
  5794. height: args[1],
  5795. tileSize: args[2],
  5796. tileOverlap: args[3],
  5797. minLevel: args[4],
  5798. maxLevel: args[5]
  5799. };
  5800. }
  5801. //Tile sources supply some events, namely 'ready' when they must be configured
  5802. //by asyncronously fetching their configuration data.
  5803. $.EventSource.call( this );
  5804. //we allow options to override anything we dont treat as
  5805. //required via idiomatic options or which is functionally
  5806. //set depending on the state of the readiness of this tile
  5807. //source
  5808. $.extend( true, this, options );
  5809. //Any functions that are passed as arguments are bound to the ready callback
  5810. /*jshint loopfunc:true*/
  5811. for ( i = 0; i < arguments.length; i++ ) {
  5812. if ( $.isFunction( arguments[ i ] ) ) {
  5813. callback = arguments[ i ];
  5814. this.addHandler( 'ready', function ( event ) {
  5815. callback( event );
  5816. } );
  5817. //only one callback per constructor
  5818. break;
  5819. }
  5820. }
  5821. if( 'string' == $.type( arguments[ 0 ] ) ){
  5822. //in case the getImageInfo method is overriden and/or implies an
  5823. //async mechanism set some safe defaults first
  5824. this.aspectRatio = 1;
  5825. this.dimensions = new $.Point( 10, 10 );
  5826. this.tileSize = 0;
  5827. this.tileOverlap = 0;
  5828. this.minLevel = 0;
  5829. this.maxLevel = 0;
  5830. this.ready = false;
  5831. //configuration via url implies the extending class
  5832. //implements and 'configure'
  5833. console.log(arguments[ 0 ]);
  5834. this.getImageInfo( arguments[ 0 ] );
  5835. } else {
  5836. //explicit configuration via positional args in constructor
  5837. //or the more idiomatic 'options' object
  5838. this.ready = true;
  5839. this.aspectRatio = ( options.width && options.height ) ?
  5840. ( options.width / options.height ) : 1;
  5841. this.dimensions = new $.Point( options.width, options.height );
  5842. this.tileSize = options.tileSize ? options.tileSize : 0;
  5843. this.tileOverlap = options.tileOverlap ? options.tileOverlap : 0;
  5844. this.minLevel = options.minLevel ? options.minLevel : 0;
  5845. this.maxLevel = ( undefined !== options.maxLevel && null !== options.maxLevel ) ?
  5846. options.maxLevel : (
  5847. ( options.width && options.height ) ? Math.ceil(
  5848. Math.log( Math.max( options.width, options.height ) ) /
  5849. Math.log( 2 )
  5850. ) : 0
  5851. );
  5852. if( callback && $.isFunction( callback ) ){
  5853. callback( this );
  5854. }
  5855. }
  5856. };
  5857. $.TileSource.prototype = {
  5858. /**
  5859. * @function
  5860. * @param {Number} level
  5861. */
  5862. getLevelScale: function( level ) {
  5863. // see https://github.com/openseadragon/openseadragon/issues/22
  5864. // we use the tilesources implementation of getLevelScale to generate
  5865. // a memoized re-implementation
  5866. var levelScaleCache = {},
  5867. i;
  5868. for( i = 0; i <= this.maxLevel; i++ ){
  5869. levelScaleCache[ i ] = 1 / Math.pow(2, this.maxLevel - i);
  5870. }
  5871. this.getLevelScale = function( _level ){
  5872. return levelScaleCache[ _level ];
  5873. };
  5874. return this.getLevelScale( level );
  5875. },
  5876. /**
  5877. * @function
  5878. * @param {Number} level
  5879. */
  5880. getNumTiles: function( level ) {
  5881. var scale = this.getLevelScale( level ),
  5882. x = Math.ceil( scale * this.dimensions.x / this.tileSize ),
  5883. y = Math.ceil( scale * this.dimensions.y / this.tileSize );
  5884. return new $.Point( x, y );
  5885. },
  5886. /**
  5887. * @function
  5888. * @param {Number} level
  5889. */
  5890. getPixelRatio: function( level ) {
  5891. var imageSizeScaled = this.dimensions.times( this.getLevelScale( level ) ),
  5892. rx = 1.0 / imageSizeScaled.x,
  5893. ry = 1.0 / imageSizeScaled.y;
  5894. return new $.Point(rx, ry);
  5895. },
  5896. /**
  5897. * @function
  5898. * @param {Number} level
  5899. */
  5900. getClosestLevel: function( rect ) {
  5901. var i,
  5902. tilesPerSide = Math.floor( Math.max( rect.x, rect.y ) / this.tileSize ),
  5903. tiles;
  5904. for( i = this.minLevel; i < this.maxLevel; i++ ){
  5905. tiles = this.getNumTiles( i );
  5906. if( Math.max( tiles.x, tiles.y ) + 1 >= tilesPerSide ){
  5907. break;
  5908. }
  5909. }
  5910. return Math.max( 0, i - 1 );
  5911. },
  5912. /**
  5913. * @function
  5914. * @param {Number} level
  5915. * @param {OpenSeadragon.Point} point
  5916. */
  5917. getTileAtPoint: function( level, point ) {
  5918. var pixel = point.times( this.dimensions.x ).times( this.getLevelScale(level ) ),
  5919. tx = Math.floor( pixel.x / this.tileSize ),
  5920. ty = Math.floor( pixel.y / this.tileSize );
  5921. return new $.Point( tx, ty );
  5922. },
  5923. /**
  5924. * @function
  5925. * @param {Number} level
  5926. * @param {Number} x
  5927. * @param {Number} y
  5928. */
  5929. getTileBounds: function( level, x, y ) {
  5930. var dimensionsScaled = this.dimensions.times( this.getLevelScale( level ) ),
  5931. px = ( x === 0 ) ? 0 : this.tileSize * x - this.tileOverlap,
  5932. py = ( y === 0 ) ? 0 : this.tileSize * y - this.tileOverlap,
  5933. sx = this.tileSize + ( x === 0 ? 1 : 2 ) * this.tileOverlap,
  5934. sy = this.tileSize + ( y === 0 ? 1 : 2 ) * this.tileOverlap,
  5935. scale = 1.0 / dimensionsScaled.x;
  5936. sx = Math.min( sx, dimensionsScaled.x - px );
  5937. sy = Math.min( sy, dimensionsScaled.y - py );
  5938. return new $.Rect( px * scale, py * scale, sx * scale, sy * scale );
  5939. },
  5940. /**
  5941. * Responsible for retrieving, and caching the
  5942. * image metadata pertinent to this TileSources implementation.
  5943. * @function
  5944. * @param {String} url
  5945. * @throws {Error}
  5946. */
  5947. getImageInfo: function( url ) {
  5948. var _this = this,
  5949. callbackName,
  5950. callback,
  5951. readySource,
  5952. options,
  5953. urlParts,
  5954. filename,
  5955. lastDot;
  5956. if( url ) {
  5957. urlParts = url.split( '/' );
  5958. filename = urlParts[ urlParts.length - 1 ];
  5959. lastDot = filename.lastIndexOf( '.' );
  5960. if ( lastDot > -1 ) {
  5961. urlParts[ urlParts.length - 1 ] = filename.slice( 0, lastDot );
  5962. }
  5963. }
  5964. callback = function( data ){
  5965. if( typeof(data) === "string" ) {
  5966. data = $.parseXml( data );
  5967. }
  5968. var $TileSource = $.TileSource.determineType( _this, data, url );
  5969. if ( !$TileSource ) {
  5970. _this.raiseEvent( 'open-failed', { message: "Unable to load TileSource", source: url } );
  5971. return;
  5972. }
  5973. options = $TileSource.prototype.configure.apply( _this, [ data, url ]);
  5974. readySource = new $TileSource( options );
  5975. _this.ready = true;
  5976. _this.raiseEvent( 'ready', { tileSource: readySource } );
  5977. };
  5978. if( url.match(/\.js$/) ){
  5979. //TODO: Its not very flexible to require tile sources to end jsonp
  5980. // request for info with a url that ends with '.js' but for
  5981. // now it's the only way I see to distinguish uniformly.
  5982. callbackName = url.split( '/' ).pop().replace('.js','');
  5983. $.jsonp({
  5984. url: url,
  5985. async: false,
  5986. callbackName: callbackName,
  5987. callback: callback
  5988. });
  5989. } else {
  5990. // request info via xhr asyncronously.
  5991. $.makeAjaxRequest( url, function( xhr ) {
  5992. var data = processResponse( xhr );
  5993. callback( data );
  5994. }, function ( xhr, exc ) {
  5995. var msg;
  5996. /*
  5997. IE < 10 will block XHR requests to different origins. Any property access on the request
  5998. object will raise an exception which we'll attempt to handle by formatting the original
  5999. exception rather than the second one raised when we try to access xhr.status
  6000. */
  6001. try {
  6002. msg = "HTTP " + xhr.status + " attempting to load TileSource";
  6003. } catch ( e ) {
  6004. var formattedExc;
  6005. if ( typeof( exc ) == "undefined" || !exc.toString ) {
  6006. formattedExc = "Unknown error";
  6007. } else {
  6008. formattedExc = exc.toString();
  6009. }
  6010. msg = formattedExc + " attempting to load TileSource";
  6011. }
  6012. _this.raiseEvent( 'open-failed', {
  6013. message: msg,
  6014. source: url
  6015. });
  6016. });
  6017. }
  6018. },
  6019. /**
  6020. * Responsible determining if a the particular TileSource supports the
  6021. * data format ( and allowed to apply logic against the url the data was
  6022. * loaded from, if any ). Overriding implementations are expected to do
  6023. * something smart with data and / or url to determine support. Also
  6024. * understand that iteration order of TileSources is not guarunteed so
  6025. * please make sure your data or url is expressive enough to ensure a simple
  6026. * and sufficient mechanisim for clear determination.
  6027. * @function
  6028. * @param {String|Object|Array|Document} data
  6029. * @param {String} url - the url the data was loaded
  6030. * from if any.
  6031. * @return {Boolean}
  6032. */
  6033. supports: function( data, url ) {
  6034. return false;
  6035. },
  6036. /**
  6037. * Responsible for parsing and configuring the
  6038. * image metadata pertinent to this TileSources implementation.
  6039. * This method is not implemented by this class other than to throw an Error
  6040. * announcing you have to implement it. Because of the variety of tile
  6041. * server technologies, and various specifications for building image
  6042. * pyramids, this method is here to allow easy integration.
  6043. * @function
  6044. * @param {String|Object|Array|Document} data
  6045. * @param {String} url - the url the data was loaded
  6046. * from if any.
  6047. * @return {Object} options - A dictionary of keyword arguments sufficient
  6048. * to configure this tile sources constructor.
  6049. * @throws {Error}
  6050. */
  6051. configure: function( data, url ) {
  6052. throw new Error( "Method not implemented." );
  6053. },
  6054. /**
  6055. * Responsible for retriving the url which will return an image for the
  6056. * region speified by the given x, y, and level components.
  6057. * This method is not implemented by this class other than to throw an Error
  6058. * announcing you have to implement it. Because of the variety of tile
  6059. * server technologies, and various specifications for building image
  6060. * pyramids, this method is here to allow easy integration.
  6061. * @function
  6062. * @param {Number} level
  6063. * @param {Number} x
  6064. * @param {Number} y
  6065. * @throws {Error}
  6066. */
  6067. getTileUrl: function( level, x, y ) {
  6068. throw new Error( "Method not implemented." );
  6069. },
  6070. /**
  6071. * @function
  6072. * @param {Number} level
  6073. * @param {Number} x
  6074. * @param {Number} y
  6075. */
  6076. tileExists: function( level, x, y ) {
  6077. var numTiles = this.getNumTiles( level );
  6078. return level >= this.minLevel &&
  6079. level <= this.maxLevel &&
  6080. x >= 0 &&
  6081. y >= 0 &&
  6082. x < numTiles.x &&
  6083. y < numTiles.y;
  6084. }
  6085. };
  6086. $.extend( true, $.TileSource.prototype, $.EventSource.prototype );
  6087. /**
  6088. * Decides whether to try to process the response as xml, json, or hand back
  6089. * the text
  6090. * @eprivate
  6091. * @inner
  6092. * @function
  6093. * @param {XMLHttpRequest} xhr - the completed network request
  6094. */
  6095. function processResponse( xhr ){
  6096. var responseText = xhr.responseText,
  6097. status = xhr.status,
  6098. statusText,
  6099. data;
  6100. if ( !xhr ) {
  6101. throw new Error( $.getString( "Errors.Security" ) );
  6102. } else if ( xhr.status !== 200 && xhr.status !== 0 ) {
  6103. status = xhr.status;
  6104. statusText = ( status == 404 ) ?
  6105. "Not Found" :
  6106. xhr.statusText;
  6107. throw new Error( $.getString( "Errors.Status", status, statusText ) );
  6108. }
  6109. if( responseText.match(/\s*<.*/) ){
  6110. try{
  6111. data = ( xhr.responseXML && xhr.responseXML.documentElement ) ?
  6112. xhr.responseXML :
  6113. $.parseXml( responseText );
  6114. } catch (e){
  6115. data = xhr.responseText;
  6116. }
  6117. }else if( responseText.match(/\s*[\{\[].*/) ){
  6118. /*jshint evil:true*/
  6119. data = eval( '('+responseText+')' );
  6120. }else{
  6121. data = responseText;
  6122. }
  6123. return data;
  6124. }
  6125. /**
  6126. * Determines the TileSource Implementation by introspection of OpenSeadragon
  6127. * namespace, calling each TileSource implementation of 'isType'
  6128. * @eprivate
  6129. * @inner
  6130. * @function
  6131. * @param {Object|Array|Document} data - the tile source configuration object
  6132. * @param {String} url - the url where the tile source configuration object was
  6133. * loaded from, if any.
  6134. */
  6135. $.TileSource.determineType = function( tileSource, data, url ){
  6136. var property;
  6137. for( property in OpenSeadragon ){
  6138. if( property.match(/.+TileSource$/) &&
  6139. $.isFunction( OpenSeadragon[ property ] ) &&
  6140. $.isFunction( OpenSeadragon[ property ].prototype.supports ) &&
  6141. OpenSeadragon[ property ].prototype.supports.call( tileSource, data, url )
  6142. ){
  6143. return OpenSeadragon[ property ];
  6144. }
  6145. }
  6146. $.console.error( "No TileSource was able to open %s %s", url, data );
  6147. };
  6148. }( OpenSeadragon ));
  6149. /*
  6150. * OpenSeadragon - DziTileSource
  6151. *
  6152. * Copyright (C) 2009 CodePlex Foundation
  6153. * Copyright (C) 2010-2013 OpenSeadragon contributors
  6154. *
  6155. * Redistribution and use in source and binary forms, with or without
  6156. * modification, are permitted provided that the following conditions are
  6157. * met:
  6158. *
  6159. * - Redistributions of source code must retain the above copyright notice,
  6160. * this list of conditions and the following disclaimer.
  6161. *
  6162. * - Redistributions in binary form must reproduce the above copyright
  6163. * notice, this list of conditions and the following disclaimer in the
  6164. * documentation and/or other materials provided with the distribution.
  6165. *
  6166. * - Neither the name of CodePlex Foundation nor the names of its
  6167. * contributors may be used to endorse or promote products derived from
  6168. * this software without specific prior written permission.
  6169. *
  6170. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  6171. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  6172. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  6173. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  6174. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  6175. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  6176. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  6177. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  6178. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  6179. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  6180. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  6181. */
  6182. (function( $ ){
  6183. /**
  6184. * @class
  6185. * @extends OpenSeadragon.TileSource
  6186. * @param {Number|Object} width - the pixel width of the image or the idiomatic
  6187. * options object which is used instead of positional arguments.
  6188. * @param {Number} height
  6189. * @param {Number} tileSize
  6190. * @param {Number} tileOverlap
  6191. * @param {String} tilesUrl
  6192. * @param {String} fileFormat
  6193. * @param {OpenSeadragon.DisplayRect[]} displayRects
  6194. * @property {String} tilesUrl
  6195. * @property {String} fileFormat
  6196. * @property {OpenSeadragon.DisplayRect[]} displayRects
  6197. */
  6198. $.DziTileSource = function( width, height, tileSize, tileOverlap, tilesUrl, fileFormat, displayRects, minLevel, maxLevel ) {
  6199. var i,
  6200. rect,
  6201. level,
  6202. options;
  6203. if( $.isPlainObject( width ) ){
  6204. options = width;
  6205. }else{
  6206. options = {
  6207. width: arguments[ 0 ],
  6208. height: arguments[ 1 ],
  6209. tileSize: arguments[ 2 ],
  6210. tileOverlap: arguments[ 3 ],
  6211. tilesUrl: arguments[ 4 ],
  6212. fileFormat: arguments[ 5 ],
  6213. displayRects: arguments[ 6 ],
  6214. minLevel: arguments[ 7 ],
  6215. maxLevel: arguments[ 8 ]
  6216. };
  6217. }
  6218. this._levelRects = {};
  6219. this.tilesUrl = options.tilesUrl;
  6220. this.fileFormat = options.fileFormat;
  6221. this.displayRects = options.displayRects;
  6222. if ( this.displayRects ) {
  6223. for ( i = this.displayRects.length - 1; i >= 0; i-- ) {
  6224. rect = this.displayRects[ i ];
  6225. for ( level = rect.minLevel; level <= rect.maxLevel; level++ ) {
  6226. if ( !this._levelRects[ level ] ) {
  6227. this._levelRects[ level ] = [];
  6228. }
  6229. this._levelRects[ level ].push( rect );
  6230. }
  6231. }
  6232. }
  6233. $.TileSource.apply( this, [ options ] );
  6234. };
  6235. $.extend( $.DziTileSource.prototype, $.TileSource.prototype, {
  6236. /**
  6237. * Determine if the data and/or url imply the image service is supported by
  6238. * this tile source.
  6239. * @function
  6240. * @name OpenSeadragon.DziTileSource.prototype.supports
  6241. * @param {Object|Array} data
  6242. * @param {String} optional - url
  6243. */
  6244. supports: function( data, url ){
  6245. var ns;
  6246. if ( data.Image ) {
  6247. ns = data.Image.xmlns;
  6248. } else if ( data.documentElement && "Image" == data.documentElement.tagName ) {
  6249. ns = data.documentElement.namespaceURI;
  6250. }
  6251. return ( "http://schemas.microsoft.com/deepzoom/2008" == ns ||
  6252. "http://schemas.microsoft.com/deepzoom/2009" == ns );
  6253. },
  6254. /**
  6255. *
  6256. * @function
  6257. * @name OpenSeadragon.DziTileSource.prototype.configure
  6258. * @param {Object|XMLDocument} data - the raw configuration
  6259. * @param {String} url - the url the data was retreived from if any.
  6260. * @return {Object} options - A dictionary of keyword arguments sufficient
  6261. * to configure this tile sources constructor.
  6262. */
  6263. configure: function( data, url ){
  6264. var options;
  6265. if( !$.isPlainObject(data) ){
  6266. options = configureFromXML( this, data );
  6267. }else{
  6268. options = configureFromObject( this, data );
  6269. }
  6270. if (url && !options.tilesUrl) {
  6271. options.tilesUrl = url.replace(/([^\/]+)\.(dzi|xml|js)$/, '$1_files/');
  6272. }
  6273. return options;
  6274. },
  6275. /**
  6276. * @function
  6277. * @name OpenSeadragon.DziTileSource.prototype.getTileUrl
  6278. * @param {Number} level
  6279. * @param {Number} x
  6280. * @param {Number} y
  6281. */
  6282. getTileUrl: function( level, x, y ) {
  6283. return [ this.tilesUrl, level, '/', x, '_', y, '.', this.fileFormat ].join( '' );
  6284. },
  6285. /**
  6286. * @function
  6287. * @name OpenSeadragon.DziTileSource.prototype.tileExists
  6288. * @param {Number} level
  6289. * @param {Number} x
  6290. * @param {Number} y
  6291. */
  6292. tileExists: function( level, x, y ) {
  6293. var rects = this._levelRects[ level ],
  6294. rect,
  6295. scale,
  6296. xMin,
  6297. yMin,
  6298. xMax,
  6299. yMax,
  6300. i;
  6301. if ( !rects || !rects.length ) {
  6302. return true;
  6303. }
  6304. for ( i = rects.length - 1; i >= 0; i-- ) {
  6305. rect = rects[ i ];
  6306. if ( level < rect.minLevel || level > rect.maxLevel ) {
  6307. continue;
  6308. }
  6309. scale = this.getLevelScale( level );
  6310. xMin = rect.x * scale;
  6311. yMin = rect.y * scale;
  6312. xMax = xMin + rect.width * scale;
  6313. yMax = yMin + rect.height * scale;
  6314. xMin = Math.floor( xMin / this.tileSize );
  6315. yMin = Math.floor( yMin / this.tileSize );
  6316. xMax = Math.ceil( xMax / this.tileSize );
  6317. yMax = Math.ceil( yMax / this.tileSize );
  6318. if ( xMin <= x && x < xMax && yMin <= y && y < yMax ) {
  6319. return true;
  6320. }
  6321. }
  6322. return false;
  6323. }
  6324. });
  6325. /**
  6326. * @private
  6327. * @inner
  6328. * @function
  6329. */
  6330. function configureFromXML( tileSource, xmlDoc ){
  6331. if ( !xmlDoc || !xmlDoc.documentElement ) {
  6332. throw new Error( $.getString( "Errors.Xml" ) );
  6333. }
  6334. var root = xmlDoc.documentElement,
  6335. rootName = root.tagName,
  6336. configuration = null,
  6337. displayRects = [],
  6338. dispRectNodes,
  6339. dispRectNode,
  6340. rectNode,
  6341. sizeNode,
  6342. i;
  6343. if ( rootName == "Image" ) {
  6344. try {
  6345. sizeNode = root.getElementsByTagName( "Size" )[ 0 ];
  6346. configuration = {
  6347. Image: {
  6348. xmlns: "http://schemas.microsoft.com/deepzoom/2008",
  6349. Url: root.getAttribute( "Url" ),
  6350. Format: root.getAttribute( "Format" ),
  6351. DisplayRect: null,
  6352. Overlap: parseInt( root.getAttribute( "Overlap" ), 10 ),
  6353. TileSize: parseInt( root.getAttribute( "TileSize" ), 10 ),
  6354. Size: {
  6355. Height: parseInt( sizeNode.getAttribute( "Height" ), 10 ),
  6356. Width: parseInt( sizeNode.getAttribute( "Width" ), 10 )
  6357. }
  6358. }
  6359. };
  6360. if ( !$.imageFormatSupported( configuration.Image.Format ) ) {
  6361. throw new Error(
  6362. $.getString( "Errors.ImageFormat", configuration.Image.Format.toUpperCase() )
  6363. );
  6364. }
  6365. dispRectNodes = root.getElementsByTagName( "DisplayRect" );
  6366. for ( i = 0; i < dispRectNodes.length; i++ ) {
  6367. dispRectNode = dispRectNodes[ i ];
  6368. rectNode = dispRectNode.getElementsByTagName( "Rect" )[ 0 ];
  6369. displayRects.push({
  6370. Rect: {
  6371. X: parseInt( rectNode.getAttribute( "X" ), 10 ),
  6372. Y: parseInt( rectNode.getAttribute( "Y" ), 10 ),
  6373. Width: parseInt( rectNode.getAttribute( "Width" ), 10 ),
  6374. Height: parseInt( rectNode.getAttribute( "Height" ), 10 ),
  6375. MinLevel: parseInt( dispRectNode.getAttribute( "MinLevel" ), 10 ),
  6376. MaxLevel: parseInt( dispRectNode.getAttribute( "MaxLevel" ), 10 )
  6377. }
  6378. });
  6379. }
  6380. if( displayRects.length ){
  6381. configuration.Image.DisplayRect = displayRects;
  6382. }
  6383. return configureFromObject( tileSource, configuration );
  6384. } catch ( e ) {
  6385. throw (e instanceof Error) ?
  6386. e :
  6387. new Error( $.getString("Errors.Dzi") );
  6388. }
  6389. } else if ( rootName == "Collection" ) {
  6390. throw new Error( $.getString( "Errors.Dzc" ) );
  6391. } else if ( rootName == "Error" ) {
  6392. return $._processDZIError( root );
  6393. }
  6394. throw new Error( $.getString( "Errors.Dzi" ) );
  6395. }
  6396. /**
  6397. * @private
  6398. * @inner
  6399. * @function
  6400. */
  6401. function configureFromObject( tileSource, configuration ){
  6402. var imageData = configuration.Image,
  6403. tilesUrl = imageData.Url,
  6404. fileFormat = imageData.Format,
  6405. sizeData = imageData.Size,
  6406. dispRectData = imageData.DisplayRect || [],
  6407. width = parseInt( sizeData.Width, 10 ),
  6408. height = parseInt( sizeData.Height, 10 ),
  6409. tileSize = parseInt( imageData.TileSize, 10 ),
  6410. tileOverlap = parseInt( imageData.Overlap, 10 ),
  6411. displayRects = [],
  6412. rectData,
  6413. i;
  6414. //TODO: need to figure out out to better handle image format compatibility
  6415. // which actually includes additional file formats like xml and pdf
  6416. // and plain text for various tilesource implementations to avoid low
  6417. // level errors.
  6418. //
  6419. // For now, just don't perform the check.
  6420. //
  6421. /*if ( !imageFormatSupported( fileFormat ) ) {
  6422. throw new Error(
  6423. $.getString( "Errors.ImageFormat", fileFormat.toUpperCase() )
  6424. );
  6425. }*/
  6426. for ( i = 0; i < dispRectData.length; i++ ) {
  6427. rectData = dispRectData[ i ].Rect;
  6428. displayRects.push( new $.DisplayRect(
  6429. parseInt( rectData.X, 10 ),
  6430. parseInt( rectData.Y, 10 ),
  6431. parseInt( rectData.Width, 10 ),
  6432. parseInt( rectData.Height, 10 ),
  6433. parseInt( rectData.MinLevel, 10 ),
  6434. parseInt( rectData.MaxLevel, 10 )
  6435. ));
  6436. }
  6437. return $.extend(true, {
  6438. width: width, /* width *required */
  6439. height: height, /* height *required */
  6440. tileSize: tileSize, /* tileSize *required */
  6441. tileOverlap: tileOverlap, /* tileOverlap *required */
  6442. minLevel: null, /* minLevel */
  6443. maxLevel: null, /* maxLevel */
  6444. tilesUrl: tilesUrl, /* tilesUrl */
  6445. fileFormat: fileFormat, /* fileFormat */
  6446. displayRects: displayRects /* displayRects */
  6447. }, configuration );
  6448. }
  6449. }( OpenSeadragon ));
  6450. /*
  6451. * OpenSeadragon - IIIFTileSource
  6452. *
  6453. * Copyright (C) 2009 CodePlex Foundation
  6454. * Copyright (C) 2010-2013 OpenSeadragon contributors
  6455. *
  6456. * Redistribution and use in source and binary forms, with or without
  6457. * modification, are permitted provided that the following conditions are
  6458. * met:
  6459. *
  6460. * - Redistributions of source code must retain the above copyright notice,
  6461. * this list of conditions and the following disclaimer.
  6462. *
  6463. * - Redistributions in binary form must reproduce the above copyright
  6464. * notice, this list of conditions and the following disclaimer in the
  6465. * documentation and/or other materials provided with the distribution.
  6466. *
  6467. * - Neither the name of CodePlex Foundation nor the names of its
  6468. * contributors may be used to endorse or promote products derived from
  6469. * this software without specific prior written permission.
  6470. *
  6471. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  6472. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  6473. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  6474. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  6475. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  6476. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  6477. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  6478. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  6479. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  6480. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  6481. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  6482. */
  6483. /*
  6484. * The getTileUrl implementation is based on Jon Stroop's Python version,
  6485. * which is released under the New BSD license:
  6486. * https://gist.github.com/jpstroop/4624253
  6487. */
  6488. (function( $ ){
  6489. /**
  6490. * A client implementation of the International Image Interoperability
  6491. * Format: Image API Draft 0.2 - Please read more about the specification
  6492. * at
  6493. *
  6494. * @class
  6495. * @extends OpenSeadragon.TileSource
  6496. * @see http://library.stanford.edu/iiif/image-api/
  6497. */
  6498. $.IIIFTileSource = function( options ){
  6499. $.extend( true, this, options );
  6500. if( !(this.height && this.width && this.identifier && this.tilesUrl ) ){
  6501. throw new Error('IIIF required parameters not provided.');
  6502. }
  6503. //TODO: at this point the base tile source implementation assumes
  6504. // a tile is a square and so only has one property tileSize
  6505. // to store it. It may be possible to make tileSize a vector
  6506. // OpenSeadraon.Point but would require careful implementation
  6507. // to preserve backward compatibility.
  6508. options.tileSize = this.tile_width;
  6509. if (! options.maxLevel ) {
  6510. var mf = -1;
  6511. var scfs = this.scale_factors || this.scale_factor;
  6512. if ( scfs instanceof Array ) {
  6513. for ( var i = 0; i < scfs.length; i++ ) {
  6514. var cf = Number( scfs[i] );
  6515. if ( !isNaN( cf ) && cf > mf ) { mf = cf; }
  6516. }
  6517. }
  6518. if ( mf < 0 ) { options.maxLevel = Number(Math.ceil(Math.log(Math.max(this.width, this.height), 2))); }
  6519. else { options.maxLevel = mf; }
  6520. }
  6521. $.TileSource.apply( this, [ options ] );
  6522. };
  6523. $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, {
  6524. /**
  6525. * Determine if the data and/or url imply the image service is supported by
  6526. * this tile source.
  6527. * @function
  6528. * @name OpenSeadragon.IIIFTileSource.prototype.supports
  6529. * @param {Object|Array} data
  6530. * @param {String} optional - url
  6531. */
  6532. supports: function( data, url ){
  6533. return (
  6534. data.ns &&
  6535. "http://library.stanford.edu/iiif/image-api/ns/" == data.ns
  6536. ) || (
  6537. data.profile && (
  6538. "http://library.stanford.edu/iiif/image-api/compliance.html#level1" == data.profile ||
  6539. "http://library.stanford.edu/iiif/image-api/compliance.html#level2" == data.profile ||
  6540. "http://library.stanford.edu/iiif/image-api/compliance.html#level3" == data.profile ||
  6541. "http://library.stanford.edu/iiif/image-api/compliance.html" == data.profile
  6542. )
  6543. ) || (
  6544. data.documentElement &&
  6545. "info" == data.documentElement.tagName &&
  6546. "http://library.stanford.edu/iiif/image-api/ns/" ==
  6547. data.documentElement.namespaceURI
  6548. );
  6549. },
  6550. /**
  6551. *
  6552. * @function
  6553. * @name OpenSeadragon.IIIFTileSource.prototype.configure
  6554. * @param {Object|XMLDocument} data - the raw configuration
  6555. * @param {String} url - the url the data was retreived from if any.
  6556. * @return {Object} options - A dictionary of keyword arguments sufficient
  6557. * to configure this tile source via it's constructor.
  6558. */
  6559. configure: function( data, url ){
  6560. var service,
  6561. options,
  6562. host;
  6563. if( !$.isPlainObject(data) ){
  6564. options = configureFromXml( this, data );
  6565. }else{
  6566. options = configureFromObject( this, data );
  6567. }
  6568. if( url && !options.tilesUrl ){
  6569. service = url.split('/');
  6570. service.pop(); //info.json or info.xml
  6571. service = service.join('/');
  6572. if( 'http' !== url.substring( 0, 4 ) ){
  6573. host = location.protocol + '//' + location.host;
  6574. service = host + service;
  6575. }
  6576. options.tilesUrl = service.replace(
  6577. data.identifier,
  6578. ''
  6579. );
  6580. }
  6581. return options;
  6582. },
  6583. /**
  6584. * Responsible for retreiving the url which will return an image for the
  6585. * region speified by the given x, y, and level components.
  6586. * @function
  6587. * @name OpenSeadragon.IIIFTileSource.prototype.getTileUrl
  6588. * @param {Number} level - z index
  6589. * @param {Number} x
  6590. * @param {Number} y
  6591. * @throws {Error}
  6592. */
  6593. getTileUrl: function( level, x, y ){
  6594. //# constants
  6595. var IIIF_ROTATION = '0',
  6596. IIIF_QUALITY = 'native.jpg',
  6597. //## get the scale (level as a decimal)
  6598. scale = Math.pow( 0.5, this.maxLevel - level ),
  6599. //## get iiif size
  6600. // iiif_size = 'pct:' + ( scale * 100 ),
  6601. //# image dimensions at this level
  6602. level_width = Math.ceil( this.width * scale ),
  6603. level_height = Math.ceil( this.height * scale ),
  6604. //## iiif region
  6605. iiif_tile_size_width = Math.ceil( this.tileSize / scale ),
  6606. iiif_tile_size_height = Math.ceil( this.tileSize / scale ),
  6607. iiif_region,
  6608. iiif_tile_x,
  6609. iiif_tile_y,
  6610. iiif_tile_w,
  6611. iiif_tile_h,
  6612. iiif_size;
  6613. if ( level_width < this.tile_width && level_height < this.tile_height ){
  6614. iiif_size = level_width + ","; // + level_height; only one dim. for IIIF level 1 compliance
  6615. iiif_region = 'full';
  6616. } else {
  6617. iiif_tile_x = x * iiif_tile_size_width;
  6618. iiif_tile_y = y * iiif_tile_size_height;
  6619. iiif_tile_w = Math.min( iiif_tile_size_width, this.width - iiif_tile_x );
  6620. iiif_tile_h = Math.min( iiif_tile_size_height, this.height - iiif_tile_y );
  6621. iiif_size = Math.ceil(iiif_tile_w * scale) + ",";
  6622. iiif_region = [ iiif_tile_x, iiif_tile_y, iiif_tile_w, iiif_tile_h ].join(',');
  6623. }
  6624. return [
  6625. this.tilesUrl,
  6626. this.identifier,
  6627. iiif_region,
  6628. iiif_size,
  6629. IIIF_ROTATION,
  6630. IIIF_QUALITY
  6631. ].join('/');
  6632. }
  6633. });
  6634. /**
  6635. * @private
  6636. * @inner
  6637. * @function
  6638. *
  6639. <?xml version="1.0" encoding="UTF-8"?>
  6640. <info xmlns="http://library.stanford.edu/iiif/image-api/ns/">
  6641. <identifier>1E34750D-38DB-4825-A38A-B60A345E591C</identifier>
  6642. <width>6000</width>
  6643. <height>4000</height>
  6644. <scale_factors>
  6645. <scale_factor>1</scale_factor>
  6646. <scale_factor>2</scale_factor>
  6647. <scale_factor>4</scale_factor>
  6648. </scale_factors>
  6649. <tile_width>1024</tile_width>
  6650. <tile_height>1024</tile_height>
  6651. <formats>
  6652. <format>jpg</format>
  6653. <format>png</format>
  6654. </formats>
  6655. <qualities>
  6656. <quality>native</quality>
  6657. <quality>grey</quality>
  6658. </qualities>
  6659. </info>
  6660. */
  6661. function configureFromXml( tileSource, xmlDoc ){
  6662. //parse the xml
  6663. if ( !xmlDoc || !xmlDoc.documentElement ) {
  6664. throw new Error( $.getString( "Errors.Xml" ) );
  6665. }
  6666. var root = xmlDoc.documentElement,
  6667. rootName = root.tagName,
  6668. configuration = null;
  6669. if ( rootName == "info" ) {
  6670. try {
  6671. configuration = {
  6672. "ns": root.namespaceURI
  6673. };
  6674. parseXML( root, configuration );
  6675. return configureFromObject( tileSource, configuration );
  6676. } catch ( e ) {
  6677. throw (e instanceof Error) ?
  6678. e :
  6679. new Error( $.getString("Errors.IIIF") );
  6680. }
  6681. }
  6682. throw new Error( $.getString( "Errors.IIIF" ) );
  6683. }
  6684. /**
  6685. * @private
  6686. * @inner
  6687. * @function
  6688. */
  6689. function parseXML( node, configuration, property ){
  6690. var i,
  6691. value;
  6692. if( node.nodeType == 3 && property ){//text node
  6693. value = node.nodeValue.trim();
  6694. if( value.match(/^\d*$/)){
  6695. value = Number( value );
  6696. }
  6697. if( !configuration[ property ] ){
  6698. configuration[ property ] = value;
  6699. }else{
  6700. if( !$.isArray( configuration[ property ] ) ){
  6701. configuration[ property ] = [ configuration[ property ] ];
  6702. }
  6703. configuration[ property ].push( value );
  6704. }
  6705. } else if( node.nodeType == 1 ){
  6706. for( i = 0; i < node.childNodes.length; i++ ){
  6707. parseXML( node.childNodes[ i ], configuration, node.nodeName );
  6708. }
  6709. }
  6710. }
  6711. /**
  6712. * @private
  6713. * @inner
  6714. * @function
  6715. *
  6716. {
  6717. "profile" : "http://library.stanford.edu/iiif/image-api/compliance.html#level1",
  6718. "identifier" : "1E34750D-38DB-4825-A38A-B60A345E591C",
  6719. "width" : 6000,
  6720. "height" : 4000,
  6721. "scale_factors" : [ 1, 2, 4 ],
  6722. "tile_width" : 1024,
  6723. "tile_height" : 1024,
  6724. "formats" : [ "jpg", "png" ],
  6725. "quality" : [ "native", "grey" ]
  6726. }
  6727. */
  6728. function configureFromObject( tileSource, configuration ){
  6729. //the image_host property is not part of the iiif standard but is included here to
  6730. //allow the info.json and info.xml specify a different server to load the
  6731. //images from so we can test the implementation.
  6732. if( configuration.image_host ){
  6733. configuration.tilesUrl = configuration.image_host;
  6734. }
  6735. return configuration;
  6736. }
  6737. }( OpenSeadragon ));
  6738. /*
  6739. * OpenSeadragon - IIIF1_1TileSource
  6740. *
  6741. * Copyright (C) 2009 CodePlex Foundation
  6742. * Copyright (C) 2010-2013 OpenSeadragon contributors
  6743. *
  6744. * Redistribution and use in source and binary forms, with or without
  6745. * modification, are permitted provided that the following conditions are
  6746. * met:
  6747. *
  6748. * - Redistributions of source code must retain the above copyright notice,
  6749. * this list of conditions and the following disclaimer.
  6750. *
  6751. * - Redistributions in binary form must reproduce the above copyright
  6752. * notice, this list of conditions and the following disclaimer in the
  6753. * documentation and/or other materials provided with the distribution.
  6754. *
  6755. * - Neither the name of CodePlex Foundation nor the names of its
  6756. * contributors may be used to endorse or promote products derived from
  6757. * this software without specific prior written permission.
  6758. *
  6759. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  6760. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  6761. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  6762. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  6763. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  6764. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  6765. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  6766. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  6767. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  6768. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  6769. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  6770. */
  6771. (function( $ ){
  6772. /**
  6773. * A client implementation of the International Image Interoperability
  6774. * Format: Image API 1.1 - Please read more about the specification
  6775. * at
  6776. *
  6777. * @class
  6778. * @extends OpenSeadragon.TileSource
  6779. * @see http://library.stanford.edu/iiif/image-api/
  6780. */
  6781. $.IIIF1_1TileSource = function( options ){
  6782. $.extend( true, this, options );
  6783. if( !(this.height && this.width && this['@id'] ) ){
  6784. throw new Error('IIIF required parameters not provided.');
  6785. }
  6786. options.tileSize = this.tile_width;
  6787. if (! options.maxLevel ) {
  6788. var mf = -1;
  6789. var scfs = this.scale_factors || this.scale_factor;
  6790. if ( scfs instanceof Array ) {
  6791. for ( var i = 0; i < scfs.length; i++ ) {
  6792. var cf = Number( scfs[i] );
  6793. if ( !isNaN( cf ) && cf > mf ) { mf = cf; }
  6794. }
  6795. }
  6796. if ( mf < 0 ) { options.maxLevel = Number(Math.ceil(Math.log(Math.max(this.width, this.height), 2))); }
  6797. else { options.maxLevel = mf; }
  6798. }
  6799. $.TileSource.apply( this, [ options ] );
  6800. };
  6801. $.extend( $.IIIF1_1TileSource.prototype, $.TileSource.prototype, {
  6802. /**
  6803. * Determine if the data and/or url imply the image service is supported by
  6804. * this tile source.
  6805. * @function
  6806. * @name OpenSeadragon.IIIF1_1TileSource.prototype.supports
  6807. * @param {Object|Array} data
  6808. * @param {String} optional - url
  6809. */
  6810. supports: function( data, url ){
  6811. return data.profile && (
  6812. "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level0" == data.profile ||
  6813. "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level1" == data.profile ||
  6814. "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level2" == data.profile ||
  6815. "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level3" == data.profile ||
  6816. "http://library.stanford.edu/iiif/image-api/1.1/compliance.html" == data.profile
  6817. );
  6818. },
  6819. /**
  6820. *
  6821. * @function
  6822. * @name OpenSeadragon.IIIF1_1TileSource.prototype.configure
  6823. * @param {Object} data - the raw configuration
  6824. */
  6825. // IIIF 1.1 Info Looks like this (XML syntax is no more):
  6826. // {
  6827. // "@context" : "http://library.stanford.edu/iiif/image-api/1.1/context.json",
  6828. // "@id" : "http://iiif.example.com/prefix/1E34750D-38DB-4825-A38A-B60A345E591C",
  6829. // "width" : 6000,
  6830. // "height" : 4000,
  6831. // "scale_factors" : [ 1, 2, 4 ],
  6832. // "tile_width" : 1024,
  6833. // "tile_height" : 1024,
  6834. // "formats" : [ "jpg", "png" ],
  6835. // "qualities" : [ "native", "grey" ]
  6836. // "profile" : "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level0"
  6837. // }
  6838. configure: function( data ){
  6839. return data;
  6840. },
  6841. /**
  6842. * Responsible for retreiving the url which will return an image for the
  6843. * region specified by the given x, y, and level components.
  6844. * @function
  6845. * @name OpenSeadragon.IIIF1_1TileSource.prototype.getTileUrl
  6846. * @param {Number} level - z index
  6847. * @param {Number} x
  6848. * @param {Number} y
  6849. * @throws {Error}
  6850. */
  6851. getTileUrl: function( level, x, y ){
  6852. //# constants
  6853. var IIIF_ROTATION = '0',
  6854. IIIF_QUALITY = 'native.jpg',
  6855. //## get the scale (level as a decimal)
  6856. scale = Math.pow( 0.5, this.maxLevel - level ),
  6857. //# image dimensions at this level
  6858. level_width = Math.ceil( this.width * scale ),
  6859. level_height = Math.ceil( this.height * scale ),
  6860. //## iiif region
  6861. iiif_tile_size_width = Math.ceil( this.tileSize / scale ),
  6862. iiif_tile_size_height = Math.ceil( this.tileSize / scale ),
  6863. iiif_region,
  6864. iiif_tile_x,
  6865. iiif_tile_y,
  6866. iiif_tile_w,
  6867. iiif_tile_h,
  6868. iiif_size,
  6869. uri;
  6870. if ( level_width < this.tile_width && level_height < this.tile_height ){
  6871. iiif_size = level_width + ",";
  6872. iiif_region = 'full';
  6873. } else {
  6874. iiif_tile_x = x * iiif_tile_size_width;
  6875. iiif_tile_y = y * iiif_tile_size_height;
  6876. iiif_tile_w = Math.min( iiif_tile_size_width, this.width - iiif_tile_x );
  6877. iiif_tile_h = Math.min( iiif_tile_size_height, this.height - iiif_tile_y );
  6878. iiif_size = Math.ceil(iiif_tile_w * scale) + ",";
  6879. iiif_region = [ iiif_tile_x, iiif_tile_y, iiif_tile_w, iiif_tile_h ].join(',');
  6880. }
  6881. uri = [ this['@id'], iiif_region, iiif_size, IIIF_ROTATION, IIIF_QUALITY ].join('/');
  6882. return uri;
  6883. }
  6884. });
  6885. }( OpenSeadragon ));
  6886. /*
  6887. * OpenSeadragon - OsmTileSource
  6888. *
  6889. * Copyright (C) 2009 CodePlex Foundation
  6890. * Copyright (C) 2010-2013 OpenSeadragon contributors
  6891. *
  6892. * Redistribution and use in source and binary forms, with or without
  6893. * modification, are permitted provided that the following conditions are
  6894. * met:
  6895. *
  6896. * - Redistributions of source code must retain the above copyright notice,
  6897. * this list of conditions and the following disclaimer.
  6898. *
  6899. * - Redistributions in binary form must reproduce the above copyright
  6900. * notice, this list of conditions and the following disclaimer in the
  6901. * documentation and/or other materials provided with the distribution.
  6902. *
  6903. * - Neither the name of CodePlex Foundation nor the names of its
  6904. * contributors may be used to endorse or promote products derived from
  6905. * this software without specific prior written permission.
  6906. *
  6907. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  6908. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  6909. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  6910. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  6911. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  6912. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  6913. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  6914. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  6915. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  6916. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  6917. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  6918. */
  6919. /*
  6920. * Derived from the OSM tile source in Rainer Simon's seajax-utils project
  6921. * <http://github.com/rsimon/seajax-utils>. Rainer Simon has contributed
  6922. * the included code to the OpenSeadragon project under the New BSD license;
  6923. * see <https://github.com/openseadragon/openseadragon/issues/58>.
  6924. */
  6925. (function( $ ){
  6926. /**
  6927. * A tilesource implementation for OpenStreetMap.
  6928. *
  6929. * Note 1. Zoomlevels. Deep Zoom and OSM define zoom levels differently. In Deep
  6930. * Zoom, level 0 equals an image of 1x1 pixels. In OSM, level 0 equals an image of
  6931. * 256x256 levels (see http://gasi.ch/blog/inside-deep-zoom-2). I.e. there is a
  6932. * difference of log2(256)=8 levels.
  6933. *
  6934. * Note 2. Image dimension. According to the OSM Wiki
  6935. * (http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Zoom_levels)
  6936. * the highest Mapnik zoom level has 256.144x256.144 tiles, with a 256x256
  6937. * pixel size. I.e. the Deep Zoom image dimension is 65.572.864x65.572.864
  6938. * pixels.
  6939. *
  6940. * @class
  6941. * @extends OpenSeadragon.TileSource
  6942. * @param {Number|Object} width - the pixel width of the image or the idiomatic
  6943. * options object which is used instead of positional arguments.
  6944. * @param {Number} height
  6945. * @param {Number} tileSize
  6946. * @param {Number} tileOverlap
  6947. * @param {String} tilesUrl
  6948. */
  6949. $.OsmTileSource = function( width, height, tileSize, tileOverlap, tilesUrl ) {
  6950. var options;
  6951. if( $.isPlainObject( width ) ){
  6952. options = width;
  6953. }else{
  6954. options = {
  6955. width: arguments[0],
  6956. height: arguments[1],
  6957. tileSize: arguments[2],
  6958. tileOverlap: arguments[3],
  6959. tilesUrl: arguments[4]
  6960. };
  6961. }
  6962. //apply default setting for standard public OpenStreatMaps service
  6963. //but allow them to be specified so fliks can host there own instance
  6964. //or apply against other services supportting the same standard
  6965. if( !options.width || !options.height ){
  6966. options.width = 65572864;
  6967. options.height = 65572864;
  6968. }
  6969. if( !options.tileSize ){
  6970. options.tileSize = 256;
  6971. options.tileOverlap = 0;
  6972. }
  6973. if( !options.tilesUrl ){
  6974. options.tilesUrl = "http://tile.openstreetmap.org/";
  6975. }
  6976. options.minLevel = 8;
  6977. $.TileSource.apply( this, [ options ] );
  6978. };
  6979. $.extend( $.OsmTileSource.prototype, $.TileSource.prototype, {
  6980. /**
  6981. * Determine if the data and/or url imply the image service is supported by
  6982. * this tile source.
  6983. * @function
  6984. * @name OpenSeadragon.OsmTileSource.prototype.supports
  6985. * @param {Object|Array} data
  6986. * @param {String} optional - url
  6987. */
  6988. supports: function( data, url ){
  6989. return (
  6990. data.type &&
  6991. "openstreetmaps" == data.type
  6992. );
  6993. },
  6994. /**
  6995. *
  6996. * @function
  6997. * @name OpenSeadragon.OsmTileSource.prototype.configure
  6998. * @param {Object} data - the raw configuration
  6999. * @param {String} url - the url the data was retreived from if any.
  7000. * @return {Object} options - A dictionary of keyword arguments sufficient
  7001. * to configure this tile sources constructor.
  7002. */
  7003. configure: function( data, url ){
  7004. return data;
  7005. },
  7006. /**
  7007. * @function
  7008. * @name OpenSeadragon.OsmTileSource.prototype.getTileUrl
  7009. * @param {Number} level
  7010. * @param {Number} x
  7011. * @param {Number} y
  7012. */
  7013. getTileUrl: function( level, x, y ) {
  7014. return this.tilesUrl + (level - 8) + "/" + x + "/" + y + ".png";
  7015. }
  7016. });
  7017. }( OpenSeadragon ));
  7018. /*
  7019. * OpenSeadragon - TmsTileSource
  7020. *
  7021. * Copyright (C) 2009 CodePlex Foundation
  7022. * Copyright (C) 2010-2013 OpenSeadragon contributors
  7023. *
  7024. * Redistribution and use in source and binary forms, with or without
  7025. * modification, are permitted provided that the following conditions are
  7026. * met:
  7027. *
  7028. * - Redistributions of source code must retain the above copyright notice,
  7029. * this list of conditions and the following disclaimer.
  7030. *
  7031. * - Redistributions in binary form must reproduce the above copyright
  7032. * notice, this list of conditions and the following disclaimer in the
  7033. * documentation and/or other materials provided with the distribution.
  7034. *
  7035. * - Neither the name of CodePlex Foundation nor the names of its
  7036. * contributors may be used to endorse or promote products derived from
  7037. * this software without specific prior written permission.
  7038. *
  7039. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  7040. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  7041. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  7042. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  7043. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  7044. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  7045. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  7046. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  7047. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  7048. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  7049. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  7050. */
  7051. /*
  7052. * Derived from the TMS tile source in Rainer Simon's seajax-utils project
  7053. * <http://github.com/rsimon/seajax-utils>. Rainer Simon has contributed
  7054. * the included code to the OpenSeadragon project under the New BSD license;
  7055. * see <https://github.com/openseadragon/openseadragon/issues/58>.
  7056. */
  7057. (function( $ ){
  7058. /**
  7059. * A tilesource implementation for Tiled Map Services (TMS).
  7060. * TMS tile scheme ( [ as supported by OpenLayers ] is described here
  7061. * ( http://openlayers.org/dev/examples/tms.html ).
  7062. *
  7063. * @class
  7064. * @extends OpenSeadragon.TileSource
  7065. * @param {Number|Object} width - the pixel width of the image or the idiomatic
  7066. * options object which is used instead of positional arguments.
  7067. * @param {Number} height
  7068. * @param {Number} tileSize
  7069. * @param {Number} tileOverlap
  7070. * @param {String} tilesUrl
  7071. */
  7072. $.TmsTileSource = function( width, height, tileSize, tileOverlap, tilesUrl ) {
  7073. var options;
  7074. if( $.isPlainObject( width ) ){
  7075. options = width;
  7076. }else{
  7077. options = {
  7078. width: arguments[0],
  7079. height: arguments[1],
  7080. tileSize: arguments[2],
  7081. tileOverlap: arguments[3],
  7082. tilesUrl: arguments[4]
  7083. };
  7084. }
  7085. // TMS has integer multiples of 256 for width/height and adds buffer
  7086. // if necessary -> account for this!
  7087. var bufferedWidth = Math.ceil(options.width / 256) * 256,
  7088. bufferedHeight = Math.ceil(options.height / 256) * 256,
  7089. max;
  7090. // Compute number of zoomlevels in this tileset
  7091. if (bufferedWidth > bufferedHeight) {
  7092. max = bufferedWidth / 256;
  7093. } else {
  7094. max = bufferedHeight / 256;
  7095. }
  7096. options.maxLevel = Math.ceil(Math.log(max)/Math.log(2)) - 1;
  7097. options.tileSize = 256;
  7098. options.width = bufferedWidth;
  7099. options.height = bufferedHeight;
  7100. $.TileSource.apply( this, [ options ] );
  7101. };
  7102. $.extend( $.TmsTileSource.prototype, $.TileSource.prototype, {
  7103. /**
  7104. * Determine if the data and/or url imply the image service is supported by
  7105. * this tile source.
  7106. * @function
  7107. * @name OpenSeadragon.TmsTileSource.prototype.supports
  7108. * @param {Object|Array} data
  7109. * @param {String} optional - url
  7110. */
  7111. supports: function( data, url ){
  7112. return ( data.type && "tiledmapservice" == data.type );
  7113. },
  7114. /**
  7115. *
  7116. * @function
  7117. * @name OpenSeadragon.TmsTileSource.prototype.configure
  7118. * @param {Object} data - the raw configuration
  7119. * @param {String} url - the url the data was retreived from if any.
  7120. * @return {Object} options - A dictionary of keyword arguments sufficient
  7121. * to configure this tile sources constructor.
  7122. */
  7123. configure: function( data, url ){
  7124. return data;
  7125. },
  7126. /**
  7127. * @function
  7128. * @name OpenSeadragon.TmsTileSource.prototype.getTileUrl
  7129. * @param {Number} level
  7130. * @param {Number} x
  7131. * @param {Number} y
  7132. */
  7133. getTileUrl: function( level, x, y ) {
  7134. // Convert from Deep Zoom definition to TMS zoom definition
  7135. var yTiles = this.getNumTiles( level ).y - 1;
  7136. return this.tilesUrl + level + "/" + x + "/" + (yTiles - y) + ".png";
  7137. }
  7138. });
  7139. }( OpenSeadragon ));
  7140. /*
  7141. * OpenSeadragon - LegacyTileSource
  7142. *
  7143. * Copyright (C) 2009 CodePlex Foundation
  7144. * Copyright (C) 2010-2013 OpenSeadragon contributors
  7145. *
  7146. * Redistribution and use in source and binary forms, with or without
  7147. * modification, are permitted provided that the following conditions are
  7148. * met:
  7149. *
  7150. * - Redistributions of source code must retain the above copyright notice,
  7151. * this list of conditions and the following disclaimer.
  7152. *
  7153. * - Redistributions in binary form must reproduce the above copyright
  7154. * notice, this list of conditions and the following disclaimer in the
  7155. * documentation and/or other materials provided with the distribution.
  7156. *
  7157. * - Neither the name of CodePlex Foundation nor the names of its
  7158. * contributors may be used to endorse or promote products derived from
  7159. * this software without specific prior written permission.
  7160. *
  7161. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  7162. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  7163. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  7164. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  7165. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  7166. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  7167. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  7168. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  7169. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  7170. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  7171. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  7172. */
  7173. (function( $ ){
  7174. /**
  7175. * The LegacyTileSource allows simple, traditional image pyramids to be loaded
  7176. * into an OpenSeadragon Viewer. Basically, this translates to the historically
  7177. * common practice of starting with a 'master' image, maybe a tiff for example,
  7178. * and generating a set of 'service' images like one or more thumbnails, a medium
  7179. * resolution image and a high resolution image in standard web formats like
  7180. * png or jpg.
  7181. * @class
  7182. * @extends OpenSeadragon.TileSource
  7183. * @param {Array} levels An array of file descriptions, each is an object with
  7184. * a 'url', a 'width', and a 'height'. Overriding classes can expect more
  7185. * properties but these properties are sufficient for this implementation.
  7186. * Additionally, the levels are required to be listed in order from
  7187. * smallest to largest.
  7188. * @property {Number} aspectRatio
  7189. * @property {Number} dimensions
  7190. * @property {Number} tileSize
  7191. * @property {Number} tileOverlap
  7192. * @property {Number} minLevel
  7193. * @property {Number} maxLevel
  7194. * @property {Array} levels
  7195. */
  7196. $.LegacyTileSource = function( levels ) {
  7197. var options,
  7198. width,
  7199. height;
  7200. if( $.isArray( levels ) ){
  7201. options = {
  7202. type: 'legacy-image-pyramid',
  7203. levels: levels
  7204. };
  7205. }
  7206. //clean up the levels to make sure we support all formats
  7207. options.levels = filterFiles( options.levels );
  7208. if ( options.levels.length > 0 ) {
  7209. width = options.levels[ options.levels.length - 1 ].width;
  7210. height = options.levels[ options.levels.length - 1 ].height;
  7211. }
  7212. else {
  7213. width = 0;
  7214. height = 0;
  7215. $.console.error( "No supported image formats found" );
  7216. }
  7217. $.extend( true, options, {
  7218. width: width,
  7219. height: height,
  7220. tileSize: Math.max( height, width ),
  7221. tileOverlap: 0,
  7222. minLevel: 0,
  7223. maxLevel: options.levels.length > 0 ? options.levels.length - 1 : 0
  7224. } );
  7225. $.TileSource.apply( this, [ options ] );
  7226. this.levels = options.levels;
  7227. };
  7228. $.extend( $.LegacyTileSource.prototype, $.TileSource.prototype, {
  7229. /**
  7230. * Determine if the data and/or url imply the image service is supported by
  7231. * this tile source.
  7232. * @function
  7233. * @name OpenSeadragon.LegacyTileSource.prototype.supports
  7234. * @param {Object|Array} data
  7235. * @param {String} optional - url
  7236. */
  7237. supports: function( data, url ){
  7238. return (
  7239. data.type &&
  7240. "legacy-image-pyramid" == data.type
  7241. ) || (
  7242. data.documentElement &&
  7243. "legacy-image-pyramid" == data.documentElement.getAttribute('type')
  7244. );
  7245. },
  7246. /**
  7247. *
  7248. * @function
  7249. * @name OpenSeadragon.LegacyTileSource.prototype.configure
  7250. * @param {Object|XMLDocument} configuration - the raw configuration
  7251. * @param {String} dataUrl - the url the data was retreived from if any.
  7252. * @return {Object} options - A dictionary of keyword arguments sufficient
  7253. * to configure this tile sources constructor.
  7254. */
  7255. configure: function( configuration, dataUrl ){
  7256. var options;
  7257. if( !$.isPlainObject(configuration) ){
  7258. options = configureFromXML( this, configuration );
  7259. }else{
  7260. options = configureFromObject( this, configuration );
  7261. }
  7262. return options;
  7263. },
  7264. /**
  7265. * @function
  7266. * @name OpenSeadragon.LegacyTileSource.prototype.getLevelScale
  7267. * @param {Number} level
  7268. */
  7269. getLevelScale: function ( level ) {
  7270. var levelScale = NaN;
  7271. if ( this.levels.length > 0 && level >= this.minLevel && level <= this.maxLevel ) {
  7272. levelScale =
  7273. this.levels[ level ].width /
  7274. this.levels[ this.maxLevel ].width;
  7275. }
  7276. return levelScale;
  7277. },
  7278. /**
  7279. * @function
  7280. * @name OpenSeadragon.LegacyTileSource.prototype.getNumTiles
  7281. * @param {Number} level
  7282. */
  7283. getNumTiles: function( level ) {
  7284. var scale = this.getLevelScale( level );
  7285. if ( scale ){
  7286. return new $.Point( 1, 1 );
  7287. } else {
  7288. return new $.Point( 0, 0 );
  7289. }
  7290. },
  7291. /**
  7292. * @function
  7293. * @name OpenSeadragon.LegacyTileSource.prototype.getTileAtPoint
  7294. * @param {Number} level
  7295. * @param {OpenSeadragon.Point} point
  7296. */
  7297. getTileAtPoint: function( level, point ) {
  7298. return new $.Point( 0, 0 );
  7299. },
  7300. /**
  7301. * This method is not implemented by this class other than to throw an Error
  7302. * announcing you have to implement it. Because of the variety of tile
  7303. * server technologies, and various specifications for building image
  7304. * pyramids, this method is here to allow easy integration.
  7305. * @function
  7306. * @name OpenSeadragon.LegacyTileSource.prototype.getTileUrl
  7307. * @param {Number} level
  7308. * @param {Number} x
  7309. * @param {Number} y
  7310. * @throws {Error}
  7311. */
  7312. getTileUrl: function ( level, x, y ) {
  7313. var url = null;
  7314. if ( this.levels.length > 0 && level >= this.minLevel && level <= this.maxLevel ) {
  7315. url = this.levels[ level ].url;
  7316. }
  7317. return url;
  7318. }
  7319. } );
  7320. /**
  7321. * This method removes any files from the Array which dont conform to our
  7322. * basic requirements for a 'level' in the LegacyTileSource.
  7323. * @private
  7324. * @inner
  7325. * @function
  7326. */
  7327. function filterFiles( files ){
  7328. var filtered = [],
  7329. file,
  7330. i;
  7331. for( i = 0; i < files.length; i++ ){
  7332. file = files[ i ];
  7333. if( file.height &&
  7334. file.width &&
  7335. file.url && (
  7336. file.url.toLowerCase().match(/^.*\.(png|jpg|jpeg|gif)$/) || (
  7337. file.mimetype &&
  7338. file.mimetype.toLowerCase().match(/^.*\/(png|jpg|jpeg|gif)$/)
  7339. )
  7340. ) ){
  7341. //This is sufficient to serve as a level
  7342. filtered.push({
  7343. url: file.url,
  7344. width: Number( file.width ),
  7345. height: Number( file.height )
  7346. });
  7347. }
  7348. else {
  7349. $.console.error( 'Unsupported image format: %s', file.url ? file.url : '<no URL>' );
  7350. }
  7351. }
  7352. return filtered.sort(function(a,b){
  7353. return a.height - b.height;
  7354. });
  7355. }
  7356. /**
  7357. * @private
  7358. * @inner
  7359. * @function
  7360. */
  7361. function configureFromXML( tileSource, xmlDoc ){
  7362. if ( !xmlDoc || !xmlDoc.documentElement ) {
  7363. throw new Error( $.getString( "Errors.Xml" ) );
  7364. }
  7365. var root = xmlDoc.documentElement,
  7366. rootName = root.tagName,
  7367. conf = null,
  7368. levels = [],
  7369. level,
  7370. i;
  7371. if ( rootName == "image" ) {
  7372. try {
  7373. conf = {
  7374. type: root.getAttribute( "type" ),
  7375. levels: []
  7376. };
  7377. levels = root.getElementsByTagName( "level" );
  7378. for ( i = 0; i < levels.length; i++ ) {
  7379. level = levels[ i ];
  7380. conf.levels .push({
  7381. url: level.getAttribute( "url" ),
  7382. width: parseInt( level.getAttribute( "width" ), 10 ),
  7383. height: parseInt( level.getAttribute( "height" ), 10 )
  7384. });
  7385. }
  7386. return configureFromObject( tileSource, conf );
  7387. } catch ( e ) {
  7388. throw (e instanceof Error) ?
  7389. e :
  7390. new Error( 'Unknown error parsing Legacy Image Pyramid XML.' );
  7391. }
  7392. } else if ( rootName == "collection" ) {
  7393. throw new Error( 'Legacy Image Pyramid Collections not yet supported.' );
  7394. } else if ( rootName == "error" ) {
  7395. throw new Error( 'Error: ' + xmlDoc );
  7396. }
  7397. throw new Error( 'Unknown element ' + rootName );
  7398. }
  7399. /**
  7400. * @private
  7401. * @inner
  7402. * @function
  7403. */
  7404. function configureFromObject( tileSource, configuration ){
  7405. return configuration.levels;
  7406. }
  7407. }( OpenSeadragon ));
  7408. /*
  7409. * OpenSeadragon - TileSourceCollection
  7410. *
  7411. * Copyright (C) 2009 CodePlex Foundation
  7412. * Copyright (C) 2010-2013 OpenSeadragon contributors
  7413. *
  7414. * Redistribution and use in source and binary forms, with or without
  7415. * modification, are permitted provided that the following conditions are
  7416. * met:
  7417. *
  7418. * - Redistributions of source code must retain the above copyright notice,
  7419. * this list of conditions and the following disclaimer.
  7420. *
  7421. * - Redistributions in binary form must reproduce the above copyright
  7422. * notice, this list of conditions and the following disclaimer in the
  7423. * documentation and/or other materials provided with the distribution.
  7424. *
  7425. * - Neither the name of CodePlex Foundation nor the names of its
  7426. * contributors may be used to endorse or promote products derived from
  7427. * this software without specific prior written permission.
  7428. *
  7429. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  7430. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  7431. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  7432. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  7433. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  7434. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  7435. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  7436. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  7437. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  7438. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  7439. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  7440. */
  7441. (function( $ ){
  7442. /**
  7443. * @class
  7444. * @extends OpenSeadragon.TileSource
  7445. */
  7446. $.TileSourceCollection = function( tileSize, tileSources, rows, layout ) {
  7447. var options;
  7448. if( $.isPlainObject( tileSize ) ){
  7449. options = tileSize;
  7450. }else{
  7451. options = {
  7452. tileSize: arguments[ 0 ],
  7453. tileSources: arguments[ 1 ],
  7454. rows: arguments[ 2 ],
  7455. layout: arguments[ 3 ]
  7456. };
  7457. }
  7458. if( !options.layout ){
  7459. options.layout = 'horizontal';
  7460. }
  7461. var minLevel = 0,
  7462. levelSize = 1.0,
  7463. tilesPerRow = Math.ceil( options.tileSources.length / options.rows ),
  7464. longSide = tilesPerRow >= options.rows ?
  7465. tilesPerRow :
  7466. options.rows;
  7467. if( 'horizontal' == options.layout ){
  7468. options.width = ( options.tileSize ) * tilesPerRow;
  7469. options.height = ( options.tileSize ) * options.rows;
  7470. } else {
  7471. options.height = ( options.tileSize ) * tilesPerRow;
  7472. options.width = ( options.tileSize ) * options.rows;
  7473. }
  7474. options.tileOverlap = -options.tileMargin;
  7475. options.tilesPerRow = tilesPerRow;
  7476. //Set min level to avoid loading sublevels since collection is a
  7477. //different kind of abstraction
  7478. while( levelSize < ( options.tileSize ) * longSide ){
  7479. //$.console.log( '%s levelSize %s minLevel %s', options.tileSize * longSide, levelSize, minLevel );
  7480. levelSize = levelSize * 2.0;
  7481. minLevel++;
  7482. }
  7483. options.minLevel = minLevel;
  7484. //for( var name in options ){
  7485. // $.console.log( 'Collection %s %s', name, options[ name ] );
  7486. //}
  7487. $.TileSource.apply( this, [ options ] );
  7488. };
  7489. $.extend( $.TileSourceCollection.prototype, $.TileSource.prototype, {
  7490. /**
  7491. * @function
  7492. * @name OpenSeadragon.TileSourceCollection.prototype.getTileBounds
  7493. * @param {Number} level
  7494. * @param {Number} x
  7495. * @param {Number} y
  7496. */
  7497. getTileBounds: function( level, x, y ) {
  7498. var dimensionsScaled = this.dimensions.times( this.getLevelScale( level ) ),
  7499. px = this.tileSize * x - this.tileOverlap,
  7500. py = this.tileSize * y - this.tileOverlap,
  7501. sx = this.tileSize + 1 * this.tileOverlap,
  7502. sy = this.tileSize + 1 * this.tileOverlap,
  7503. scale = 1.0 / dimensionsScaled.x;
  7504. sx = Math.min( sx, dimensionsScaled.x - px );
  7505. sy = Math.min( sy, dimensionsScaled.y - py );
  7506. return new $.Rect( px * scale, py * scale, sx * scale, sy * scale );
  7507. },
  7508. /**
  7509. *
  7510. * @function
  7511. * @name OpenSeadragon.TileSourceCollection.prototype.configure
  7512. */
  7513. configure: function( data, url ){
  7514. return;
  7515. },
  7516. /**
  7517. * @function
  7518. * @name OpenSeadragon.TileSourceCollection.prototype.getTileUrl
  7519. * @param {Number} level
  7520. * @param {Number} x
  7521. * @param {Number} y
  7522. */
  7523. getTileUrl: function( level, x, y ) {
  7524. //$.console.log([ level, '/', x, '_', y ].join( '' ));
  7525. return null;
  7526. }
  7527. });
  7528. }( OpenSeadragon ));
  7529. /*
  7530. * OpenSeadragon - Button
  7531. *
  7532. * Copyright (C) 2009 CodePlex Foundation
  7533. * Copyright (C) 2010-2013 OpenSeadragon contributors
  7534. *
  7535. * Redistribution and use in source and binary forms, with or without
  7536. * modification, are permitted provided that the following conditions are
  7537. * met:
  7538. *
  7539. * - Redistributions of source code must retain the above copyright notice,
  7540. * this list of conditions and the following disclaimer.
  7541. *
  7542. * - Redistributions in binary form must reproduce the above copyright
  7543. * notice, this list of conditions and the following disclaimer in the
  7544. * documentation and/or other materials provided with the distribution.
  7545. *
  7546. * - Neither the name of CodePlex Foundation nor the names of its
  7547. * contributors may be used to endorse or promote products derived from
  7548. * this software without specific prior written permission.
  7549. *
  7550. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  7551. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  7552. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  7553. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  7554. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  7555. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  7556. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  7557. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  7558. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  7559. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  7560. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  7561. */
  7562. (function( $ ){
  7563. /**
  7564. * An enumeration of button states including, REST, GROUP, HOVER, and DOWN
  7565. * @static
  7566. */
  7567. $.ButtonState = {
  7568. REST: 0,
  7569. GROUP: 1,
  7570. HOVER: 2,
  7571. DOWN: 3
  7572. };
  7573. /**
  7574. * Manages events, hover states for individual buttons, tool-tips, as well
  7575. * as fading the bottons out when the user has not interacted with them
  7576. * for a specified period.
  7577. * @class
  7578. * @extends OpenSeadragon.EventSource
  7579. * @param {Object} options
  7580. * @param {String} options.tooltip Provides context help for the button we the
  7581. * user hovers over it.
  7582. * @param {String} options.srcRest URL of image to use in 'rest' state
  7583. * @param {String} options.srcGroup URL of image to use in 'up' state
  7584. * @param {String} options.srcHover URL of image to use in 'hover' state
  7585. * @param {String} options.srcDown URL of image to use in 'down' state
  7586. * @param {Element} [options.element] Element to use as a container for the
  7587. * button.
  7588. * @property {String} tooltip Provides context help for the button we the
  7589. * user hovers over it.
  7590. * @property {String} srcRest URL of image to use in 'rest' state
  7591. * @property {String} srcGroup URL of image to use in 'up' state
  7592. * @property {String} srcHover URL of image to use in 'hover' state
  7593. * @property {String} srcDown URL of image to use in 'down' state
  7594. * @property {Object} config Configurable settings for this button. DEPRECATED.
  7595. * @property {Element} [element] Element to use as a container for the
  7596. * button.
  7597. * @property {Number} fadeDelay How long to wait before fading
  7598. * @property {Number} fadeLength How long should it take to fade the button.
  7599. * @property {Number} fadeBeginTime When the button last began to fade.
  7600. * @property {Boolean} shouldFade Whether this button should fade after user
  7601. * stops interacting with the viewport.
  7602. this.fadeDelay = 0; // begin fading immediately
  7603. this.fadeLength = 2000; // fade over a period of 2 seconds
  7604. this.fadeBeginTime = null;
  7605. this.shouldFade = false;
  7606. */
  7607. $.Button = function( options ) {
  7608. var _this = this;
  7609. $.EventSource.call( this );
  7610. $.extend( true, this, {
  7611. tooltip: null,
  7612. srcRest: null,
  7613. srcGroup: null,
  7614. srcHover: null,
  7615. srcDown: null,
  7616. clickTimeThreshold: $.DEFAULT_SETTINGS.clickTimeThreshold,
  7617. clickDistThreshold: $.DEFAULT_SETTINGS.clickDistThreshold,
  7618. // begin fading immediately
  7619. fadeDelay: 0,
  7620. // fade over a period of 2 seconds
  7621. fadeLength: 2000,
  7622. onPress: null,
  7623. onRelease: null,
  7624. onClick: null,
  7625. onEnter: null,
  7626. onExit: null,
  7627. onFocus: null,
  7628. onBlur: null
  7629. }, options );
  7630. this.element = options.element || $.makeNeutralElement( "button" );
  7631. this.element.href = this.element.href || '#';
  7632. //if the user has specified the element to bind the control to explicitly
  7633. //then do not add the default control images
  7634. if( !options.element ){
  7635. this.imgRest = $.makeTransparentImage( this.srcRest );
  7636. this.imgGroup = $.makeTransparentImage( this.srcGroup );
  7637. this.imgHover = $.makeTransparentImage( this.srcHover );
  7638. this.imgDown = $.makeTransparentImage( this.srcDown );
  7639. this.element.appendChild( this.imgRest );
  7640. this.element.appendChild( this.imgGroup );
  7641. this.element.appendChild( this.imgHover );
  7642. this.element.appendChild( this.imgDown );
  7643. this.imgGroup.style.position =
  7644. this.imgHover.style.position =
  7645. this.imgDown.style.position =
  7646. "absolute";
  7647. this.imgGroup.style.top =
  7648. this.imgHover.style.top =
  7649. this.imgDown.style.top =
  7650. "0px";
  7651. this.imgGroup.style.left =
  7652. this.imgHover.style.left =
  7653. this.imgDown.style.left =
  7654. "0px";
  7655. this.imgHover.style.visibility =
  7656. this.imgDown.style.visibility =
  7657. "hidden";
  7658. if ( $.Browser.vendor == $.BROWSERS.FIREFOX && $.Browser.version < 3 ){
  7659. this.imgGroup.style.top =
  7660. this.imgHover.style.top =
  7661. this.imgDown.style.top =
  7662. "";
  7663. }
  7664. }
  7665. this.addHandler( "press", this.onPress );
  7666. this.addHandler( "release", this.onRelease );
  7667. this.addHandler( "click", this.onClick );
  7668. this.addHandler( "enter", this.onEnter );
  7669. this.addHandler( "exit", this.onExit );
  7670. this.addHandler( "focus", this.onFocus );
  7671. this.addHandler( "blur", this.onBlur );
  7672. this.currentState = $.ButtonState.GROUP;
  7673. this.fadeBeginTime = null;
  7674. this.shouldFade = false;
  7675. this.element.style.display = "inline-block";
  7676. this.element.style.position = "relative";
  7677. this.element.title = this.tooltip;
  7678. this.tracker = new $.MouseTracker({
  7679. element: this.element,
  7680. clickTimeThreshold: this.clickTimeThreshold,
  7681. clickDistThreshold: this.clickDistThreshold,
  7682. enterHandler: function( event ) {
  7683. if ( event.insideElementPressed ) {
  7684. inTo( _this, $.ButtonState.DOWN );
  7685. _this.raiseEvent( "enter", { originalEvent: event.originalEvent } );
  7686. } else if ( !event.buttonDownAny ) {
  7687. inTo( _this, $.ButtonState.HOVER );
  7688. }
  7689. },
  7690. focusHandler: function ( event ) {
  7691. this.enterHandler( event );
  7692. _this.raiseEvent( "focus", { originalEvent: event.originalEvent } );
  7693. },
  7694. exitHandler: function( event ) {
  7695. outTo( _this, $.ButtonState.GROUP );
  7696. if ( event.insideElementPressed ) {
  7697. _this.raiseEvent( "exit", { originalEvent: event.originalEvent } );
  7698. }
  7699. },
  7700. blurHandler: function ( event ) {
  7701. this.exitHandler( event );
  7702. _this.raiseEvent( "blur", { originalEvent: event.originalEvent } );
  7703. },
  7704. pressHandler: function ( event ) {
  7705. inTo( _this, $.ButtonState.DOWN );
  7706. _this.raiseEvent( "press", { originalEvent: event.originalEvent } );
  7707. },
  7708. releaseHandler: function( event ) {
  7709. if ( event.insideElementPressed && event.insideElementReleased ) {
  7710. outTo( _this, $.ButtonState.HOVER );
  7711. _this.raiseEvent( "release", { originalEvent: event.originalEvent } );
  7712. } else if ( event.insideElementPressed ) {
  7713. outTo( _this, $.ButtonState.GROUP );
  7714. } else {
  7715. inTo( _this, $.ButtonState.HOVER );
  7716. }
  7717. },
  7718. clickHandler: function( event ) {
  7719. if ( event.quick ) {
  7720. _this.raiseEvent("click", { originalEvent: event.originalEvent });
  7721. }
  7722. },
  7723. keyHandler: function( event ){
  7724. //console.log( "%s : handling key %s!", _this.tooltip, event.keyCode);
  7725. if( 13 === event.keyCode ){
  7726. _this.raiseEvent( "click", { originalEvent: event.originalEvent } );
  7727. _this.raiseEvent( "release", { originalEvent: event.originalEvent } );
  7728. return false;
  7729. }
  7730. return true;
  7731. }
  7732. }).setTracking( true );
  7733. outTo( this, $.ButtonState.REST );
  7734. };
  7735. $.extend( $.Button.prototype, $.EventSource.prototype, {
  7736. /**
  7737. * TODO: Determine what this function is intended to do and if it's actually
  7738. * useful as an API point.
  7739. * @function
  7740. * @name OpenSeadragon.Button.prototype.notifyGroupEnter
  7741. */
  7742. notifyGroupEnter: function() {
  7743. inTo( this, $.ButtonState.GROUP );
  7744. },
  7745. /**
  7746. * TODO: Determine what this function is intended to do and if it's actually
  7747. * useful as an API point.
  7748. * @function
  7749. * @name OpenSeadragon.Button.prototype.notifyGroupExit
  7750. */
  7751. notifyGroupExit: function() {
  7752. outTo( this, $.ButtonState.REST );
  7753. },
  7754. disable: function(){
  7755. this.notifyGroupExit();
  7756. this.element.disabled = true;
  7757. $.setElementOpacity( this.element, 0.2, true );
  7758. },
  7759. enable: function(){
  7760. this.element.disabled = false;
  7761. $.setElementOpacity( this.element, 1.0, true );
  7762. this.notifyGroupEnter();
  7763. }
  7764. });
  7765. function scheduleFade( button ) {
  7766. $.requestAnimationFrame(function(){
  7767. updateFade( button );
  7768. });
  7769. }
  7770. function updateFade( button ) {
  7771. var currentTime,
  7772. deltaTime,
  7773. opacity;
  7774. if ( button.shouldFade ) {
  7775. currentTime = $.now();
  7776. deltaTime = currentTime - button.fadeBeginTime;
  7777. opacity = 1.0 - deltaTime / button.fadeLength;
  7778. opacity = Math.min( 1.0, opacity );
  7779. opacity = Math.max( 0.0, opacity );
  7780. if( button.imgGroup ){
  7781. $.setElementOpacity( button.imgGroup, opacity, true );
  7782. }
  7783. if ( opacity > 0 ) {
  7784. // fade again
  7785. scheduleFade( button );
  7786. }
  7787. }
  7788. }
  7789. function beginFading( button ) {
  7790. button.shouldFade = true;
  7791. button.fadeBeginTime = $.now() + button.fadeDelay;
  7792. window.setTimeout( function(){
  7793. scheduleFade( button );
  7794. }, button.fadeDelay );
  7795. }
  7796. function stopFading( button ) {
  7797. button.shouldFade = false;
  7798. if( button.imgGroup ){
  7799. $.setElementOpacity( button.imgGroup, 1.0, true );
  7800. }
  7801. }
  7802. function inTo( button, newState ) {
  7803. if( button.element.disabled ){
  7804. return;
  7805. }
  7806. if ( newState >= $.ButtonState.GROUP &&
  7807. button.currentState == $.ButtonState.REST ) {
  7808. stopFading( button );
  7809. button.currentState = $.ButtonState.GROUP;
  7810. }
  7811. if ( newState >= $.ButtonState.HOVER &&
  7812. button.currentState == $.ButtonState.GROUP ) {
  7813. if( button.imgHover ){
  7814. button.imgHover.style.visibility = "";
  7815. }
  7816. button.currentState = $.ButtonState.HOVER;
  7817. }
  7818. if ( newState >= $.ButtonState.DOWN &&
  7819. button.currentState == $.ButtonState.HOVER ) {
  7820. if( button.imgDown ){
  7821. button.imgDown.style.visibility = "";
  7822. }
  7823. button.currentState = $.ButtonState.DOWN;
  7824. }
  7825. }
  7826. function outTo( button, newState ) {
  7827. if( button.element.disabled ){
  7828. return;
  7829. }
  7830. if ( newState <= $.ButtonState.HOVER &&
  7831. button.currentState == $.ButtonState.DOWN ) {
  7832. if( button.imgDown ){
  7833. button.imgDown.style.visibility = "hidden";
  7834. }
  7835. button.currentState = $.ButtonState.HOVER;
  7836. }
  7837. if ( newState <= $.ButtonState.GROUP &&
  7838. button.currentState == $.ButtonState.HOVER ) {
  7839. if( button.imgHover ){
  7840. button.imgHover.style.visibility = "hidden";
  7841. }
  7842. button.currentState = $.ButtonState.GROUP;
  7843. }
  7844. if ( newState <= $.ButtonState.REST &&
  7845. button.currentState == $.ButtonState.GROUP ) {
  7846. beginFading( button );
  7847. button.currentState = $.ButtonState.REST;
  7848. }
  7849. }
  7850. }( OpenSeadragon ));
  7851. /*
  7852. * OpenSeadragon - ButtonGroup
  7853. *
  7854. * Copyright (C) 2009 CodePlex Foundation
  7855. * Copyright (C) 2010-2013 OpenSeadragon contributors
  7856. *
  7857. * Redistribution and use in source and binary forms, with or without
  7858. * modification, are permitted provided that the following conditions are
  7859. * met:
  7860. *
  7861. * - Redistributions of source code must retain the above copyright notice,
  7862. * this list of conditions and the following disclaimer.
  7863. *
  7864. * - Redistributions in binary form must reproduce the above copyright
  7865. * notice, this list of conditions and the following disclaimer in the
  7866. * documentation and/or other materials provided with the distribution.
  7867. *
  7868. * - Neither the name of CodePlex Foundation nor the names of its
  7869. * contributors may be used to endorse or promote products derived from
  7870. * this software without specific prior written permission.
  7871. *
  7872. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  7873. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  7874. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  7875. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  7876. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  7877. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  7878. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  7879. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  7880. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  7881. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  7882. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  7883. */
  7884. (function( $ ){
  7885. /**
  7886. * Manages events on groups of buttons.
  7887. * @class
  7888. * @param {Object} options - a dictionary of settings applied against the entire
  7889. * group of buttons
  7890. * @param {Array} options.buttons Array of buttons
  7891. * @param {Element} [options.group] Element to use as the container,
  7892. * @param {Object} options.config Object with Viewer settings ( TODO: is
  7893. * this actually used anywhere? )
  7894. * @param {Function} [options.enter] Function callback for when the mouse
  7895. * enters group
  7896. * @param {Function} [options.exit] Function callback for when mouse leaves
  7897. * the group
  7898. * @param {Function} [options.release] Function callback for when mouse is
  7899. * released
  7900. * @property {Array} buttons - An array containing the buttons themselves.
  7901. * @property {Element} element - The shared container for the buttons.
  7902. * @property {Object} config - Configurable settings for the group of buttons.
  7903. * @property {OpenSeadragon.MouseTracker} tracker - Tracks mouse events accross
  7904. * the group of buttons.
  7905. **/
  7906. $.ButtonGroup = function( options ) {
  7907. $.extend( true, this, {
  7908. buttons: [],
  7909. clickTimeThreshold: $.DEFAULT_SETTINGS.clickTimeThreshold,
  7910. clickDistThreshold: $.DEFAULT_SETTINGS.clickDistThreshold,
  7911. labelText: ""
  7912. }, options );
  7913. // copy the botton elements
  7914. var buttons = this.buttons.concat([]),
  7915. _this = this,
  7916. i;
  7917. this.element = options.element || $.makeNeutralElement( "fieldgroup" );
  7918. if( !options.group ){
  7919. this.label = $.makeNeutralElement( "label" );
  7920. //TODO: support labels for ButtonGroups
  7921. //this.label.innerHTML = this.labelText;
  7922. this.element.style.display = "inline-block";
  7923. this.element.appendChild( this.label );
  7924. for ( i = 0; i < buttons.length; i++ ) {
  7925. this.element.appendChild( buttons[ i ].element );
  7926. }
  7927. }
  7928. this.tracker = new $.MouseTracker({
  7929. element: this.element,
  7930. clickTimeThreshold: this.clickTimeThreshold,
  7931. clickDistThreshold: this.clickDistThreshold,
  7932. enterHandler: function ( event ) {
  7933. var i;
  7934. for ( i = 0; i < _this.buttons.length; i++ ) {
  7935. _this.buttons[ i ].notifyGroupEnter();
  7936. }
  7937. },
  7938. exitHandler: function ( event ) {
  7939. var i;
  7940. if ( !event.insideElementPressed ) {
  7941. for ( i = 0; i < _this.buttons.length; i++ ) {
  7942. _this.buttons[ i ].notifyGroupExit();
  7943. }
  7944. }
  7945. },
  7946. releaseHandler: function ( event ) {
  7947. var i;
  7948. if ( !event.insideElementReleased ) {
  7949. for ( i = 0; i < _this.buttons.length; i++ ) {
  7950. _this.buttons[ i ].notifyGroupExit();
  7951. }
  7952. }
  7953. }
  7954. }).setTracking( true );
  7955. };
  7956. $.ButtonGroup.prototype = {
  7957. /**
  7958. * TODO: Figure out why this is used on the public API and if a more useful
  7959. * api can be created.
  7960. * @function
  7961. * @name OpenSeadragon.ButtonGroup.prototype.emulateEnter
  7962. */
  7963. emulateEnter: function() {
  7964. this.tracker.enterHandler( { eventSource: this.tracker } );
  7965. },
  7966. /**
  7967. * TODO: Figure out why this is used on the public API and if a more useful
  7968. * api can be created.
  7969. * @function
  7970. * @name OpenSeadragon.ButtonGroup.prototype.emulateExit
  7971. */
  7972. emulateExit: function() {
  7973. this.tracker.exitHandler( { eventSource: this.tracker } );
  7974. }
  7975. };
  7976. }( OpenSeadragon ));
  7977. /*
  7978. * OpenSeadragon - Rect
  7979. *
  7980. * Copyright (C) 2009 CodePlex Foundation
  7981. * Copyright (C) 2010-2013 OpenSeadragon contributors
  7982. *
  7983. * Redistribution and use in source and binary forms, with or without
  7984. * modification, are permitted provided that the following conditions are
  7985. * met:
  7986. *
  7987. * - Redistributions of source code must retain the above copyright notice,
  7988. * this list of conditions and the following disclaimer.
  7989. *
  7990. * - Redistributions in binary form must reproduce the above copyright
  7991. * notice, this list of conditions and the following disclaimer in the
  7992. * documentation and/or other materials provided with the distribution.
  7993. *
  7994. * - Neither the name of CodePlex Foundation nor the names of its
  7995. * contributors may be used to endorse or promote products derived from
  7996. * this software without specific prior written permission.
  7997. *
  7998. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  7999. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  8000. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  8001. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  8002. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  8003. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  8004. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  8005. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  8006. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  8007. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  8008. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  8009. */
  8010. (function( $ ){
  8011. /**
  8012. * A Rectangle really represents a 2x2 matrix where each row represents a
  8013. * 2 dimensional vector component, the first is (x,y) and the second is
  8014. * (width, height). The latter component implies the equation of a simple
  8015. * plane.
  8016. *
  8017. * @class
  8018. * @param {Number} x The vector component 'x'.
  8019. * @param {Number} y The vector component 'y'.
  8020. * @param {Number} width The vector component 'height'.
  8021. * @param {Number} height The vector component 'width'.
  8022. * @property {Number} x The vector component 'x'.
  8023. * @property {Number} y The vector component 'y'.
  8024. * @property {Number} width The vector component 'width'.
  8025. * @property {Number} height The vector component 'height'.
  8026. */
  8027. $.Rect = function( x, y, width, height ) {
  8028. this.x = typeof ( x ) == "number" ? x : 0;
  8029. this.y = typeof ( y ) == "number" ? y : 0;
  8030. this.width = typeof ( width ) == "number" ? width : 0;
  8031. this.height = typeof ( height ) == "number" ? height : 0;
  8032. };
  8033. $.Rect.prototype = {
  8034. /**
  8035. * The aspect ratio is simply the ratio of width to height.
  8036. * @function
  8037. * @returns {Number} The ratio of width to height.
  8038. */
  8039. getAspectRatio: function() {
  8040. return this.width / this.height;
  8041. },
  8042. /**
  8043. * Provides the coordinates of the upper-left corner of the rectangle as a
  8044. * point.
  8045. * @function
  8046. * @returns {OpenSeadragon.Point} The coordinate of the upper-left corner of
  8047. * the rectangle.
  8048. */
  8049. getTopLeft: function() {
  8050. return new $.Point(
  8051. this.x,
  8052. this.y
  8053. );
  8054. },
  8055. /**
  8056. * Provides the coordinates of the bottom-right corner of the rectangle as a
  8057. * point.
  8058. * @function
  8059. * @returns {OpenSeadragon.Point} The coordinate of the bottom-right corner of
  8060. * the rectangle.
  8061. */
  8062. getBottomRight: function() {
  8063. return new $.Point(
  8064. this.x + this.width,
  8065. this.y + this.height
  8066. );
  8067. },
  8068. /**
  8069. * Provides the coordinates of the top-right corner of the rectangle as a
  8070. * point.
  8071. * @function
  8072. * @returns {OpenSeadragon.Point} The coordinate of the top-right corner of
  8073. * the rectangle.
  8074. */
  8075. getTopRight: function() {
  8076. return new $.Point(
  8077. this.x + this.width,
  8078. this.y
  8079. );
  8080. },
  8081. /**
  8082. * Provides the coordinates of the bottom-left corner of the rectangle as a
  8083. * point.
  8084. * @function
  8085. * @returns {OpenSeadragon.Point} The coordinate of the bottom-left corner of
  8086. * the rectangle.
  8087. */
  8088. getBottomLeft: function() {
  8089. return new $.Point(
  8090. this.x,
  8091. this.y + this.height
  8092. );
  8093. },
  8094. /**
  8095. * Computes the center of the rectangle.
  8096. * @function
  8097. * @returns {OpenSeadragon.Point} The center of the rectangle as represented
  8098. * as represented by a 2-dimensional vector (x,y)
  8099. */
  8100. getCenter: function() {
  8101. return new $.Point(
  8102. this.x + this.width / 2.0,
  8103. this.y + this.height / 2.0
  8104. );
  8105. },
  8106. /**
  8107. * Returns the width and height component as a vector OpenSeadragon.Point
  8108. * @function
  8109. * @returns {OpenSeadragon.Point} The 2 dimensional vector representing the
  8110. * the width and height of the rectangle.
  8111. */
  8112. getSize: function() {
  8113. return new $.Point( this.width, this.height );
  8114. },
  8115. /**
  8116. * Determines if two Rectangles have equivalent components.
  8117. * @function
  8118. * @param {OpenSeadragon.Rect} rectangle The Rectangle to compare to.
  8119. * @return {Boolean} 'true' if all components are equal, otherwise 'false'.
  8120. */
  8121. equals: function( other ) {
  8122. return ( other instanceof $.Rect ) &&
  8123. ( this.x === other.x ) &&
  8124. ( this.y === other.y ) &&
  8125. ( this.width === other.width ) &&
  8126. ( this.height === other.height );
  8127. },
  8128. /**
  8129. * Rotates a rectangle around a point. Currently only 90, 180, and 270
  8130. * degrees are supported.
  8131. * @function
  8132. * @param {Number} degrees The angle in degrees to rotate.
  8133. * @param {OpenSeadragon.Point} pivot The point about which to rotate.
  8134. * Defaults to the center of the rectangle.
  8135. * @return {OpenSeadragon.Rect}
  8136. */
  8137. rotate: function( degrees, pivot ) {
  8138. // TODO support arbitrary rotation
  8139. var width = this.width,
  8140. height = this.height,
  8141. newTopLeft;
  8142. degrees = ( degrees + 360 ) % 360;
  8143. if( degrees % 90 !== 0 ) {
  8144. throw new Error('Currently only 0, 90, 180, and 270 degrees are supported.');
  8145. }
  8146. if( degrees === 0 ){
  8147. return new $.Rect(
  8148. this.x,
  8149. this.y,
  8150. this.width,
  8151. this.height
  8152. );
  8153. }
  8154. pivot = pivot || this.getCenter();
  8155. switch ( degrees ) {
  8156. case 90:
  8157. newTopLeft = this.getBottomLeft();
  8158. width = this.height;
  8159. height = this.width;
  8160. break;
  8161. case 180:
  8162. newTopLeft = this.getBottomRight();
  8163. break;
  8164. case 270:
  8165. newTopLeft = this.getTopRight();
  8166. width = this.height;
  8167. height = this.width;
  8168. break;
  8169. default:
  8170. newTopLeft = this.getTopLeft();
  8171. break;
  8172. }
  8173. newTopLeft = newTopLeft.rotate(degrees, pivot);
  8174. return new $.Rect(newTopLeft.x, newTopLeft.y, width, height);
  8175. },
  8176. /**
  8177. * Provides a string representation of the rectangle which is useful for
  8178. * debugging.
  8179. * @function
  8180. * @returns {String} A string representation of the rectangle.
  8181. */
  8182. toString: function() {
  8183. return "[" +
  8184. Math.round(this.x*100) + "," +
  8185. Math.round(this.y*100) + "," +
  8186. Math.round(this.width*100) + "x" +
  8187. Math.round(this.height*100) +
  8188. "]";
  8189. }
  8190. };
  8191. }( OpenSeadragon ));
  8192. /*
  8193. * OpenSeadragon - ReferenceStrip
  8194. *
  8195. * Copyright (C) 2009 CodePlex Foundation
  8196. * Copyright (C) 2010-2013 OpenSeadragon contributors
  8197. *
  8198. * Redistribution and use in source and binary forms, with or without
  8199. * modification, are permitted provided that the following conditions are
  8200. * met:
  8201. *
  8202. * - Redistributions of source code must retain the above copyright notice,
  8203. * this list of conditions and the following disclaimer.
  8204. *
  8205. * - Redistributions in binary form must reproduce the above copyright
  8206. * notice, this list of conditions and the following disclaimer in the
  8207. * documentation and/or other materials provided with the distribution.
  8208. *
  8209. * - Neither the name of CodePlex Foundation nor the names of its
  8210. * contributors may be used to endorse or promote products derived from
  8211. * this software without specific prior written permission.
  8212. *
  8213. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  8214. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  8215. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  8216. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  8217. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  8218. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  8219. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  8220. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  8221. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  8222. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  8223. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  8224. */
  8225. (function ( $ ) {
  8226. // dictionary from id to private properties
  8227. var THIS = {};
  8228. /**
  8229. * The CollectionDrawer is a reimplementation if the Drawer API that
  8230. * focuses on allowing a viewport to be redefined as a collection
  8231. * of smaller viewports, defined by a clear number of rows and / or
  8232. * columns of which each item in the matrix of viewports has its own
  8233. * source.
  8234. *
  8235. * This idea is a reexpression of the idea of dzi collections
  8236. * which allows a clearer algorithm to reuse the tile sources already
  8237. * supported by OpenSeadragon, in heterogenious or homogenious
  8238. * sequences just like mixed groups already supported by the viewer
  8239. * for the purpose of image sequnces.
  8240. *
  8241. * TODO: The difficult part of this feature is figuring out how to express
  8242. * this functionality as a combination of the functionality already
  8243. * provided by Drawer, Viewport, TileSource, and Navigator. It may
  8244. * require better abstraction at those points in order to effeciently
  8245. * reuse those paradigms.
  8246. */
  8247. $.ReferenceStrip = function ( options ) {
  8248. var _this = this,
  8249. viewer = options.viewer,
  8250. viewerSize = $.getElementSize( viewer.element ),
  8251. element,
  8252. style,
  8253. i;
  8254. //We may need to create a new element and id if they did not
  8255. //provide the id for the existing element
  8256. if ( !options.id ) {
  8257. options.id = 'referencestrip-' + $.now();
  8258. this.element = $.makeNeutralElement( "div" );
  8259. this.element.id = options.id;
  8260. this.element.className = 'referencestrip';
  8261. }
  8262. options = $.extend( true, {
  8263. sizeRatio: $.DEFAULT_SETTINGS.referenceStripSizeRatio,
  8264. position: $.DEFAULT_SETTINGS.referenceStripPosition,
  8265. scroll: $.DEFAULT_SETTINGS.referenceStripScroll,
  8266. clickTimeThreshold: $.DEFAULT_SETTINGS.clickTimeThreshold
  8267. }, options, {
  8268. //required overrides
  8269. element: this.element,
  8270. //These need to be overridden to prevent recursion since
  8271. //the navigator is a viewer and a viewer has a navigator
  8272. showNavigator: false,
  8273. mouseNavEnabled: false,
  8274. showNavigationControl: false,
  8275. showSequenceControl: false
  8276. } );
  8277. $.extend( this, options );
  8278. //Private state properties
  8279. THIS[this.id] = {
  8280. "animating": false
  8281. };
  8282. this.minPixelRatio = this.viewer.minPixelRatio;
  8283. style = this.element.style;
  8284. style.marginTop = '0px';
  8285. style.marginRight = '0px';
  8286. style.marginBottom = '0px';
  8287. style.marginLeft = '0px';
  8288. style.left = '0px';
  8289. style.bottom = '0px';
  8290. style.border = '0px';
  8291. style.background = '#000';
  8292. style.position = 'relative';
  8293. $.setElementOpacity( this.element, 0.8 );
  8294. this.viewer = viewer;
  8295. this.innerTracker = new $.MouseTracker( {
  8296. element: this.element,
  8297. dragHandler: $.delegate( this, onStripDrag ),
  8298. scrollHandler: $.delegate( this, onStripScroll ),
  8299. enterHandler: $.delegate( this, onStripEnter ),
  8300. exitHandler: $.delegate( this, onStripExit ),
  8301. keyHandler: $.delegate( this, onKeyPress )
  8302. } ).setTracking( true );
  8303. //Controls the position and orientation of the reference strip and sets the
  8304. //appropriate width and height
  8305. if ( options.width && options.height ) {
  8306. this.element.style.width = options.width + 'px';
  8307. this.element.style.height = options.height + 'px';
  8308. viewer.addControl(
  8309. this.element,
  8310. { anchor: $.ControlAnchor.BOTTOM_LEFT }
  8311. );
  8312. } else {
  8313. if ( "horizontal" == options.scroll ) {
  8314. this.element.style.width = (
  8315. viewerSize.x *
  8316. options.sizeRatio *
  8317. viewer.tileSources.length
  8318. ) + ( 12 * viewer.tileSources.length ) + 'px';
  8319. this.element.style.height = (
  8320. viewerSize.y *
  8321. options.sizeRatio
  8322. ) + 'px';
  8323. viewer.addControl(
  8324. this.element,
  8325. { anchor: $.ControlAnchor.BOTTOM_LEFT }
  8326. );
  8327. } else {
  8328. this.element.style.height = (
  8329. viewerSize.y *
  8330. options.sizeRatio *
  8331. viewer.tileSources.length
  8332. ) + ( 12 * viewer.tileSources.length ) + 'px';
  8333. this.element.style.width = (
  8334. viewerSize.x *
  8335. options.sizeRatio
  8336. ) + 'px';
  8337. viewer.addControl(
  8338. this.element,
  8339. { anchor: $.ControlAnchor.TOP_LEFT }
  8340. );
  8341. }
  8342. }
  8343. this.panelWidth = ( viewerSize.x * this.sizeRatio ) + 8;
  8344. this.panelHeight = ( viewerSize.y * this.sizeRatio ) + 8;
  8345. this.panels = [];
  8346. /*jshint loopfunc:true*/
  8347. for ( i = 0; i < viewer.tileSources.length; i++ ) {
  8348. element = $.makeNeutralElement( 'div' );
  8349. element.id = this.element.id + "-" + i;
  8350. element.style.width = _this.panelWidth + 'px';
  8351. element.style.height = _this.panelHeight + 'px';
  8352. element.style.display = 'inline';
  8353. element.style.float = 'left'; //Webkit
  8354. element.style.cssFloat = 'left'; //Firefox
  8355. element.style.styleFloat = 'left'; //IE
  8356. element.style.padding = '2px';
  8357. element.innerTracker = new $.MouseTracker( {
  8358. element: element,
  8359. clickTimeThreshold: this.clickTimeThreshold,
  8360. clickDistThreshold: this.clickDistThreshold,
  8361. pressHandler: function ( event ) {
  8362. event.eventSource.dragging = $.now();
  8363. },
  8364. releaseHandler: function ( event ) {
  8365. var tracker = event.eventSource,
  8366. id = tracker.element.id,
  8367. page = Number( id.split( '-' )[2] ),
  8368. now = $.now();
  8369. if ( event.insideElementPressed &&
  8370. event.insideElementReleased &&
  8371. tracker.dragging &&
  8372. ( now - tracker.dragging ) < tracker.clickTimeThreshold ) {
  8373. tracker.dragging = null;
  8374. viewer.goToPage( page );
  8375. }
  8376. }
  8377. } ).setTracking( true );
  8378. this.element.appendChild( element );
  8379. element.activePanel = false;
  8380. this.panels.push( element );
  8381. }
  8382. loadPanels( this, this.scroll == 'vertical' ? viewerSize.y : viewerSize.y, 0 );
  8383. this.setFocus( 0 );
  8384. };
  8385. $.extend( $.ReferenceStrip.prototype, $.EventSource.prototype, $.Viewer.prototype, {
  8386. setFocus: function ( page ) {
  8387. var element = $.getElement( this.element.id + '-' + page ),
  8388. viewerSize = $.getElementSize( this.viewer.canvas ),
  8389. scrollWidth = Number( this.element.style.width.replace( 'px', '' ) ),
  8390. scrollHeight = Number( this.element.style.height.replace( 'px', '' ) ),
  8391. offsetLeft = -Number( this.element.style.marginLeft.replace( 'px', '' ) ),
  8392. offsetTop = -Number( this.element.style.marginTop.replace( 'px', '' ) ),
  8393. offset;
  8394. if ( this.currentSelected !== element ) {
  8395. if ( this.currentSelected ) {
  8396. this.currentSelected.style.background = '#000';
  8397. }
  8398. this.currentSelected = element;
  8399. this.currentSelected.style.background = '#999';
  8400. if ( 'horizontal' == this.scroll ) {
  8401. //right left
  8402. offset = ( Number( page ) ) * ( this.panelWidth + 3 );
  8403. if ( offset > offsetLeft + viewerSize.x - this.panelWidth ) {
  8404. offset = Math.min( offset, ( scrollWidth - viewerSize.x ) );
  8405. this.element.style.marginLeft = -offset + 'px';
  8406. loadPanels( this, viewerSize.x, -offset );
  8407. } else if ( offset < offsetLeft ) {
  8408. offset = Math.max( 0, offset - viewerSize.x / 2 );
  8409. this.element.style.marginLeft = -offset + 'px';
  8410. loadPanels( this, viewerSize.x, -offset );
  8411. }
  8412. } else {
  8413. offset = ( Number( page ) ) * ( this.panelHeight + 3 );
  8414. if ( offset > offsetTop + viewerSize.y - this.panelHeight ) {
  8415. offset = Math.min( offset, ( scrollHeight - viewerSize.y ) );
  8416. this.element.style.marginTop = -offset + 'px';
  8417. loadPanels( this, viewerSize.y, -offset );
  8418. } else if ( offset < offsetTop ) {
  8419. offset = Math.max( 0, offset - viewerSize.y / 2 );
  8420. this.element.style.marginTop = -offset + 'px';
  8421. loadPanels( this, viewerSize.y, -offset );
  8422. }
  8423. }
  8424. this.currentPage = page;
  8425. $.getElement( element.id + '-displayregion' ).focus();
  8426. onStripEnter.call( this, { eventSource: this.innerTracker } );
  8427. }
  8428. },
  8429. /**
  8430. * @function
  8431. * @name OpenSeadragon.ReferenceStrip.prototype.update
  8432. */
  8433. update: function () {
  8434. if ( THIS[this.id].animating ) {
  8435. $.console.log( 'image reference strip update' );
  8436. return true;
  8437. }
  8438. return false;
  8439. }
  8440. } );
  8441. /**
  8442. * @private
  8443. * @inner
  8444. * @function
  8445. */
  8446. function onStripDrag( event ) {
  8447. var offsetLeft = Number( this.element.style.marginLeft.replace( 'px', '' ) ),
  8448. offsetTop = Number( this.element.style.marginTop.replace( 'px', '' ) ),
  8449. scrollWidth = Number( this.element.style.width.replace( 'px', '' ) ),
  8450. scrollHeight = Number( this.element.style.height.replace( 'px', '' ) ),
  8451. viewerSize = $.getElementSize( this.viewer.canvas );
  8452. this.dragging = true;
  8453. if ( this.element ) {
  8454. if ( 'horizontal' == this.scroll ) {
  8455. if ( -event.delta.x > 0 ) {
  8456. //forward
  8457. if ( offsetLeft > -( scrollWidth - viewerSize.x ) ) {
  8458. this.element.style.marginLeft = ( offsetLeft + ( event.delta.x * 2 ) ) + 'px';
  8459. loadPanels( this, viewerSize.x, offsetLeft + ( event.delta.x * 2 ) );
  8460. }
  8461. } else if ( -event.delta.x < 0 ) {
  8462. //reverse
  8463. if ( offsetLeft < 0 ) {
  8464. this.element.style.marginLeft = ( offsetLeft + ( event.delta.x * 2 ) ) + 'px';
  8465. loadPanels( this, viewerSize.x, offsetLeft + ( event.delta.x * 2 ) );
  8466. }
  8467. }
  8468. } else {
  8469. if ( -event.delta.y > 0 ) {
  8470. //forward
  8471. if ( offsetTop > -( scrollHeight - viewerSize.y ) ) {
  8472. this.element.style.marginTop = ( offsetTop + ( event.delta.y * 2 ) ) + 'px';
  8473. loadPanels( this, viewerSize.y, offsetTop + ( event.delta.y * 2 ) );
  8474. }
  8475. } else if ( -event.delta.y < 0 ) {
  8476. //reverse
  8477. if ( offsetTop < 0 ) {
  8478. this.element.style.marginTop = ( offsetTop + ( event.delta.y * 2 ) ) + 'px';
  8479. loadPanels( this, viewerSize.y, offsetTop + ( event.delta.y * 2 ) );
  8480. }
  8481. }
  8482. }
  8483. }
  8484. return false;
  8485. }
  8486. /**
  8487. * @private
  8488. * @inner
  8489. * @function
  8490. */
  8491. function onStripScroll( event ) {
  8492. var offsetLeft = Number( this.element.style.marginLeft.replace( 'px', '' ) ),
  8493. offsetTop = Number( this.element.style.marginTop.replace( 'px', '' ) ),
  8494. scrollWidth = Number( this.element.style.width.replace( 'px', '' ) ),
  8495. scrollHeight = Number( this.element.style.height.replace( 'px', '' ) ),
  8496. viewerSize = $.getElementSize( this.viewer.canvas );
  8497. if ( this.element ) {
  8498. if ( 'horizontal' == this.scroll ) {
  8499. if ( event.scroll > 0 ) {
  8500. //forward
  8501. if ( offsetLeft > -( scrollWidth - viewerSize.x ) ) {
  8502. this.element.style.marginLeft = ( offsetLeft - ( event.scroll * 60 ) ) + 'px';
  8503. loadPanels( this, viewerSize.x, offsetLeft - ( event.scroll * 60 ) );
  8504. }
  8505. } else if ( event.scroll < 0 ) {
  8506. //reverse
  8507. if ( offsetLeft < 0 ) {
  8508. this.element.style.marginLeft = ( offsetLeft - ( event.scroll * 60 ) ) + 'px';
  8509. loadPanels( this, viewerSize.x, offsetLeft - ( event.scroll * 60 ) );
  8510. }
  8511. }
  8512. } else {
  8513. if ( event.scroll < 0 ) {
  8514. //scroll up
  8515. if ( offsetTop > viewerSize.y - scrollHeight ) {
  8516. this.element.style.marginTop = ( offsetTop + ( event.scroll * 60 ) ) + 'px';
  8517. loadPanels( this, viewerSize.y, offsetTop + ( event.scroll * 60 ) );
  8518. }
  8519. } else if ( event.scroll > 0 ) {
  8520. //scroll dowm
  8521. if ( offsetTop < 0 ) {
  8522. this.element.style.marginTop = ( offsetTop + ( event.scroll * 60 ) ) + 'px';
  8523. loadPanels( this, viewerSize.y, offsetTop + ( event.scroll * 60 ) );
  8524. }
  8525. }
  8526. }
  8527. }
  8528. //cancels event
  8529. return false;
  8530. }
  8531. function loadPanels( strip, viewerSize, scroll ) {
  8532. var panelSize,
  8533. activePanelsStart,
  8534. activePanelsEnd,
  8535. miniViewer,
  8536. style,
  8537. i,
  8538. element;
  8539. if ( 'horizontal' == strip.scroll ) {
  8540. panelSize = strip.panelWidth;
  8541. } else {
  8542. panelSize = strip.panelHeight;
  8543. }
  8544. activePanelsStart = Math.ceil( viewerSize / panelSize ) + 5;
  8545. activePanelsEnd = Math.ceil( ( Math.abs( scroll ) + viewerSize ) / panelSize ) + 1;
  8546. activePanelsStart = activePanelsEnd - activePanelsStart;
  8547. activePanelsStart = activePanelsStart < 0 ? 0 : activePanelsStart;
  8548. for ( i = activePanelsStart; i < activePanelsEnd && i < strip.panels.length; i++ ) {
  8549. element = strip.panels[i];
  8550. if ( !element.activePanel ) {
  8551. miniViewer = new $.Viewer( {
  8552. id: element.id,
  8553. tileSources: [strip.viewer.tileSources[i]],
  8554. element: element,
  8555. navigatorSizeRatio: strip.sizeRatio,
  8556. showNavigator: false,
  8557. mouseNavEnabled: false,
  8558. showNavigationControl: false,
  8559. showSequenceControl: false,
  8560. immediateRender: true,
  8561. blendTime: 0,
  8562. animationTime: 0
  8563. } );
  8564. miniViewer.displayRegion = $.makeNeutralElement( "textarea" );
  8565. miniViewer.displayRegion.id = element.id + '-displayregion';
  8566. miniViewer.displayRegion.className = 'displayregion';
  8567. style = miniViewer.displayRegion.style;
  8568. style.position = 'relative';
  8569. style.top = '0px';
  8570. style.left = '0px';
  8571. style.fontSize = '0px';
  8572. style.overflow = 'hidden';
  8573. style.float = 'left'; //Webkit
  8574. style.cssFloat = 'left'; //Firefox
  8575. style.styleFloat = 'left'; //IE
  8576. style.zIndex = 999999999;
  8577. style.cursor = 'default';
  8578. style.width = ( strip.panelWidth - 4 ) + 'px';
  8579. style.height = ( strip.panelHeight - 4 ) + 'px';
  8580. miniViewer.displayRegion.innerTracker = new $.MouseTracker( {
  8581. element: miniViewer.displayRegion
  8582. } );
  8583. element.getElementsByTagName( 'form' )[0].appendChild(
  8584. miniViewer.displayRegion
  8585. );
  8586. element.activePanel = true;
  8587. }
  8588. }
  8589. }
  8590. /**
  8591. * @private
  8592. * @inner
  8593. * @function
  8594. */
  8595. function onStripEnter( event ) {
  8596. var element = event.eventSource.element;
  8597. //$.setElementOpacity(element, 0.8);
  8598. //element.style.border = '1px solid #555';
  8599. //element.style.background = '#000';
  8600. if ( 'horizontal' == this.scroll ) {
  8601. //element.style.paddingTop = "0px";
  8602. element.style.marginBottom = "0px";
  8603. } else {
  8604. //element.style.paddingRight = "0px";
  8605. element.style.marginLeft = "0px";
  8606. }
  8607. return false;
  8608. }
  8609. /**
  8610. * @private
  8611. * @inner
  8612. * @function
  8613. */
  8614. function onStripExit( event ) {
  8615. var element = event.eventSource.element;
  8616. if ( 'horizontal' == this.scroll ) {
  8617. //element.style.paddingTop = "10px";
  8618. element.style.marginBottom = "-" + ( $.getElementSize( element ).y / 2 ) + "px";
  8619. } else {
  8620. //element.style.paddingRight = "10px";
  8621. element.style.marginLeft = "-" + ( $.getElementSize( element ).x / 2 ) + "px";
  8622. }
  8623. return false;
  8624. }
  8625. /**
  8626. * @private
  8627. * @inner
  8628. * @function
  8629. */
  8630. function onKeyPress( event ) {
  8631. //console.log( event.keyCode );
  8632. switch ( event.keyCode ) {
  8633. case 61: //=|+
  8634. onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
  8635. return false;
  8636. case 45: //-|_
  8637. onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
  8638. return false;
  8639. case 48: //0|)
  8640. case 119: //w
  8641. case 87: //W
  8642. case 38: //up arrow
  8643. onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
  8644. return false;
  8645. case 115: //s
  8646. case 83: //S
  8647. case 40: //down arrow
  8648. onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
  8649. return false;
  8650. case 97: //a
  8651. case 37: //left arrow
  8652. onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
  8653. return false;
  8654. case 100: //d
  8655. case 39: //right arrow
  8656. onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
  8657. return false;
  8658. default:
  8659. //console.log( 'navigator keycode %s', event.keyCode );
  8660. return true;
  8661. }
  8662. }
  8663. } ( OpenSeadragon ) );
  8664. /*
  8665. * OpenSeadragon - DisplayRect
  8666. *
  8667. * Copyright (C) 2009 CodePlex Foundation
  8668. * Copyright (C) 2010-2013 OpenSeadragon contributors
  8669. *
  8670. * Redistribution and use in source and binary forms, with or without
  8671. * modification, are permitted provided that the following conditions are
  8672. * met:
  8673. *
  8674. * - Redistributions of source code must retain the above copyright notice,
  8675. * this list of conditions and the following disclaimer.
  8676. *
  8677. * - Redistributions in binary form must reproduce the above copyright
  8678. * notice, this list of conditions and the following disclaimer in the
  8679. * documentation and/or other materials provided with the distribution.
  8680. *
  8681. * - Neither the name of CodePlex Foundation nor the names of its
  8682. * contributors may be used to endorse or promote products derived from
  8683. * this software without specific prior written permission.
  8684. *
  8685. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  8686. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  8687. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  8688. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  8689. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  8690. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  8691. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  8692. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  8693. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  8694. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  8695. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  8696. */
  8697. (function( $ ){
  8698. /**
  8699. * A display rectanlge is very similar to the OpenSeadragon.Rect but adds two
  8700. * fields, 'minLevel' and 'maxLevel' which denote the supported zoom levels
  8701. * for this rectangle.
  8702. * @class
  8703. * @extends OpenSeadragon.Rect
  8704. * @param {Number} x The vector component 'x'.
  8705. * @param {Number} y The vector component 'y'.
  8706. * @param {Number} width The vector component 'height'.
  8707. * @param {Number} height The vector component 'width'.
  8708. * @param {Number} minLevel The lowest zoom level supported.
  8709. * @param {Number} maxLevel The highest zoom level supported.
  8710. * @property {Number} minLevel The lowest zoom level supported.
  8711. * @property {Number} maxLevel The highest zoom level supported.
  8712. */
  8713. $.DisplayRect = function( x, y, width, height, minLevel, maxLevel ) {
  8714. $.Rect.apply( this, [ x, y, width, height ] );
  8715. this.minLevel = minLevel;
  8716. this.maxLevel = maxLevel;
  8717. };
  8718. $.extend( $.DisplayRect.prototype, $.Rect.prototype );
  8719. }( OpenSeadragon ));
  8720. /*
  8721. * OpenSeadragon - Spring
  8722. *
  8723. * Copyright (C) 2009 CodePlex Foundation
  8724. * Copyright (C) 2010-2013 OpenSeadragon contributors
  8725. *
  8726. * Redistribution and use in source and binary forms, with or without
  8727. * modification, are permitted provided that the following conditions are
  8728. * met:
  8729. *
  8730. * - Redistributions of source code must retain the above copyright notice,
  8731. * this list of conditions and the following disclaimer.
  8732. *
  8733. * - Redistributions in binary form must reproduce the above copyright
  8734. * notice, this list of conditions and the following disclaimer in the
  8735. * documentation and/or other materials provided with the distribution.
  8736. *
  8737. * - Neither the name of CodePlex Foundation nor the names of its
  8738. * contributors may be used to endorse or promote products derived from
  8739. * this software without specific prior written permission.
  8740. *
  8741. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  8742. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  8743. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  8744. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  8745. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  8746. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  8747. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  8748. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  8749. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  8750. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  8751. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  8752. */
  8753. (function( $ ){
  8754. /**
  8755. * @class
  8756. * @param {Object} options - Spring configuration settings.
  8757. * @param {Number} options.initial - Initial value of spring, default to 0 so
  8758. * spring is not in motion initally by default.
  8759. * @param {Number} options.springStiffness - Spring stiffness.
  8760. * @param {Number} options.animationTime - Animation duration per spring.
  8761. *
  8762. * @property {Number} initial - Initial value of spring, default to 0 so
  8763. * spring is not in motion initally by default.
  8764. * @property {Number} springStiffness - Spring stiffness.
  8765. * @property {Number} animationTime - Animation duration per spring.
  8766. * @property {Object} current
  8767. * @property {Number} start
  8768. * @property {Number} target
  8769. */
  8770. $.Spring = function( options ) {
  8771. var args = arguments;
  8772. if( typeof( options ) != 'object' ){
  8773. //allows backward compatible use of ( initialValue, config ) as
  8774. //constructor parameters
  8775. options = {
  8776. initial: args.length && typeof ( args[ 0 ] ) == "number" ?
  8777. args[ 0 ] :
  8778. 0,
  8779. springStiffness: args.length > 1 ?
  8780. args[ 1 ].springStiffness :
  8781. 5.0,
  8782. animationTime: args.length > 1 ?
  8783. args[ 1 ].animationTime :
  8784. 1.5
  8785. };
  8786. }
  8787. $.extend( true, this, options);
  8788. this.current = {
  8789. value: typeof ( this.initial ) == "number" ?
  8790. this.initial :
  8791. 0,
  8792. time: $.now() // always work in milliseconds
  8793. };
  8794. this.start = {
  8795. value: this.current.value,
  8796. time: this.current.time
  8797. };
  8798. this.target = {
  8799. value: this.current.value,
  8800. time: this.current.time
  8801. };
  8802. };
  8803. $.Spring.prototype = {
  8804. /**
  8805. * @function
  8806. * @param {Number} target
  8807. */
  8808. resetTo: function( target ) {
  8809. this.target.value = target;
  8810. this.target.time = this.current.time;
  8811. this.start.value = this.target.value;
  8812. this.start.time = this.target.time;
  8813. },
  8814. /**
  8815. * @function
  8816. * @param {Number} target
  8817. */
  8818. springTo: function( target ) {
  8819. this.start.value = this.current.value;
  8820. this.start.time = this.current.time;
  8821. this.target.value = target;
  8822. this.target.time = this.start.time + 1000 * this.animationTime;
  8823. },
  8824. /**
  8825. * @function
  8826. * @param {Number} delta
  8827. */
  8828. shiftBy: function( delta ) {
  8829. this.start.value += delta;
  8830. this.target.value += delta;
  8831. },
  8832. /**
  8833. * @function
  8834. */
  8835. update: function() {
  8836. this.current.time = $.now();
  8837. this.current.value = (this.current.time >= this.target.time) ?
  8838. this.target.value :
  8839. this.start.value +
  8840. ( this.target.value - this.start.value ) *
  8841. transform(
  8842. this.springStiffness,
  8843. ( this.current.time - this.start.time ) /
  8844. ( this.target.time - this.start.time )
  8845. );
  8846. }
  8847. };
  8848. /**
  8849. * @private
  8850. */
  8851. function transform( stiffness, x ) {
  8852. return ( 1.0 - Math.exp( stiffness * -x ) ) /
  8853. ( 1.0 - Math.exp( -stiffness ) );
  8854. }
  8855. }( OpenSeadragon ));
  8856. /*
  8857. * OpenSeadragon - Tile
  8858. *
  8859. * Copyright (C) 2009 CodePlex Foundation
  8860. * Copyright (C) 2010-2013 OpenSeadragon contributors
  8861. *
  8862. * Redistribution and use in source and binary forms, with or without
  8863. * modification, are permitted provided that the following conditions are
  8864. * met:
  8865. *
  8866. * - Redistributions of source code must retain the above copyright notice,
  8867. * this list of conditions and the following disclaimer.
  8868. *
  8869. * - Redistributions in binary form must reproduce the above copyright
  8870. * notice, this list of conditions and the following disclaimer in the
  8871. * documentation and/or other materials provided with the distribution.
  8872. *
  8873. * - Neither the name of CodePlex Foundation nor the names of its
  8874. * contributors may be used to endorse or promote products derived from
  8875. * this software without specific prior written permission.
  8876. *
  8877. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  8878. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  8879. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  8880. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  8881. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  8882. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  8883. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  8884. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  8885. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  8886. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  8887. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  8888. */
  8889. (function( $ ){
  8890. var TILE_CACHE = {};
  8891. /**
  8892. * @class
  8893. * @param {Number} level The zoom level this tile belongs to.
  8894. * @param {Number} x The vector component 'x'.
  8895. * @param {Number} y The vector component 'y'.
  8896. * @param {OpenSeadragon.Point} bounds Where this tile fits, in normalized
  8897. * coordinates.
  8898. * @param {Boolean} exists Is this tile a part of a sparse image? ( Also has
  8899. * this tile failed to load? )
  8900. * @param {String} url The URL of this tile's image.
  8901. *
  8902. * @property {Number} level The zoom level this tile belongs to.
  8903. * @property {Number} x The vector component 'x'.
  8904. * @property {Number} y The vector component 'y'.
  8905. * @property {OpenSeadragon.Point} bounds Where this tile fits, in normalized
  8906. * coordinates
  8907. * @property {Boolean} exists Is this tile a part of a sparse image? ( Also has
  8908. * this tile failed to load?
  8909. * @property {String} url The URL of this tile's image.
  8910. * @property {Boolean} loaded Is this tile loaded?
  8911. * @property {Boolean} loading Is this tile loading?
  8912. * @property {Element} element The HTML div element for this tile
  8913. * @property {Element} imgElement The HTML img element for this tile
  8914. * @property {Image} image The Image object for this tile
  8915. * @property {String} style The alias of this.element.style.
  8916. * @property {String} position This tile's position on screen, in pixels.
  8917. * @property {String} size This tile's size on screen, in pixels
  8918. * @property {String} blendStart The start time of this tile's blending
  8919. * @property {String} opacity The current opacity this tile should be.
  8920. * @property {String} distance The distance of this tile to the viewport center
  8921. * @property {String} visibility The visibility score of this tile.
  8922. * @property {Boolean} beingDrawn Whether this tile is currently being drawn
  8923. * @property {Number} lastTouchTime Timestamp the tile was last touched.
  8924. */
  8925. $.Tile = function(level, x, y, bounds, exists, url) {
  8926. this.level = level;
  8927. this.x = x;
  8928. this.y = y;
  8929. this.bounds = bounds;
  8930. this.exists = exists;
  8931. this.url = url;
  8932. this.loaded = false;
  8933. this.loading = false;
  8934. this.element = null;
  8935. this.imgElement = null;
  8936. this.image = null;
  8937. this.style = null;
  8938. this.position = null;
  8939. this.size = null;
  8940. this.blendStart = null;
  8941. this.opacity = null;
  8942. this.distance = null;
  8943. this.visibility = null;
  8944. this.beingDrawn = false;
  8945. this.lastTouchTime = 0;
  8946. };
  8947. $.Tile.prototype = {
  8948. /**
  8949. * Provides a string representation of this tiles level and (x,y)
  8950. * components.
  8951. * @function
  8952. * @returns {String}
  8953. */
  8954. toString: function() {
  8955. return this.level + "/" + this.x + "_" + this.y;
  8956. },
  8957. /**
  8958. * Renders the tile in an html container.
  8959. * @function
  8960. * @param {Element} container
  8961. */
  8962. drawHTML: function( container ) {
  8963. if ( !this.loaded || !this.image ) {
  8964. $.console.warn(
  8965. "Attempting to draw tile %s when it's not yet loaded.",
  8966. this.toString()
  8967. );
  8968. return;
  8969. }
  8970. //EXPERIMENTAL - trying to figure out how to scale the container
  8971. // content during animation of the container size.
  8972. if ( !this.element ) {
  8973. this.element = $.makeNeutralElement( "div" );
  8974. this.imgElement = $.makeNeutralElement( "img" );
  8975. this.imgElement.src = this.url;
  8976. this.imgElement.style.msInterpolationMode = "nearest-neighbor";
  8977. this.imgElement.style.width = "100%";
  8978. this.imgElement.style.height = "100%";
  8979. this.style = this.element.style;
  8980. this.style.position = "absolute";
  8981. }
  8982. if ( this.element.parentNode != container ) {
  8983. container.appendChild( this.element );
  8984. }
  8985. if ( this.imgElement.parentNode != this.element ) {
  8986. this.element.appendChild( this.imgElement );
  8987. }
  8988. this.style.top = this.position.y + "px";
  8989. this.style.left = this.position.x + "px";
  8990. this.style.height = this.size.y + "px";
  8991. this.style.width = this.size.x + "px";
  8992. $.setElementOpacity( this.element, this.opacity );
  8993. },
  8994. /**
  8995. * Renders the tile in a canvas-based context.
  8996. * @function
  8997. * @param {Canvas} context
  8998. */
  8999. drawCanvas: function( context ) {
  9000. var position = this.position,
  9001. size = this.size,
  9002. rendered,
  9003. canvas;
  9004. if ( !this.loaded || !( this.image || TILE_CACHE[ this.url ] ) ){
  9005. $.console.warn(
  9006. "Attempting to draw tile %s when it's not yet loaded.",
  9007. this.toString()
  9008. );
  9009. return;
  9010. }
  9011. context.globalAlpha = this.opacity;
  9012. //context.save();
  9013. //if we are supposed to be rendering fully opaque rectangle,
  9014. //ie its done fading or fading is turned off, and if we are drawing
  9015. //an image with an alpha channel, then the only way
  9016. //to avoid seeing the tile underneath is to clear the rectangle
  9017. if( context.globalAlpha == 1 && this.url.match('.png') ){
  9018. //clearing only the inside of the rectangle occupied
  9019. //by the png prevents edge flikering
  9020. context.clearRect(
  9021. position.x+1,
  9022. position.y+1,
  9023. size.x-2,
  9024. size.y-2
  9025. );
  9026. }
  9027. if( !TILE_CACHE[ this.url ] ){
  9028. canvas = document.createElement( 'canvas' );
  9029. canvas.width = this.image.width;
  9030. canvas.height = this.image.height;
  9031. rendered = canvas.getContext('2d');
  9032. rendered.drawImage( this.image, 0, 0 );
  9033. TILE_CACHE[ this.url ] = rendered;
  9034. //since we are caching the prerendered image on a canvas
  9035. //allow the image to not be held in memory
  9036. this.image = null;
  9037. }
  9038. rendered = TILE_CACHE[ this.url ];
  9039. //rendered.save();
  9040. context.drawImage(
  9041. rendered.canvas,
  9042. 0,
  9043. 0,
  9044. rendered.canvas.width,
  9045. rendered.canvas.height,
  9046. position.x,
  9047. position.y,
  9048. size.x,
  9049. size.y
  9050. );
  9051. //rendered.restore();
  9052. //context.restore();
  9053. },
  9054. /**
  9055. * Removes tile from it's contianer.
  9056. * @function
  9057. */
  9058. unload: function() {
  9059. if ( this.imgElement && this.imgElement.parentNode ) {
  9060. this.imgElement.parentNode.removeChild( this.imgElement );
  9061. }
  9062. if ( this.element && this.element.parentNode ) {
  9063. this.element.parentNode.removeChild( this.element );
  9064. }
  9065. if ( TILE_CACHE[ this.url ]){
  9066. delete TILE_CACHE[ this.url ];
  9067. }
  9068. this.element = null;
  9069. this.imgElement = null;
  9070. this.image = null;
  9071. this.loaded = false;
  9072. this.loading = false;
  9073. }
  9074. };
  9075. }( OpenSeadragon ));
  9076. /*
  9077. * OpenSeadragon - Overlay
  9078. *
  9079. * Copyright (C) 2009 CodePlex Foundation
  9080. * Copyright (C) 2010-2013 OpenSeadragon contributors
  9081. *
  9082. * Redistribution and use in source and binary forms, with or without
  9083. * modification, are permitted provided that the following conditions are
  9084. * met:
  9085. *
  9086. * - Redistributions of source code must retain the above copyright notice,
  9087. * this list of conditions and the following disclaimer.
  9088. *
  9089. * - Redistributions in binary form must reproduce the above copyright
  9090. * notice, this list of conditions and the following disclaimer in the
  9091. * documentation and/or other materials provided with the distribution.
  9092. *
  9093. * - Neither the name of CodePlex Foundation nor the names of its
  9094. * contributors may be used to endorse or promote products derived from
  9095. * this software without specific prior written permission.
  9096. *
  9097. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  9098. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  9099. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  9100. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  9101. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  9102. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  9103. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  9104. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  9105. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  9106. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  9107. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  9108. */
  9109. (function( $ ){
  9110. /**
  9111. * An enumeration of positions that an overlay may be assigned relative
  9112. * to the viewport including CENTER, TOP_LEFT (default), TOP, TOP_RIGHT,
  9113. * RIGHT, BOTTOM_RIGHT, BOTTOM, BOTTOM_LEFT, and LEFT.
  9114. * @static
  9115. */
  9116. $.OverlayPlacement = {
  9117. CENTER: 0,
  9118. TOP_LEFT: 1,
  9119. TOP: 2,
  9120. TOP_RIGHT: 3,
  9121. RIGHT: 4,
  9122. BOTTOM_RIGHT: 5,
  9123. BOTTOM: 6,
  9124. BOTTOM_LEFT: 7,
  9125. LEFT: 8
  9126. };
  9127. /**
  9128. * An Overlay provides a
  9129. * @class
  9130. */
  9131. $.Overlay = function( element, location, placement ) {
  9132. var options;
  9133. if( $.isPlainObject( element ) ){
  9134. options = element;
  9135. } else{
  9136. options = {
  9137. element: element,
  9138. location: location,
  9139. placement: placement
  9140. };
  9141. }
  9142. this.element = options.element;
  9143. this.scales = options.location instanceof $.Rect;
  9144. this.bounds = new $.Rect(
  9145. options.location.x,
  9146. options.location.y,
  9147. options.location.width,
  9148. options.location.height
  9149. );
  9150. this.position = new $.Point(
  9151. options.location.x,
  9152. options.location.y
  9153. );
  9154. this.size = new $.Point(
  9155. options.location.width,
  9156. options.location.height
  9157. );
  9158. this.style = options.element.style;
  9159. // rects are always top-left
  9160. this.placement = options.location instanceof $.Point ?
  9161. options.placement :
  9162. $.OverlayPlacement.TOP_LEFT;
  9163. this.onDraw = options.onDraw;
  9164. };
  9165. $.Overlay.prototype = {
  9166. /**
  9167. * @function
  9168. * @param {OpenSeadragon.OverlayPlacement} position
  9169. * @param {OpenSeadragon.Point} size
  9170. */
  9171. adjust: function( position, size ) {
  9172. switch ( this.placement ) {
  9173. case $.OverlayPlacement.TOP_LEFT:
  9174. break;
  9175. case $.OverlayPlacement.TOP:
  9176. position.x -= size.x / 2;
  9177. break;
  9178. case $.OverlayPlacement.TOP_RIGHT:
  9179. position.x -= size.x;
  9180. break;
  9181. case $.OverlayPlacement.RIGHT:
  9182. position.x -= size.x;
  9183. position.y -= size.y / 2;
  9184. break;
  9185. case $.OverlayPlacement.BOTTOM_RIGHT:
  9186. position.x -= size.x;
  9187. position.y -= size.y;
  9188. break;
  9189. case $.OverlayPlacement.BOTTOM:
  9190. position.x -= size.x / 2;
  9191. position.y -= size.y;
  9192. break;
  9193. case $.OverlayPlacement.BOTTOM_LEFT:
  9194. position.y -= size.y;
  9195. break;
  9196. case $.OverlayPlacement.LEFT:
  9197. position.y -= size.y / 2;
  9198. break;
  9199. default:
  9200. case $.OverlayPlacement.CENTER:
  9201. position.x -= size.x / 2;
  9202. position.y -= size.y / 2;
  9203. break;
  9204. }
  9205. },
  9206. /**
  9207. * @function
  9208. */
  9209. destroy: function() {
  9210. var element = this.element,
  9211. style = this.style;
  9212. if ( element.parentNode ) {
  9213. element.parentNode.removeChild( element );
  9214. //this should allow us to preserve overlays when required between
  9215. //pages
  9216. if( element.prevElementParent ){
  9217. style.display = 'none';
  9218. //element.prevElementParent.insertBefore(
  9219. // element,
  9220. // element.prevNextSibling
  9221. //);
  9222. document.body.appendChild( element );
  9223. }
  9224. }
  9225. // clear the onDraw callback
  9226. this.onDraw = null;
  9227. style.top = "";
  9228. style.left = "";
  9229. style.position = "";
  9230. if ( this.scales ) {
  9231. style.width = "";
  9232. style.height = "";
  9233. }
  9234. },
  9235. /**
  9236. * @function
  9237. * @param {Element} container
  9238. */
  9239. drawHTML: function( container, viewport ) {
  9240. var element = this.element,
  9241. style = this.style,
  9242. scales = this.scales,
  9243. drawerCenter = new $.Point(
  9244. viewport.viewer.drawer.canvas.width / 2,
  9245. viewport.viewer.drawer.canvas.height / 2
  9246. ),
  9247. degrees = viewport.degrees,
  9248. position,
  9249. size,
  9250. overlayCenter;
  9251. if ( element.parentNode != container ) {
  9252. //save the source parent for later if we need it
  9253. element.prevElementParent = element.parentNode;
  9254. element.prevNextSibling = element.nextSibling;
  9255. container.appendChild( element );
  9256. }
  9257. if ( !scales ) {
  9258. this.size = $.getElementSize( element );
  9259. }
  9260. position = this.position;
  9261. size = this.size;
  9262. this.adjust( position, size );
  9263. position = position.apply( Math.floor );
  9264. size = size.apply( Math.ceil );
  9265. // rotate the position of the overlay
  9266. // TODO only rotate overlays if in canvas mode
  9267. // TODO replace the size rotation with CSS3 transforms
  9268. // TODO add an option to overlays to not rotate with the image
  9269. // Currently only rotates position and size
  9270. if( degrees !== 0 && this.scales ) {
  9271. overlayCenter = new $.Point( size.x / 2, size.y / 2 );
  9272. position = position.plus( overlayCenter ).rotate(
  9273. degrees,
  9274. drawerCenter
  9275. ).minus( overlayCenter );
  9276. size = size.rotate( degrees, new $.Point( 0, 0 ) );
  9277. size = new $.Point( Math.abs( size.x ), Math.abs( size.y ) );
  9278. }
  9279. // call the onDraw callback if there is one to allow, this allows someone to overwrite
  9280. // the drawing/positioning/sizing of the overlay
  9281. if (this.onDraw) {
  9282. this.onDraw(position, size, element);
  9283. } else {
  9284. style.left = position.x + "px";
  9285. style.top = position.y + "px";
  9286. style.position = "absolute";
  9287. style.display = 'block';
  9288. if ( scales ) {
  9289. style.width = size.x + "px";
  9290. style.height = size.y + "px";
  9291. }
  9292. }
  9293. },
  9294. /**
  9295. * @function
  9296. * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location
  9297. * @param {OpenSeadragon.OverlayPlacement} position
  9298. */
  9299. update: function( location, placement ) {
  9300. this.scales = location instanceof $.Rect;
  9301. this.bounds = new $.Rect(
  9302. location.x,
  9303. location.y,
  9304. location.width,
  9305. location.height
  9306. );
  9307. // rects are always top-left
  9308. this.placement = location instanceof $.Point ?
  9309. placement :
  9310. $.OverlayPlacement.TOP_LEFT;
  9311. }
  9312. };
  9313. }( OpenSeadragon ));
  9314. /*
  9315. * OpenSeadragon - Drawer
  9316. *
  9317. * Copyright (C) 2009 CodePlex Foundation
  9318. * Copyright (C) 2010-2013 OpenSeadragon contributors
  9319. *
  9320. * Redistribution and use in source and binary forms, with or without
  9321. * modification, are permitted provided that the following conditions are
  9322. * met:
  9323. *
  9324. * - Redistributions of source code must retain the above copyright notice,
  9325. * this list of conditions and the following disclaimer.
  9326. *
  9327. * - Redistributions in binary form must reproduce the above copyright
  9328. * notice, this list of conditions and the following disclaimer in the
  9329. * documentation and/or other materials provided with the distribution.
  9330. *
  9331. * - Neither the name of CodePlex Foundation nor the names of its
  9332. * contributors may be used to endorse or promote products derived from
  9333. * this software without specific prior written permission.
  9334. *
  9335. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  9336. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  9337. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  9338. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  9339. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  9340. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  9341. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  9342. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  9343. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  9344. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  9345. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  9346. */
  9347. (function( $ ){
  9348. var DEVICE_SCREEN = $.getWindowSize(),
  9349. BROWSER = $.Browser.vendor,
  9350. BROWSER_VERSION = $.Browser.version,
  9351. SUBPIXEL_RENDERING = (
  9352. ( BROWSER == $.BROWSERS.FIREFOX ) ||
  9353. ( BROWSER == $.BROWSERS.OPERA ) ||
  9354. ( BROWSER == $.BROWSERS.SAFARI && BROWSER_VERSION >= 4 ) ||
  9355. ( BROWSER == $.BROWSERS.CHROME && BROWSER_VERSION >= 2 ) ||
  9356. ( BROWSER == $.BROWSERS.IE && BROWSER_VERSION >= 9 )
  9357. ),
  9358. USE_CANVAS = SUBPIXEL_RENDERING &&
  9359. !( DEVICE_SCREEN.x <= 400 || DEVICE_SCREEN.y <= 400 ) &&
  9360. !( navigator.appVersion.match( 'Mobile' ) ) &&
  9361. $.isFunction( document.createElement( "canvas" ).getContext );
  9362. //console.error( 'USE_CANVAS ' + USE_CANVAS );
  9363. /**
  9364. * @class
  9365. * @param {OpenSeadragon.TileSource} source - Reference to Viewer tile source.
  9366. * @param {OpenSeadragon.Viewport} viewport - Reference to Viewer viewport.
  9367. * @param {Element} element - Reference to Viewer 'canvas'.
  9368. * @property {OpenSeadragon.TileSource} source - Reference to Viewer tile source.
  9369. * @property {OpenSeadragon.Viewport} viewport - Reference to Viewer viewport.
  9370. * @property {Element} container - Reference to Viewer 'canvas'.
  9371. * @property {Element|Canvas} canvas - TODO
  9372. * @property {CanvasContext} context - TODO
  9373. * @property {Object} config - Reference to Viewer config.
  9374. * @property {Number} downloading - How many images are currently being loaded in parallel.
  9375. * @property {Number} normHeight - Ratio of zoomable image height to width.
  9376. * @property {Object} tilesMatrix - A '3d' dictionary [level][x][y] --> Tile.
  9377. * @property {Array} tilesLoaded - An unordered list of Tiles with loaded images.
  9378. * @property {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean.
  9379. * @property {Array} overlays - An unordered list of Overlays added.
  9380. * @property {Array} lastDrawn - An unordered list of Tiles drawn last frame.
  9381. * @property {Number} lastResetTime - Last time for which the drawer was reset.
  9382. * @property {Boolean} midUpdate - Is the drawer currently updating the viewport?
  9383. * @property {Boolean} updateAgain - Does the drawer need to update the viewort again?
  9384. * @property {Element} element - DEPRECATED Alias for container.
  9385. */
  9386. $.Drawer = function( options ) {
  9387. //backward compatibility for positional args while prefering more
  9388. //idiomatic javascript options object as the only argument
  9389. var args = arguments,
  9390. i;
  9391. if( !$.isPlainObject( options ) ){
  9392. options = {
  9393. source: args[ 0 ],
  9394. viewport: args[ 1 ],
  9395. element: args[ 2 ]
  9396. };
  9397. }
  9398. $.extend( true, this, {
  9399. //internal state properties
  9400. viewer: null,
  9401. downloading: 0,
  9402. tilesMatrix: {},
  9403. tilesLoaded: [],
  9404. coverage: {},
  9405. lastDrawn: [],
  9406. lastResetTime: 0,
  9407. midUpdate: false,
  9408. updateAgain: true,
  9409. //internal state / configurable settings
  9410. overlays: [],
  9411. collectionOverlays: {},
  9412. //configurable settings
  9413. maxImageCacheCount: $.DEFAULT_SETTINGS.maxImageCacheCount,
  9414. imageLoaderLimit: $.DEFAULT_SETTINGS.imageLoaderLimit,
  9415. minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio,
  9416. wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal,
  9417. wrapVertical: $.DEFAULT_SETTINGS.wrapVertical,
  9418. immediateRender: $.DEFAULT_SETTINGS.immediateRender,
  9419. blendTime: $.DEFAULT_SETTINGS.blendTime,
  9420. alwaysBlend: $.DEFAULT_SETTINGS.alwaysBlend,
  9421. minPixelRatio: $.DEFAULT_SETTINGS.minPixelRatio,
  9422. debugMode: $.DEFAULT_SETTINGS.debugMode,
  9423. timeout: $.DEFAULT_SETTINGS.timeout
  9424. }, options );
  9425. this.container = $.getElement( this.element );
  9426. this.canvas = $.makeNeutralElement( USE_CANVAS ? "canvas" : "div" );
  9427. this.context = USE_CANVAS ? this.canvas.getContext( "2d" ) : null;
  9428. this.normHeight = this.source.dimensions.y / this.source.dimensions.x;
  9429. this.element = this.container;
  9430. // We force our container to ltr because our drawing math doesn't work in rtl.
  9431. // This issue only affects our canvas renderer, but we do it always for consistency.
  9432. // Note that this means overlays you want to be rtl need to be explicitly set to rtl.
  9433. this.container.dir = 'ltr';
  9434. this.canvas.style.width = "100%";
  9435. this.canvas.style.height = "100%";
  9436. this.canvas.style.position = "absolute";
  9437. // explicit left-align
  9438. this.container.style.textAlign = "left";
  9439. this.container.appendChild( this.canvas );
  9440. //create the correct type of overlay by convention if the overlays
  9441. //are not already OpenSeadragon.Overlays
  9442. for( i = 0; i < this.overlays.length; i++ ){
  9443. if( $.isPlainObject( this.overlays[ i ] ) ){
  9444. this.overlays[ i ] = addOverlayFromConfiguration( this, this.overlays[ i ]);
  9445. } else if ( $.isFunction( this.overlays[ i ] ) ){
  9446. //TODO
  9447. }
  9448. }
  9449. //this.profiler = new $.Profiler();
  9450. };
  9451. $.Drawer.prototype = {
  9452. /**
  9453. * Adds an html element as an overlay to the current viewport. Useful for
  9454. * highlighting words or areas of interest on an image or other zoomable
  9455. * interface.
  9456. * @method
  9457. * @param {Element|String|Object} element - A reference to an element or an id for
  9458. * the element which will overlayed. Or an Object specifying the configuration for the overlay
  9459. * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
  9460. * rectangle which will be overlayed.
  9461. * @param {OpenSeadragon.OverlayPlacement} placement - The position of the
  9462. * viewport which the location coordinates will be treated as relative
  9463. * to.
  9464. * @param {function} onDraw - If supplied the callback is called when the overlay
  9465. * needs to be drawn. It it the responsibility of the callback to do any drawing/positioning.
  9466. * It is passed position, size and element.
  9467. */
  9468. addOverlay: function( element, location, placement, onDraw ) {
  9469. var options;
  9470. if( $.isPlainObject( element ) ){
  9471. options = element;
  9472. } else {
  9473. options = {
  9474. element: element,
  9475. location: location,
  9476. placement: placement,
  9477. onDraw: onDraw
  9478. };
  9479. }
  9480. element = $.getElement(options.element);
  9481. if ( getOverlayIndex( this.overlays, element ) >= 0 ) {
  9482. // they're trying to add a duplicate overlay
  9483. return;
  9484. }
  9485. this.overlays.push( new $.Overlay({
  9486. element: element,
  9487. location: options.location,
  9488. placement: options.placement,
  9489. onDraw: options.onDraw
  9490. }) );
  9491. this.updateAgain = true;
  9492. if( this.viewer ){
  9493. this.viewer.raiseEvent( 'add-overlay', {
  9494. element: element,
  9495. location: options.location,
  9496. placement: options.placement
  9497. });
  9498. }
  9499. return this;
  9500. },
  9501. /**
  9502. * Updates the overlay represented by the reference to the element or
  9503. * element id moving it to the new location, relative to the new placement.
  9504. * @method
  9505. * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
  9506. * rectangle which will be overlayed.
  9507. * @param {OpenSeadragon.OverlayPlacement} placement - The position of the
  9508. * viewport which the location coordinates will be treated as relative
  9509. * to.
  9510. * @return {OpenSeadragon.Drawer} Chainable.
  9511. */
  9512. updateOverlay: function( element, location, placement ) {
  9513. var i;
  9514. element = $.getElement( element );
  9515. i = getOverlayIndex( this.overlays, element );
  9516. if ( i >= 0 ) {
  9517. this.overlays[ i ].update( location, placement );
  9518. this.updateAgain = true;
  9519. }
  9520. if( this.viewer ){
  9521. this.viewer.raiseEvent( 'update-overlay', {
  9522. element: element,
  9523. location: location,
  9524. placement: placement
  9525. });
  9526. }
  9527. return this;
  9528. },
  9529. /**
  9530. * Removes and overlay identified by the reference element or element id
  9531. * and schedules and update.
  9532. * @method
  9533. * @param {Element|String} element - A reference to the element or an
  9534. * element id which represent the ovelay content to be removed.
  9535. * @return {OpenSeadragon.Drawer} Chainable.
  9536. */
  9537. removeOverlay: function( element ) {
  9538. var i;
  9539. element = $.getElement( element );
  9540. i = getOverlayIndex( this.overlays, element );
  9541. if ( i >= 0 ) {
  9542. this.overlays[ i ].destroy();
  9543. this.overlays.splice( i, 1 );
  9544. this.updateAgain = true;
  9545. }
  9546. if( this.viewer ){
  9547. this.viewer.raiseEvent( 'remove-overlay', {
  9548. element: element
  9549. });
  9550. }
  9551. return this;
  9552. },
  9553. /**
  9554. * Removes all currently configured Overlays from this Drawer and schedules
  9555. * and update.
  9556. * @method
  9557. * @return {OpenSeadragon.Drawer} Chainable.
  9558. */
  9559. clearOverlays: function() {
  9560. while ( this.overlays.length > 0 ) {
  9561. this.overlays.pop().destroy();
  9562. this.updateAgain = true;
  9563. }
  9564. if( this.viewer ){
  9565. this.viewer.raiseEvent( 'clear-overlay', {} );
  9566. }
  9567. return this;
  9568. },
  9569. /**
  9570. * Returns whether the Drawer is scheduled for an update at the
  9571. * soonest possible opportunity.
  9572. * @method
  9573. * @returns {Boolean} - Whether the Drawer is scheduled for an update at the
  9574. * soonest possible opportunity.
  9575. */
  9576. needsUpdate: function() {
  9577. return this.updateAgain;
  9578. },
  9579. /**
  9580. * Returns the total number of tiles that have been loaded by this Drawer.
  9581. * @method
  9582. * @returns {Number} - The total number of tiles that have been loaded by
  9583. * this Drawer.
  9584. */
  9585. numTilesLoaded: function() {
  9586. return this.tilesLoaded.length;
  9587. },
  9588. /**
  9589. * Clears all tiles and triggers an update on the next call to
  9590. * Drawer.prototype.update().
  9591. * @method
  9592. * @return {OpenSeadragon.Drawer} Chainable.
  9593. */
  9594. reset: function() {
  9595. clearTiles( this );
  9596. this.lastResetTime = $.now();
  9597. this.updateAgain = true;
  9598. return this;
  9599. },
  9600. /**
  9601. * Forces the Drawer to update.
  9602. * @method
  9603. * @return {OpenSeadragon.Drawer} Chainable.
  9604. */
  9605. update: function() {
  9606. //this.profiler.beginUpdate();
  9607. this.midUpdate = true;
  9608. updateViewport( this );
  9609. this.midUpdate = false;
  9610. //this.profiler.endUpdate();
  9611. return this;
  9612. },
  9613. /**
  9614. * Used internally to load images when required. May also be used to
  9615. * preload a set of images so the browser will have them available in
  9616. * the local cache to optimize user experience in certain cases. Because
  9617. * the number of parallel image loads is configurable, if too many images
  9618. * are currently being loaded, the request will be ignored. Since by
  9619. * default drawer.imageLoaderLimit is 0, the native browser parallel
  9620. * image loading policy will be used.
  9621. * @method
  9622. * @param {String} src - The url of the image to load.
  9623. * @param {Function} callback - The function that will be called with the
  9624. * Image object as the only parameter if it was loaded successfully.
  9625. * If an error occured, or the request timed out or was aborted,
  9626. * the parameter is null instead.
  9627. * @return {Boolean} loading - Whether the request was submitted or ignored
  9628. * based on OpenSeadragon.DEFAULT_SETTINGS.imageLoaderLimit.
  9629. */
  9630. loadImage: function( src, callback ) {
  9631. var _this = this,
  9632. loading = false,
  9633. image,
  9634. jobid,
  9635. complete;
  9636. if ( !this.imageLoaderLimit ||
  9637. this.downloading < this.imageLoaderLimit ) {
  9638. this.downloading++;
  9639. image = new Image();
  9640. complete = function( imagesrc, resultingImage ){
  9641. _this.downloading--;
  9642. if (typeof ( callback ) == "function") {
  9643. try {
  9644. callback( resultingImage );
  9645. } catch ( e ) {
  9646. $.console.error(
  9647. "%s while executing %s callback: %s",
  9648. e.name,
  9649. src,
  9650. e.message,
  9651. e
  9652. );
  9653. }
  9654. }
  9655. };
  9656. image.onload = function(){
  9657. finishLoadingImage( image, complete, true, jobid );
  9658. };
  9659. image.onabort = image.onerror = function(){
  9660. finishLoadingImage( image, complete, false, jobid );
  9661. };
  9662. jobid = window.setTimeout( function(){
  9663. finishLoadingImage( image, complete, false, jobid );
  9664. }, this.timeout );
  9665. loading = true;
  9666. image.src = src;
  9667. }
  9668. return loading;
  9669. },
  9670. canRotate: function() {
  9671. return USE_CANVAS;
  9672. }
  9673. };
  9674. /**
  9675. * @private
  9676. * @inner
  9677. */
  9678. function addOverlayFromConfiguration( drawer, overlay ){
  9679. var element = null,
  9680. rect = ( overlay.height && overlay.width ) ? new $.Rect(
  9681. overlay.x || overlay.px,
  9682. overlay.y || overlay.py,
  9683. overlay.width,
  9684. overlay.height
  9685. ) : new $.Point(
  9686. overlay.x || overlay.px,
  9687. overlay.y || overlay.py
  9688. ),
  9689. id = overlay.id ?
  9690. overlay.id :
  9691. "openseadragon-overlay-"+Math.floor(Math.random()*10000000);
  9692. element = $.getElement(overlay.id);
  9693. if( !element ){
  9694. element = document.createElement("a");
  9695. element.href = "#/overlay/"+id;
  9696. }
  9697. element.id = id;
  9698. $.addClass( element, overlay.className ?
  9699. overlay.className :
  9700. "openseadragon-overlay"
  9701. );
  9702. if(overlay.px !== undefined){
  9703. //if they specified 'px' so it's in pixel coordinates so
  9704. //we need to translate to viewport coordinates
  9705. rect = drawer.viewport.imageToViewportRectangle( rect );
  9706. }
  9707. if( overlay.placement ){
  9708. return new $.Overlay({
  9709. element: element,
  9710. location: drawer.viewport.pointFromPixel(rect),
  9711. placement: $.OverlayPlacement[overlay.placement.toUpperCase()],
  9712. onDraw: overlay.onDraw
  9713. });
  9714. }else{
  9715. return new $.Overlay({
  9716. element: element,
  9717. location: rect,
  9718. onDraw: overlay.onDraw
  9719. });
  9720. }
  9721. }
  9722. /**
  9723. * @private
  9724. * @inner
  9725. * Pretty much every other line in this needs to be documented so it's clear
  9726. * how each piece of this routine contributes to the drawing process. That's
  9727. * why there are so many TODO's inside this function.
  9728. */
  9729. function updateViewport( drawer ) {
  9730. drawer.updateAgain = false;
  9731. if( drawer.viewer ){
  9732. drawer.viewer.raiseEvent( 'update-viewport', {} );
  9733. }
  9734. var tile,
  9735. level,
  9736. best = null,
  9737. haveDrawn = false,
  9738. currentTime = $.now(),
  9739. viewportSize = drawer.viewport.getContainerSize(),
  9740. viewportBounds = drawer.viewport.getBounds( true ),
  9741. viewportTL = viewportBounds.getTopLeft(),
  9742. viewportBR = viewportBounds.getBottomRight(),
  9743. zeroRatioC = drawer.viewport.deltaPixelsFromPoints(
  9744. drawer.source.getPixelRatio( 0 ),
  9745. true
  9746. ).x,
  9747. lowestLevel = Math.max(
  9748. drawer.source.minLevel,
  9749. Math.floor(
  9750. Math.log( drawer.minZoomImageRatio ) /
  9751. Math.log( 2 )
  9752. )
  9753. ),
  9754. highestLevel = Math.min(
  9755. Math.abs(drawer.source.maxLevel),
  9756. Math.abs(Math.floor(
  9757. Math.log( zeroRatioC / drawer.minPixelRatio ) /
  9758. Math.log( 2 )
  9759. ))
  9760. ),
  9761. degrees = drawer.viewport.degrees,
  9762. renderPixelRatioC,
  9763. renderPixelRatioT,
  9764. zeroRatioT,
  9765. optimalRatio,
  9766. levelOpacity,
  9767. levelVisibility;
  9768. //TODO
  9769. while ( drawer.lastDrawn.length > 0 ) {
  9770. tile = drawer.lastDrawn.pop();
  9771. tile.beingDrawn = false;
  9772. }
  9773. //TODO
  9774. drawer.canvas.innerHTML = "";
  9775. if ( USE_CANVAS ) {
  9776. if( drawer.canvas.width != viewportSize.x ||
  9777. drawer.canvas.height != viewportSize.y ){
  9778. drawer.canvas.width = viewportSize.x;
  9779. drawer.canvas.height = viewportSize.y;
  9780. }
  9781. drawer.context.clearRect( 0, 0, viewportSize.x, viewportSize.y );
  9782. }
  9783. //Change bounds for rotation
  9784. if (degrees === 90 || degrees === 270) {
  9785. var rotatedBounds = viewportBounds.rotate( degrees );
  9786. viewportTL = rotatedBounds.getTopLeft();
  9787. viewportBR = rotatedBounds.getBottomRight();
  9788. }
  9789. //Don't draw if completely outside of the viewport
  9790. if ( !drawer.wrapHorizontal &&
  9791. ( viewportBR.x < 0 || viewportTL.x > 1 ) ) {
  9792. return;
  9793. } else if
  9794. ( !drawer.wrapVertical &&
  9795. ( viewportBR.y < 0 || viewportTL.y > drawer.normHeight ) ) {
  9796. return;
  9797. }
  9798. //TODO
  9799. if ( !drawer.wrapHorizontal ) {
  9800. viewportTL.x = Math.max( viewportTL.x, 0 );
  9801. viewportBR.x = Math.min( viewportBR.x, 1 );
  9802. }
  9803. if ( !drawer.wrapVertical ) {
  9804. viewportTL.y = Math.max( viewportTL.y, 0 );
  9805. viewportBR.y = Math.min( viewportBR.y, drawer.normHeight );
  9806. }
  9807. //TODO
  9808. lowestLevel = Math.min( lowestLevel, highestLevel );
  9809. //TODO
  9810. var drawLevel; // FIXME: drawLevel should have a more explanatory name
  9811. for ( level = highestLevel; level >= lowestLevel; level-- ) {
  9812. drawLevel = false;
  9813. //Avoid calculations for draw if we have already drawn this
  9814. renderPixelRatioC = drawer.viewport.deltaPixelsFromPoints(
  9815. drawer.source.getPixelRatio( level ),
  9816. true
  9817. ).x;
  9818. if ( ( !haveDrawn && renderPixelRatioC >= drawer.minPixelRatio ) ||
  9819. ( level == lowestLevel ) ) {
  9820. drawLevel = true;
  9821. haveDrawn = true;
  9822. } else if ( !haveDrawn ) {
  9823. continue;
  9824. }
  9825. //Perform calculations for draw if we haven't drawn this
  9826. renderPixelRatioT = drawer.viewport.deltaPixelsFromPoints(
  9827. drawer.source.getPixelRatio( level ),
  9828. false
  9829. ).x;
  9830. zeroRatioT = drawer.viewport.deltaPixelsFromPoints(
  9831. drawer.source.getPixelRatio(
  9832. Math.max(
  9833. drawer.source.getClosestLevel( drawer.viewport.containerSize ) - 1,
  9834. 0
  9835. )
  9836. ),
  9837. false
  9838. ).x;
  9839. optimalRatio = drawer.immediateRender ?
  9840. 1 :
  9841. zeroRatioT;
  9842. levelOpacity = Math.min( 1, ( renderPixelRatioC - 0.5 ) / 0.5 );
  9843. levelVisibility = optimalRatio / Math.abs(
  9844. optimalRatio - renderPixelRatioT
  9845. );
  9846. //TODO
  9847. best = updateLevel(
  9848. drawer,
  9849. haveDrawn,
  9850. drawLevel,
  9851. level,
  9852. levelOpacity,
  9853. levelVisibility,
  9854. viewportTL,
  9855. viewportBR,
  9856. currentTime,
  9857. best
  9858. );
  9859. //TODO
  9860. if ( providesCoverage( drawer.coverage, level ) ) {
  9861. break;
  9862. }
  9863. }
  9864. //TODO
  9865. drawTiles( drawer, drawer.lastDrawn );
  9866. drawOverlays( drawer.viewport, drawer.overlays, drawer.container );
  9867. //TODO
  9868. if ( best ) {
  9869. loadTile( drawer, best, currentTime );
  9870. // because we haven't finished drawing, so
  9871. drawer.updateAgain = true;
  9872. }
  9873. }
  9874. function updateLevel( drawer, haveDrawn, drawLevel, level, levelOpacity, levelVisibility, viewportTL, viewportBR, currentTime, best ){
  9875. var x, y,
  9876. tileTL,
  9877. tileBR,
  9878. numberOfTiles,
  9879. viewportCenter = drawer.viewport.pixelFromPoint( drawer.viewport.getCenter() );
  9880. if( drawer.viewer ){
  9881. drawer.viewer.raiseEvent( 'update-level', {
  9882. havedrawn: haveDrawn,
  9883. level: level,
  9884. opacity: levelOpacity,
  9885. visibility: levelVisibility,
  9886. topleft: viewportTL,
  9887. bottomright: viewportBR,
  9888. currenttime: currentTime,
  9889. best: best
  9890. });
  9891. }
  9892. //OK, a new drawing so do your calculations
  9893. tileTL = drawer.source.getTileAtPoint( level, viewportTL );
  9894. tileBR = drawer.source.getTileAtPoint( level, viewportBR );
  9895. numberOfTiles = drawer.source.getNumTiles( level );
  9896. resetCoverage( drawer.coverage, level );
  9897. if ( !drawer.wrapHorizontal ) {
  9898. tileBR.x = Math.min( tileBR.x, numberOfTiles.x - 1 );
  9899. }
  9900. if ( !drawer.wrapVertical ) {
  9901. tileBR.y = Math.min( tileBR.y, numberOfTiles.y - 1 );
  9902. }
  9903. for ( x = tileTL.x; x <= tileBR.x; x++ ) {
  9904. for ( y = tileTL.y; y <= tileBR.y; y++ ) {
  9905. best = updateTile(
  9906. drawer,
  9907. drawLevel,
  9908. haveDrawn,
  9909. x, y,
  9910. level,
  9911. levelOpacity,
  9912. levelVisibility,
  9913. viewportCenter,
  9914. numberOfTiles,
  9915. currentTime,
  9916. best
  9917. );
  9918. }
  9919. }
  9920. return best;
  9921. }
  9922. function updateTile( drawer, drawLevel, haveDrawn, x, y, level, levelOpacity, levelVisibility, viewportCenter, numberOfTiles, currentTime, best){
  9923. var tile = getTile(
  9924. x, y,
  9925. level,
  9926. drawer.source,
  9927. drawer.tilesMatrix,
  9928. currentTime,
  9929. numberOfTiles,
  9930. drawer.normHeight
  9931. ),
  9932. drawTile = drawLevel;
  9933. if( drawer.viewer ){
  9934. drawer.viewer.raiseEvent( 'update-tile', {
  9935. tile: tile
  9936. });
  9937. }
  9938. setCoverage( drawer.coverage, level, x, y, false );
  9939. if ( !tile.exists ) {
  9940. return best;
  9941. }
  9942. if ( haveDrawn && !drawTile ) {
  9943. if ( isCovered( drawer.coverage, level, x, y ) ) {
  9944. setCoverage( drawer.coverage, level, x, y, true );
  9945. } else {
  9946. drawTile = true;
  9947. }
  9948. }
  9949. if ( !drawTile ) {
  9950. return best;
  9951. }
  9952. positionTile(
  9953. tile,
  9954. drawer.source.tileOverlap,
  9955. drawer.viewport,
  9956. viewportCenter,
  9957. levelVisibility
  9958. );
  9959. if ( tile.loaded ) {
  9960. var needsUpdate = blendTile(
  9961. drawer,
  9962. tile,
  9963. x, y,
  9964. level,
  9965. levelOpacity,
  9966. currentTime
  9967. );
  9968. if ( needsUpdate ) {
  9969. drawer.updateAgain = true;
  9970. }
  9971. } else if ( tile.loading ) {
  9972. // the tile is already in the download queue
  9973. // thanks josh1093 for finally translating this typo
  9974. } else {
  9975. best = compareTiles( best, tile );
  9976. }
  9977. return best;
  9978. }
  9979. function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, normHeight ) {
  9980. var xMod,
  9981. yMod,
  9982. bounds,
  9983. exists,
  9984. url,
  9985. tile;
  9986. if ( !tilesMatrix[ level ] ) {
  9987. tilesMatrix[ level ] = {};
  9988. }
  9989. if ( !tilesMatrix[ level ][ x ] ) {
  9990. tilesMatrix[ level ][ x ] = {};
  9991. }
  9992. if ( !tilesMatrix[ level ][ x ][ y ] ) {
  9993. xMod = ( numTiles.x + ( x % numTiles.x ) ) % numTiles.x;
  9994. yMod = ( numTiles.y + ( y % numTiles.y ) ) % numTiles.y;
  9995. bounds = tileSource.getTileBounds( level, xMod, yMod );
  9996. exists = tileSource.tileExists( level, xMod, yMod );
  9997. url = tileSource.getTileUrl( level, xMod, yMod );
  9998. bounds.x += 1.0 * ( x - xMod ) / numTiles.x;
  9999. bounds.y += normHeight * ( y - yMod ) / numTiles.y;
  10000. tilesMatrix[ level ][ x ][ y ] = new $.Tile(
  10001. level,
  10002. x,
  10003. y,
  10004. bounds,
  10005. exists,
  10006. url
  10007. );
  10008. }
  10009. tile = tilesMatrix[ level ][ x ][ y ];
  10010. tile.lastTouchTime = time;
  10011. return tile;
  10012. }
  10013. function loadTile( drawer, tile, time ) {
  10014. if( drawer.viewport.collectionMode ){
  10015. drawer.midUpdate = false;
  10016. onTileLoad( drawer, tile, time );
  10017. } else {
  10018. tile.loading = drawer.loadImage(
  10019. tile.url,
  10020. function( image ){
  10021. onTileLoad( drawer, tile, time, image );
  10022. }
  10023. );
  10024. }
  10025. }
  10026. function onTileLoad( drawer, tile, time, image ) {
  10027. var insertionIndex,
  10028. cutoff,
  10029. worstTile,
  10030. worstTime,
  10031. worstLevel,
  10032. worstTileIndex,
  10033. prevTile,
  10034. prevTime,
  10035. prevLevel,
  10036. i;
  10037. tile.loading = false;
  10038. if ( drawer.midUpdate ) {
  10039. $.console.warn( "Tile load callback in middle of drawing routine." );
  10040. return;
  10041. } else if ( !image && !drawer.viewport.collectionMode ) {
  10042. $.console.log( "Tile %s failed to load: %s", tile, tile.url );
  10043. if( !drawer.debugMode ){
  10044. tile.exists = false;
  10045. return;
  10046. }
  10047. } else if ( time < drawer.lastResetTime ) {
  10048. $.console.log( "Ignoring tile %s loaded before reset: %s", tile, tile.url );
  10049. return;
  10050. }
  10051. tile.loaded = true;
  10052. tile.image = image;
  10053. insertionIndex = drawer.tilesLoaded.length;
  10054. if ( drawer.tilesLoaded.length >= drawer.maxImageCacheCount ) {
  10055. cutoff = Math.ceil( Math.log( drawer.source.tileSize ) / Math.log( 2 ) );
  10056. worstTile = null;
  10057. worstTileIndex = -1;
  10058. for ( i = drawer.tilesLoaded.length - 1; i >= 0; i-- ) {
  10059. prevTile = drawer.tilesLoaded[ i ];
  10060. if ( prevTile.level <= drawer.cutoff || prevTile.beingDrawn ) {
  10061. continue;
  10062. } else if ( !worstTile ) {
  10063. worstTile = prevTile;
  10064. worstTileIndex = i;
  10065. continue;
  10066. }
  10067. prevTime = prevTile.lastTouchTime;
  10068. worstTime = worstTile.lastTouchTime;
  10069. prevLevel = prevTile.level;
  10070. worstLevel = worstTile.level;
  10071. if ( prevTime < worstTime ||
  10072. ( prevTime == worstTime && prevLevel > worstLevel ) ) {
  10073. worstTile = prevTile;
  10074. worstTileIndex = i;
  10075. }
  10076. }
  10077. if ( worstTile && worstTileIndex >= 0 ) {
  10078. worstTile.unload();
  10079. insertionIndex = worstTileIndex;
  10080. }
  10081. }
  10082. drawer.tilesLoaded[ insertionIndex ] = tile;
  10083. drawer.updateAgain = true;
  10084. }
  10085. function positionTile( tile, overlap, viewport, viewportCenter, levelVisibility ){
  10086. var boundsTL = tile.bounds.getTopLeft(),
  10087. boundsSize = tile.bounds.getSize(),
  10088. positionC = viewport.pixelFromPoint( boundsTL, true ),
  10089. positionT = viewport.pixelFromPoint( boundsTL, false ),
  10090. sizeC = viewport.deltaPixelsFromPoints( boundsSize, true ),
  10091. sizeT = viewport.deltaPixelsFromPoints( boundsSize, false ),
  10092. tileCenter = positionT.plus( sizeT.divide( 2 ) ),
  10093. tileDistance = viewportCenter.distanceTo( tileCenter );
  10094. if ( !overlap ) {
  10095. sizeC = sizeC.plus( new $.Point( 1, 1 ) );
  10096. }
  10097. tile.position = positionC;
  10098. tile.size = sizeC;
  10099. tile.distance = tileDistance;
  10100. tile.visibility = levelVisibility;
  10101. }
  10102. function blendTile( drawer, tile, x, y, level, levelOpacity, currentTime ){
  10103. var blendTimeMillis = 1000 * drawer.blendTime,
  10104. deltaTime,
  10105. opacity;
  10106. if ( !tile.blendStart ) {
  10107. tile.blendStart = currentTime;
  10108. }
  10109. deltaTime = currentTime - tile.blendStart;
  10110. opacity = blendTimeMillis ? Math.min( 1, deltaTime / ( blendTimeMillis ) ) : 1;
  10111. if ( drawer.alwaysBlend ) {
  10112. opacity *= levelOpacity;
  10113. }
  10114. tile.opacity = opacity;
  10115. drawer.lastDrawn.push( tile );
  10116. if ( opacity == 1 ) {
  10117. setCoverage( drawer.coverage, level, x, y, true );
  10118. } else if ( deltaTime < blendTimeMillis ) {
  10119. return true;
  10120. }
  10121. return false;
  10122. }
  10123. function clearTiles( drawer ) {
  10124. drawer.tilesMatrix = {};
  10125. drawer.tilesLoaded = [];
  10126. }
  10127. /**
  10128. * @private
  10129. * @inner
  10130. * Returns true if the given tile provides coverage to lower-level tiles of
  10131. * lower resolution representing the same content. If neither x nor y is
  10132. * given, returns true if the entire visible level provides coverage.
  10133. *
  10134. * Note that out-of-bounds tiles provide coverage in this sense, since
  10135. * there's no content that they would need to cover. Tiles at non-existent
  10136. * levels that are within the image bounds, however, do not.
  10137. */
  10138. function providesCoverage( coverage, level, x, y ) {
  10139. var rows,
  10140. cols,
  10141. i, j;
  10142. if ( !coverage[ level ] ) {
  10143. return false;
  10144. }
  10145. if ( x === undefined || y === undefined ) {
  10146. rows = coverage[ level ];
  10147. for ( i in rows ) {
  10148. if ( rows.hasOwnProperty( i ) ) {
  10149. cols = rows[ i ];
  10150. for ( j in cols ) {
  10151. if ( cols.hasOwnProperty( j ) && !cols[ j ] ) {
  10152. return false;
  10153. }
  10154. }
  10155. }
  10156. }
  10157. return true;
  10158. }
  10159. return (
  10160. coverage[ level ][ x] === undefined ||
  10161. coverage[ level ][ x ][ y ] === undefined ||
  10162. coverage[ level ][ x ][ y ] === true
  10163. );
  10164. }
  10165. /**
  10166. * @private
  10167. * @inner
  10168. * Returns true if the given tile is completely covered by higher-level
  10169. * tiles of higher resolution representing the same content. If neither x
  10170. * nor y is given, returns true if the entire visible level is covered.
  10171. */
  10172. function isCovered( coverage, level, x, y ) {
  10173. if ( x === undefined || y === undefined ) {
  10174. return providesCoverage( coverage, level + 1 );
  10175. } else {
  10176. return (
  10177. providesCoverage( coverage, level + 1, 2 * x, 2 * y ) &&
  10178. providesCoverage( coverage, level + 1, 2 * x, 2 * y + 1 ) &&
  10179. providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y ) &&
  10180. providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y + 1 )
  10181. );
  10182. }
  10183. }
  10184. /**
  10185. * @private
  10186. * @inner
  10187. * Sets whether the given tile provides coverage or not.
  10188. */
  10189. function setCoverage( coverage, level, x, y, covers ) {
  10190. if ( !coverage[ level ] ) {
  10191. $.console.warn(
  10192. "Setting coverage for a tile before its level's coverage has been reset: %s",
  10193. level
  10194. );
  10195. return;
  10196. }
  10197. if ( !coverage[ level ][ x ] ) {
  10198. coverage[ level ][ x ] = {};
  10199. }
  10200. coverage[ level ][ x ][ y ] = covers;
  10201. }
  10202. /**
  10203. * @private
  10204. * @inner
  10205. * Resets coverage information for the given level. This should be called
  10206. * after every draw routine. Note that at the beginning of the next draw
  10207. * routine, coverage for every visible tile should be explicitly set.
  10208. */
  10209. function resetCoverage( coverage, level ) {
  10210. coverage[ level ] = {};
  10211. }
  10212. /**
  10213. * @private
  10214. * @inner
  10215. * Determines the 'z-index' of the given overlay. Overlays are ordered in
  10216. * a z-index based on the order they are added to the Drawer.
  10217. */
  10218. function getOverlayIndex( overlays, element ) {
  10219. var i;
  10220. for ( i = overlays.length - 1; i >= 0; i-- ) {
  10221. if ( overlays[ i ].element == element ) {
  10222. return i;
  10223. }
  10224. }
  10225. return -1;
  10226. }
  10227. /**
  10228. * @private
  10229. * @inner
  10230. * Determines whether the 'last best' tile for the area is better than the
  10231. * tile in question.
  10232. */
  10233. function compareTiles( previousBest, tile ) {
  10234. if ( !previousBest ) {
  10235. return tile;
  10236. }
  10237. if ( tile.visibility > previousBest.visibility ) {
  10238. return tile;
  10239. } else if ( tile.visibility == previousBest.visibility ) {
  10240. if ( tile.distance < previousBest.distance ) {
  10241. return tile;
  10242. }
  10243. }
  10244. return previousBest;
  10245. }
  10246. function finishLoadingImage( image, callback, successful, jobid ){
  10247. image.onload = null;
  10248. image.onabort = null;
  10249. image.onerror = null;
  10250. if ( jobid ) {
  10251. window.clearTimeout( jobid );
  10252. }
  10253. $.requestAnimationFrame( function() {
  10254. callback( image.src, successful ? image : null);
  10255. });
  10256. }
  10257. function drawOverlays( viewport, overlays, container ){
  10258. var i,
  10259. length = overlays.length;
  10260. for ( i = 0; i < length; i++ ) {
  10261. drawOverlay( viewport, overlays[ i ], container );
  10262. }
  10263. }
  10264. function drawOverlay( viewport, overlay, container ){
  10265. overlay.position = viewport.pixelFromPoint(
  10266. overlay.bounds.getTopLeft(),
  10267. true
  10268. );
  10269. overlay.size = viewport.deltaPixelsFromPoints(
  10270. overlay.bounds.getSize(),
  10271. true
  10272. );
  10273. overlay.drawHTML( container, viewport );
  10274. }
  10275. function drawTiles( drawer, lastDrawn ){
  10276. var i,
  10277. tile,
  10278. tileKey,
  10279. viewer,
  10280. viewport,
  10281. position,
  10282. tileSource,
  10283. collectionTileSource;
  10284. for ( i = lastDrawn.length - 1; i >= 0; i-- ) {
  10285. tile = lastDrawn[ i ];
  10286. //We dont actually 'draw' a collection tile, rather its used to house
  10287. //an overlay which does the drawing in its own viewport
  10288. if( drawer.viewport.collectionMode ){
  10289. tileKey = tile.x + '/' + tile.y;
  10290. viewport = drawer.viewport;
  10291. collectionTileSource = viewport.collectionTileSource;
  10292. if( !drawer.collectionOverlays[ tileKey ] ){
  10293. position = collectionTileSource.layout == 'horizontal' ?
  10294. tile.y + ( tile.x * collectionTileSource.rows ) :
  10295. tile.x + ( tile.y * collectionTileSource.rows );
  10296. if (position < collectionTileSource.tileSources.length) {
  10297. tileSource = collectionTileSource.tileSources[ position ];
  10298. } else {
  10299. tileSource = null;
  10300. }
  10301. //$.console.log("Rendering collection tile %s | %s | %s", tile.y, tile.y, position);
  10302. if( tileSource ){
  10303. drawer.collectionOverlays[ tileKey ] = viewer = new $.Viewer({
  10304. hash: viewport.viewer.hash + "-" + tileKey,
  10305. element: $.makeNeutralElement( "div" ),
  10306. mouseNavEnabled: false,
  10307. showNavigator: false,
  10308. showSequenceControl: false,
  10309. showNavigationControl: false,
  10310. tileSources: [
  10311. tileSource
  10312. ]
  10313. });
  10314. //TODO: IE seems to barf on this, not sure if its just the border
  10315. // but we probably need to clear this up with a better
  10316. // test of support for various css features
  10317. if( SUBPIXEL_RENDERING ){
  10318. viewer.element.style.border = '1px solid rgba(255,255,255,0.38)';
  10319. viewer.element.style['-webkit-box-reflect'] =
  10320. 'below 0px -webkit-gradient('+
  10321. 'linear,left '+
  10322. 'top,left '+
  10323. 'bottom,from(transparent),color-stop(62%,transparent),to(rgba(255,255,255,0.62))'+
  10324. ')';
  10325. }
  10326. drawer.addOverlay(
  10327. viewer.element,
  10328. tile.bounds
  10329. );
  10330. }
  10331. }else{
  10332. viewer = drawer.collectionOverlays[ tileKey ];
  10333. if( viewer.viewport ){
  10334. viewer.viewport.resize( tile.size, true );
  10335. viewer.viewport.goHome( true );
  10336. }
  10337. }
  10338. } else {
  10339. if ( USE_CANVAS ) {
  10340. // TODO do this in a more performant way
  10341. // specifically, don't save,rotate,restore every time we draw a tile
  10342. if( drawer.viewport.degrees !== 0 ) {
  10343. offsetForRotation( tile, drawer.canvas, drawer.context, drawer.viewport.degrees );
  10344. tile.drawCanvas( drawer.context );
  10345. restoreRotationChanges( tile, drawer.canvas, drawer.context );
  10346. } else {
  10347. tile.drawCanvas( drawer.context );
  10348. }
  10349. } else {
  10350. tile.drawHTML( drawer.canvas );
  10351. }
  10352. tile.beingDrawn = true;
  10353. }
  10354. if( drawer.debugMode ){
  10355. try{
  10356. drawDebugInfo( drawer, tile, lastDrawn.length, i );
  10357. }catch(e){
  10358. $.console.error(e);
  10359. }
  10360. }
  10361. if( drawer.viewer ){
  10362. drawer.viewer.raiseEvent( 'tile-drawn', {
  10363. tile: tile
  10364. });
  10365. }
  10366. }
  10367. }
  10368. function offsetForRotation( tile, canvas, context, degrees ){
  10369. var cx = canvas.width / 2,
  10370. cy = canvas.height / 2,
  10371. px = tile.position.x - cx,
  10372. py = tile.position.y - cy;
  10373. context.save();
  10374. context.translate(cx, cy);
  10375. context.rotate( Math.PI / 180 * degrees);
  10376. tile.position.x = px;
  10377. tile.position.y = py;
  10378. }
  10379. function restoreRotationChanges( tile, canvas, context ){
  10380. var cx = canvas.width / 2,
  10381. cy = canvas.height / 2,
  10382. px = tile.position.x + cx,
  10383. py = tile.position.y + cy;
  10384. tile.position.x = px;
  10385. tile.position.y = py;
  10386. context.restore();
  10387. }
  10388. function drawDebugInfo( drawer, tile, count, i ){
  10389. if ( USE_CANVAS ) {
  10390. drawer.context.save();
  10391. drawer.context.lineWidth = 2;
  10392. drawer.context.font = 'small-caps bold 13px ariel';
  10393. drawer.context.strokeStyle = drawer.debugGridColor;
  10394. drawer.context.fillStyle = drawer.debugGridColor;
  10395. drawer.context.strokeRect(
  10396. tile.position.x,
  10397. tile.position.y,
  10398. tile.size.x,
  10399. tile.size.y
  10400. );
  10401. if( tile.x === 0 && tile.y === 0 ){
  10402. drawer.context.fillText(
  10403. "Zoom: " + drawer.viewport.getZoom(),
  10404. tile.position.x,
  10405. tile.position.y - 30
  10406. );
  10407. drawer.context.fillText(
  10408. "Pan: " + drawer.viewport.getBounds().toString(),
  10409. tile.position.x,
  10410. tile.position.y - 20
  10411. );
  10412. }
  10413. drawer.context.fillText(
  10414. "Level: " + tile.level,
  10415. tile.position.x + 10,
  10416. tile.position.y + 20
  10417. );
  10418. drawer.context.fillText(
  10419. "Column: " + tile.x,
  10420. tile.position.x + 10,
  10421. tile.position.y + 30
  10422. );
  10423. drawer.context.fillText(
  10424. "Row: " + tile.y,
  10425. tile.position.x + 10,
  10426. tile.position.y + 40
  10427. );
  10428. drawer.context.fillText(
  10429. "Order: " + i + " of " + count,
  10430. tile.position.x + 10,
  10431. tile.position.y + 50
  10432. );
  10433. drawer.context.fillText(
  10434. "Size: " + tile.size.toString(),
  10435. tile.position.x + 10,
  10436. tile.position.y + 60
  10437. );
  10438. drawer.context.fillText(
  10439. "Position: " + tile.position.toString(),
  10440. tile.position.x + 10,
  10441. tile.position.y + 70
  10442. );
  10443. drawer.context.restore();
  10444. }
  10445. }
  10446. }( OpenSeadragon ));
  10447. /*
  10448. * OpenSeadragon - Viewport
  10449. *
  10450. * Copyright (C) 2009 CodePlex Foundation
  10451. * Copyright (C) 2010-2013 OpenSeadragon contributors
  10452. *
  10453. * Redistribution and use in source and binary forms, with or without
  10454. * modification, are permitted provided that the following conditions are
  10455. * met:
  10456. *
  10457. * - Redistributions of source code must retain the above copyright notice,
  10458. * this list of conditions and the following disclaimer.
  10459. *
  10460. * - Redistributions in binary form must reproduce the above copyright
  10461. * notice, this list of conditions and the following disclaimer in the
  10462. * documentation and/or other materials provided with the distribution.
  10463. *
  10464. * - Neither the name of CodePlex Foundation nor the names of its
  10465. * contributors may be used to endorse or promote products derived from
  10466. * this software without specific prior written permission.
  10467. *
  10468. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  10469. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  10470. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  10471. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  10472. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  10473. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  10474. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  10475. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  10476. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  10477. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  10478. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  10479. */
  10480. (function( $ ){
  10481. /**
  10482. * @class
  10483. */
  10484. $.Viewport = function( options ) {
  10485. //backward compatibility for positional args while prefering more
  10486. //idiomatic javascript options object as the only argument
  10487. var args = arguments;
  10488. if( args.length && args[ 0 ] instanceof $.Point ){
  10489. options = {
  10490. containerSize: args[ 0 ],
  10491. contentSize: args[ 1 ],
  10492. config: args[ 2 ]
  10493. };
  10494. }
  10495. //options.config and the general config argument are deprecated
  10496. //in favor of the more direct specification of optional settings
  10497. //being passed directly on the options object
  10498. if ( options.config ){
  10499. $.extend( true, options, options.config );
  10500. delete options.config;
  10501. }
  10502. $.extend( true, this, {
  10503. //required settings
  10504. containerSize: null,
  10505. contentSize: null,
  10506. //internal state properties
  10507. zoomPoint: null,
  10508. viewer: null,
  10509. //configurable options
  10510. springStiffness: $.DEFAULT_SETTINGS.springStiffness,
  10511. animationTime: $.DEFAULT_SETTINGS.animationTime,
  10512. minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio,
  10513. maxZoomPixelRatio: $.DEFAULT_SETTINGS.maxZoomPixelRatio,
  10514. visibilityRatio: $.DEFAULT_SETTINGS.visibilityRatio,
  10515. wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal,
  10516. wrapVertical: $.DEFAULT_SETTINGS.wrapVertical,
  10517. defaultZoomLevel: $.DEFAULT_SETTINGS.defaultZoomLevel,
  10518. minZoomLevel: $.DEFAULT_SETTINGS.minZoomLevel,
  10519. maxZoomLevel: $.DEFAULT_SETTINGS.maxZoomLevel,
  10520. degrees: $.DEFAULT_SETTINGS.degrees
  10521. }, options );
  10522. this.centerSpringX = new $.Spring({
  10523. initial: 0,
  10524. springStiffness: this.springStiffness,
  10525. animationTime: this.animationTime
  10526. });
  10527. this.centerSpringY = new $.Spring({
  10528. initial: 0,
  10529. springStiffness: this.springStiffness,
  10530. animationTime: this.animationTime
  10531. });
  10532. this.zoomSpring = new $.Spring({
  10533. initial: 1,
  10534. springStiffness: this.springStiffness,
  10535. animationTime: this.animationTime
  10536. });
  10537. this.resetContentSize( this.contentSize );
  10538. this.goHome( true );
  10539. this.update();
  10540. };
  10541. $.Viewport.prototype = {
  10542. /**
  10543. * @function
  10544. * @return {OpenSeadragon.Viewport} Chainable.
  10545. */
  10546. resetContentSize: function( contentSize ){
  10547. this.contentSize = contentSize;
  10548. this.contentAspectX = this.contentSize.x / this.contentSize.y;
  10549. this.contentAspectY = this.contentSize.y / this.contentSize.x;
  10550. this.fitWidthBounds = new $.Rect( 0, 0, 1, this.contentAspectY );
  10551. this.fitHeightBounds = new $.Rect( 0, 0, this.contentAspectY, this.contentAspectY);
  10552. this.homeBounds = new $.Rect( 0, 0, 1, this.contentAspectY );
  10553. if( this.viewer ){
  10554. this.viewer.raiseEvent( 'reset-size', {
  10555. contentSize: contentSize
  10556. });
  10557. }
  10558. return this;
  10559. },
  10560. /**
  10561. * @function
  10562. */
  10563. getHomeZoom: function() {
  10564. var aspectFactor =
  10565. this.contentAspectX / this.getAspectRatio();
  10566. if( this.defaultZoomLevel ){
  10567. return this.defaultZoomLevel;
  10568. } else {
  10569. return ( aspectFactor >= 1 ) ?
  10570. 1 :
  10571. aspectFactor;
  10572. }
  10573. },
  10574. /**
  10575. * @function
  10576. */
  10577. getHomeBounds: function() {
  10578. var center = this.homeBounds.getCenter( ),
  10579. width = 1.0 / this.getHomeZoom( ),
  10580. height = width / this.getAspectRatio();
  10581. return new $.Rect(
  10582. center.x - ( width / 2.0 ),
  10583. center.y - ( height / 2.0 ),
  10584. width,
  10585. height
  10586. );
  10587. },
  10588. /**
  10589. * @function
  10590. * @param {Boolean} immediately
  10591. */
  10592. goHome: function( immediately ) {
  10593. if( this.viewer ){
  10594. this.viewer.raiseEvent( 'home', {
  10595. immediately: immediately
  10596. });
  10597. }
  10598. return this.fitBounds( this.getHomeBounds(), immediately );
  10599. },
  10600. /**
  10601. * @function
  10602. */
  10603. getMinZoom: function() {
  10604. var homeZoom = this.getHomeZoom(),
  10605. zoom = this.minZoomLevel ?
  10606. this.minZoomLevel :
  10607. this.minZoomImageRatio * homeZoom;
  10608. return Math.min( zoom, homeZoom );
  10609. },
  10610. /**
  10611. * @function
  10612. */
  10613. getMaxZoom: function() {
  10614. var zoom = this.maxZoomLevel ?
  10615. this.maxZoomLevel :
  10616. ( this.contentSize.x * this.maxZoomPixelRatio / this.containerSize.x );
  10617. return Math.max( zoom, this.getHomeZoom() );
  10618. },
  10619. /**
  10620. * @function
  10621. */
  10622. getAspectRatio: function() {
  10623. return this.containerSize.x / this.containerSize.y;
  10624. },
  10625. /**
  10626. * @function
  10627. */
  10628. getContainerSize: function() {
  10629. return new $.Point(
  10630. this.containerSize.x,
  10631. this.containerSize.y
  10632. );
  10633. },
  10634. /**
  10635. * @function
  10636. * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
  10637. */
  10638. getBounds: function( current ) {
  10639. var center = this.getCenter( current ),
  10640. width = 1.0 / this.getZoom( current ),
  10641. height = width / this.getAspectRatio();
  10642. return new $.Rect(
  10643. center.x - ( width / 2.0 ),
  10644. center.y - ( height / 2.0 ),
  10645. width,
  10646. height
  10647. );
  10648. },
  10649. /**
  10650. * @function
  10651. * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
  10652. */
  10653. getCenter: function( current ) {
  10654. var centerCurrent = new $.Point(
  10655. this.centerSpringX.current.value,
  10656. this.centerSpringY.current.value
  10657. ),
  10658. centerTarget = new $.Point(
  10659. this.centerSpringX.target.value,
  10660. this.centerSpringY.target.value
  10661. ),
  10662. oldZoomPixel,
  10663. zoom,
  10664. width,
  10665. height,
  10666. bounds,
  10667. newZoomPixel,
  10668. deltaZoomPixels,
  10669. deltaZoomPoints;
  10670. if ( current ) {
  10671. return centerCurrent;
  10672. } else if ( !this.zoomPoint ) {
  10673. return centerTarget;
  10674. }
  10675. oldZoomPixel = this.pixelFromPoint(this.zoomPoint, true);
  10676. zoom = this.getZoom();
  10677. width = 1.0 / zoom;
  10678. height = width / this.getAspectRatio();
  10679. bounds = new $.Rect(
  10680. centerCurrent.x - width / 2.0,
  10681. centerCurrent.y - height / 2.0,
  10682. width,
  10683. height
  10684. );
  10685. newZoomPixel = this.zoomPoint.minus(
  10686. bounds.getTopLeft()
  10687. ).times(
  10688. this.containerSize.x / bounds.width
  10689. );
  10690. deltaZoomPixels = newZoomPixel.minus( oldZoomPixel );
  10691. deltaZoomPoints = deltaZoomPixels.divide( this.containerSize.x * zoom );
  10692. return centerTarget.plus( deltaZoomPoints );
  10693. },
  10694. /**
  10695. * @function
  10696. * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
  10697. */
  10698. getZoom: function( current ) {
  10699. if ( current ) {
  10700. return this.zoomSpring.current.value;
  10701. } else {
  10702. return this.zoomSpring.target.value;
  10703. }
  10704. },
  10705. /**
  10706. * @function
  10707. * @return {OpenSeadragon.Viewport} Chainable.
  10708. */
  10709. applyConstraints: function( immediately ) {
  10710. var actualZoom = this.getZoom(),
  10711. constrainedZoom = Math.max(
  10712. Math.min( actualZoom, this.getMaxZoom() ),
  10713. this.getMinZoom()
  10714. ),
  10715. bounds,
  10716. horizontalThreshold,
  10717. verticalThreshold,
  10718. left,
  10719. right,
  10720. top,
  10721. bottom,
  10722. dx = 0,
  10723. dy = 0;
  10724. if ( actualZoom != constrainedZoom ) {
  10725. this.zoomTo( constrainedZoom, this.zoomPoint, immediately );
  10726. }
  10727. bounds = this.getBounds();
  10728. horizontalThreshold = this.visibilityRatio * bounds.width;
  10729. verticalThreshold = this.visibilityRatio * bounds.height;
  10730. left = bounds.x + bounds.width;
  10731. right = 1 - bounds.x;
  10732. top = bounds.y + bounds.height;
  10733. bottom = this.contentAspectY - bounds.y;
  10734. if ( this.wrapHorizontal ) {
  10735. //do nothing
  10736. } else {
  10737. if ( left < horizontalThreshold ) {
  10738. dx = horizontalThreshold - left;
  10739. }
  10740. if ( right < horizontalThreshold ) {
  10741. dx = dx ?
  10742. ( dx + right - horizontalThreshold ) / 2 :
  10743. ( right - horizontalThreshold );
  10744. }
  10745. }
  10746. if ( this.wrapVertical ) {
  10747. //do nothing
  10748. } else {
  10749. if ( top < verticalThreshold ) {
  10750. dy = ( verticalThreshold - top );
  10751. }
  10752. if ( bottom < verticalThreshold ) {
  10753. dy = dy ?
  10754. ( dy + bottom - verticalThreshold ) / 2 :
  10755. ( bottom - verticalThreshold );
  10756. }
  10757. }
  10758. if ( dx || dy || immediately ) {
  10759. bounds.x += dx;
  10760. bounds.y += dy;
  10761. if( bounds.width > 1 ){
  10762. bounds.x = 0.5 - bounds.width/2;
  10763. }
  10764. if( bounds.height > this.contentAspectY ){
  10765. bounds.y = this.contentAspectY/2 - bounds.height/2;
  10766. }
  10767. this.fitBounds( bounds, immediately );
  10768. }
  10769. if( this.viewer ){
  10770. this.viewer.raiseEvent( 'constrain', {
  10771. immediately: immediately
  10772. });
  10773. }
  10774. return this;
  10775. },
  10776. /**
  10777. * @function
  10778. * @param {Boolean} immediately
  10779. */
  10780. ensureVisible: function( immediately ) {
  10781. return this.applyConstraints( immediately );
  10782. },
  10783. /**
  10784. * @function
  10785. * @param {OpenSeadragon.Rect} bounds
  10786. * @param {Boolean} immediately
  10787. * @return {OpenSeadragon.Viewport} Chainable.
  10788. */
  10789. fitBounds: function( bounds, immediately ) {
  10790. var aspect = this.getAspectRatio(),
  10791. center = bounds.getCenter(),
  10792. newBounds = new $.Rect(
  10793. bounds.x,
  10794. bounds.y,
  10795. bounds.width,
  10796. bounds.height
  10797. ),
  10798. oldBounds,
  10799. oldZoom,
  10800. newZoom,
  10801. referencePoint;
  10802. if ( newBounds.getAspectRatio() >= aspect ) {
  10803. newBounds.height = bounds.width / aspect;
  10804. newBounds.y = center.y - newBounds.height / 2;
  10805. } else {
  10806. newBounds.width = bounds.height * aspect;
  10807. newBounds.x = center.x - newBounds.width / 2;
  10808. }
  10809. this.panTo( this.getCenter( true ), true );
  10810. this.zoomTo( this.getZoom( true ), null, true );
  10811. oldBounds = this.getBounds();
  10812. oldZoom = this.getZoom();
  10813. newZoom = 1.0 / newBounds.width;
  10814. if ( newZoom == oldZoom || newBounds.width == oldBounds.width ) {
  10815. return this.panTo( center, immediately );
  10816. }
  10817. referencePoint = oldBounds.getTopLeft().times(
  10818. this.containerSize.x / oldBounds.width
  10819. ).minus(
  10820. newBounds.getTopLeft().times(
  10821. this.containerSize.x / newBounds.width
  10822. )
  10823. ).divide(
  10824. this.containerSize.x / oldBounds.width -
  10825. this.containerSize.x / newBounds.width
  10826. );
  10827. return this.zoomTo( newZoom, referencePoint, immediately );
  10828. },
  10829. /**
  10830. * @function
  10831. * @param {Boolean} immediately
  10832. * @return {OpenSeadragon.Viewport} Chainable.
  10833. */
  10834. fitVertically: function( immediately ) {
  10835. var center = this.getCenter();
  10836. if ( this.wrapHorizontal ) {
  10837. center.x = ( 1 + ( center.x % 1 ) ) % 1;
  10838. this.centerSpringX.resetTo( center.x );
  10839. this.centerSpringX.update();
  10840. }
  10841. if ( this.wrapVertical ) {
  10842. center.y = (
  10843. this.contentAspectY + ( center.y % this.contentAspectY )
  10844. ) % this.contentAspectY;
  10845. this.centerSpringY.resetTo( center.y );
  10846. this.centerSpringY.update();
  10847. }
  10848. return this.fitBounds( this.fitHeightBounds, immediately );
  10849. },
  10850. /**
  10851. * @function
  10852. * @param {Boolean} immediately
  10853. * @return {OpenSeadragon.Viewport} Chainable.
  10854. */
  10855. fitHorizontally: function( immediately ) {
  10856. var center = this.getCenter();
  10857. if ( this.wrapHorizontal ) {
  10858. center.x = (
  10859. this.contentAspectX + ( center.x % this.contentAspectX )
  10860. ) % this.contentAspectX;
  10861. this.centerSpringX.resetTo( center.x );
  10862. this.centerSpringX.update();
  10863. }
  10864. if ( this.wrapVertical ) {
  10865. center.y = ( 1 + ( center.y % 1 ) ) % 1;
  10866. this.centerSpringY.resetTo( center.y );
  10867. this.centerSpringY.update();
  10868. }
  10869. return this.fitBounds( this.fitWidthBounds, immediately );
  10870. },
  10871. /**
  10872. * @function
  10873. * @param {OpenSeadragon.Point} delta
  10874. * @param {Boolean} immediately
  10875. * @return {OpenSeadragon.Viewport} Chainable.
  10876. */
  10877. panBy: function( delta, immediately ) {
  10878. var center = new $.Point(
  10879. this.centerSpringX.target.value,
  10880. this.centerSpringY.target.value
  10881. );
  10882. delta = delta.rotate( -this.degrees, new $.Point( 0, 0 ) );
  10883. return this.panTo( center.plus( delta ), immediately );
  10884. },
  10885. /**
  10886. * @function
  10887. * @param {OpenSeadragon.Point} center
  10888. * @param {Boolean} immediately
  10889. * @return {OpenSeadragon.Viewport} Chainable.
  10890. */
  10891. panTo: function( center, immediately ) {
  10892. if ( immediately ) {
  10893. this.centerSpringX.resetTo( center.x );
  10894. this.centerSpringY.resetTo( center.y );
  10895. } else {
  10896. this.centerSpringX.springTo( center.x );
  10897. this.centerSpringY.springTo( center.y );
  10898. }
  10899. if( this.viewer ){
  10900. this.viewer.raiseEvent( 'pan', {
  10901. center: center,
  10902. immediately: immediately
  10903. });
  10904. }
  10905. return this;
  10906. },
  10907. /**
  10908. * @function
  10909. * @return {OpenSeadragon.Viewport} Chainable.
  10910. */
  10911. zoomBy: function( factor, refPoint, immediately ) {
  10912. if( refPoint instanceof $.Point && !isNaN( refPoint.x ) && !isNaN( refPoint.y ) ) {
  10913. refPoint = refPoint.rotate(
  10914. -this.degrees,
  10915. new $.Point( this.centerSpringX.target.value, this.centerSpringY.target.value )
  10916. );
  10917. }
  10918. return this.zoomTo( this.zoomSpring.target.value * factor, refPoint, immediately );
  10919. },
  10920. /**
  10921. * @function
  10922. * @return {OpenSeadragon.Viewport} Chainable.
  10923. */
  10924. zoomTo: function( zoom, refPoint, immediately ) {
  10925. this.zoomPoint = refPoint instanceof $.Point &&
  10926. !isNaN(refPoint.x) &&
  10927. !isNaN(refPoint.y) ?
  10928. refPoint :
  10929. null;
  10930. if ( immediately ) {
  10931. this.zoomSpring.resetTo( zoom );
  10932. } else {
  10933. this.zoomSpring.springTo( zoom );
  10934. }
  10935. if( this.viewer ){
  10936. this.viewer.raiseEvent( 'zoom', {
  10937. zoom: zoom,
  10938. refPoint: refPoint,
  10939. immediately: immediately
  10940. });
  10941. }
  10942. return this;
  10943. },
  10944. /**
  10945. * Currently only 90 degree rotation is supported and it only works
  10946. * with the canvas. Additionally, the navigator does not rotate yet,
  10947. * debug mode doesn't rotate yet, and overlay rotation is only
  10948. * partially supported.
  10949. * @function
  10950. * @name OpenSeadragon.Viewport.prototype.setRotation
  10951. * @return {OpenSeadragon.Viewport} Chainable.
  10952. */
  10953. setRotation: function( degrees ) {
  10954. if( !( this.viewer && this.viewer.drawer.canRotate() ) ) {
  10955. return this;
  10956. }
  10957. degrees = ( degrees + 360 ) % 360;
  10958. if( degrees % 90 !== 0 ) {
  10959. throw new Error('Currently only 0, 90, 180, and 270 degrees are supported.');
  10960. }
  10961. this.degrees = degrees;
  10962. this.viewer.drawer.update();
  10963. return this;
  10964. },
  10965. /**
  10966. * Gets the current rotation in degrees.
  10967. * @function
  10968. * @name OpenSeadragon.Viewport.prototype.getRotation
  10969. * @return {Number} The current rotation in degrees.
  10970. */
  10971. getRotation: function() {
  10972. return this.degrees;
  10973. },
  10974. /**
  10975. * @function
  10976. * @return {OpenSeadragon.Viewport} Chainable.
  10977. */
  10978. resize: function( newContainerSize, maintain ) {
  10979. var oldBounds = this.getBounds(),
  10980. newBounds = oldBounds,
  10981. widthDeltaFactor = newContainerSize.x / this.containerSize.x;
  10982. this.containerSize = new $.Point(
  10983. newContainerSize.x,
  10984. newContainerSize.y
  10985. );
  10986. if (maintain) {
  10987. newBounds.width = oldBounds.width * widthDeltaFactor;
  10988. newBounds.height = newBounds.width / this.getAspectRatio();
  10989. }
  10990. if( this.viewer ){
  10991. this.viewer.raiseEvent( 'resize', {
  10992. newContainerSize: newContainerSize,
  10993. maintain: maintain
  10994. });
  10995. }
  10996. return this.fitBounds( newBounds, true );
  10997. },
  10998. /**
  10999. * @function
  11000. */
  11001. update: function() {
  11002. var oldCenterX = this.centerSpringX.current.value,
  11003. oldCenterY = this.centerSpringY.current.value,
  11004. oldZoom = this.zoomSpring.current.value,
  11005. oldZoomPixel,
  11006. newZoomPixel,
  11007. deltaZoomPixels,
  11008. deltaZoomPoints;
  11009. if (this.zoomPoint) {
  11010. oldZoomPixel = this.pixelFromPoint( this.zoomPoint, true );
  11011. }
  11012. this.zoomSpring.update();
  11013. if (this.zoomPoint && this.zoomSpring.current.value != oldZoom) {
  11014. newZoomPixel = this.pixelFromPoint( this.zoomPoint, true );
  11015. deltaZoomPixels = newZoomPixel.minus( oldZoomPixel );
  11016. deltaZoomPoints = this.deltaPointsFromPixels( deltaZoomPixels, true );
  11017. this.centerSpringX.shiftBy( deltaZoomPoints.x );
  11018. this.centerSpringY.shiftBy( deltaZoomPoints.y );
  11019. } else {
  11020. this.zoomPoint = null;
  11021. }
  11022. this.centerSpringX.update();
  11023. this.centerSpringY.update();
  11024. return this.centerSpringX.current.value != oldCenterX ||
  11025. this.centerSpringY.current.value != oldCenterY ||
  11026. this.zoomSpring.current.value != oldZoom;
  11027. },
  11028. /**
  11029. * Convert a delta (translation vector) from pixels coordinates to viewport coordinates
  11030. * @function
  11031. * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
  11032. */
  11033. deltaPixelsFromPoints: function( deltaPoints, current ) {
  11034. return deltaPoints.times(
  11035. this.containerSize.x * this.getZoom( current )
  11036. );
  11037. },
  11038. /**
  11039. * Convert a delta (translation vector) from viewport coordinates to pixels coordinates.
  11040. * @function
  11041. * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
  11042. */
  11043. deltaPointsFromPixels: function( deltaPixels, current ) {
  11044. return deltaPixels.divide(
  11045. this.containerSize.x * this.getZoom( current )
  11046. );
  11047. },
  11048. /**
  11049. * Convert image pixel coordinates to viewport coordinates.
  11050. * @function
  11051. * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
  11052. */
  11053. pixelFromPoint: function( point, current ) {
  11054. var bounds = this.getBounds( current );
  11055. return point.minus(
  11056. bounds.getTopLeft()
  11057. ).times(
  11058. this.containerSize.x / bounds.width
  11059. );
  11060. },
  11061. /**
  11062. * Convert viewport coordinates to image pixel coordinates.
  11063. * @function
  11064. * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
  11065. */
  11066. pointFromPixel: function( pixel, current ) {
  11067. var bounds = this.getBounds( current );
  11068. return pixel.divide(
  11069. this.containerSize.x / bounds.width
  11070. ).plus(
  11071. bounds.getTopLeft()
  11072. );
  11073. },
  11074. /**
  11075. * Translates from OpenSeadragon viewer coordinate system to image coordinate system.
  11076. * This method can be called either by passing X,Y coordinates or an
  11077. * OpenSeadragon.Point
  11078. * @function
  11079. * @param {OpenSeadragon.Point} viewerX the point in viewport coordinate system.
  11080. * @param {Number} viewerX X coordinate in viewport coordinate system.
  11081. * @param {Number} viewerY Y coordinate in viewport coordinate system.
  11082. * @return {OpenSeadragon.Point} a point representing the coordinates in the image.
  11083. */
  11084. viewportToImageCoordinates: function( viewerX, viewerY ) {
  11085. if ( arguments.length == 1 ) {
  11086. //they passed a point instead of individual components
  11087. return this.viewportToImageCoordinates( viewerX.x, viewerX.y );
  11088. }
  11089. return new $.Point( viewerX * this.contentSize.x, viewerY * this.contentSize.y * this.contentAspectX );
  11090. },
  11091. /**
  11092. * Translates from image coordinate system to OpenSeadragon viewer coordinate system
  11093. * This method can be called either by passing X,Y coordinates or an
  11094. * OpenSeadragon.Point
  11095. * @function
  11096. * @param {OpenSeadragon.Point} imageX the point in image coordinate system.
  11097. * @param {Number} imageX X coordinate in image coordinate system.
  11098. * @param {Number} imageY Y coordinate in image coordinate system.
  11099. * @return {OpenSeadragon.Point} a point representing the coordinates in the viewport.
  11100. */
  11101. imageToViewportCoordinates: function( imageX, imageY ) {
  11102. if ( arguments.length == 1 ) {
  11103. //they passed a point instead of individual components
  11104. return this.imageToViewportCoordinates( imageX.x, imageX.y );
  11105. }
  11106. return new $.Point( imageX / this.contentSize.x, imageY / this.contentSize.y / this.contentAspectX );
  11107. },
  11108. /**
  11109. * Translates from a rectangle which describes a portion of the image in
  11110. * pixel coordinates to OpenSeadragon viewport rectangle coordinates.
  11111. * This method can be called either by passing X,Y,width,height or an
  11112. * OpenSeadragon.Rect
  11113. * @function
  11114. * @param {OpenSeadragon.Rect} imageX the rectangle in image coordinate system.
  11115. * @param {Number} imageX the X coordinate of the top left corner of the rectangle
  11116. * in image coordinate system.
  11117. * @param {Number} imageY the Y coordinate of the top left corner of the rectangle
  11118. * in image coordinate system.
  11119. * @param {Number} pixelWidth the width in pixel of the rectangle.
  11120. * @param {Number} pixelHeight the height in pixel of the rectangle.
  11121. */
  11122. imageToViewportRectangle: function( imageX, imageY, pixelWidth, pixelHeight ) {
  11123. var coordA,
  11124. coordB,
  11125. rect;
  11126. if( arguments.length == 1 ) {
  11127. //they passed a rectangle instead of individual components
  11128. rect = imageX;
  11129. return this.imageToViewportRectangle(
  11130. rect.x, rect.y, rect.width, rect.height
  11131. );
  11132. }
  11133. coordA = this.imageToViewportCoordinates(
  11134. imageX, imageY
  11135. );
  11136. coordB = this.imageToViewportCoordinates(
  11137. pixelWidth, pixelHeight
  11138. );
  11139. return new $.Rect(
  11140. coordA.x,
  11141. coordA.y,
  11142. coordB.x,
  11143. coordB.y
  11144. );
  11145. },
  11146. /**
  11147. * Translates from a rectangle which describes a portion of
  11148. * the viewport in point coordinates to image rectangle coordinates.
  11149. * This method can be called either by passing X,Y,width,height or an
  11150. * OpenSeadragon.Rect
  11151. * @function
  11152. * @param {OpenSeadragon.Rect} viewerX the rectangle in viewport coordinate system.
  11153. * @param {Number} viewerX the X coordinate of the top left corner of the rectangle
  11154. * in viewport coordinate system.
  11155. * @param {Number} imageY the Y coordinate of the top left corner of the rectangle
  11156. * in viewport coordinate system.
  11157. * @param {Number} pointWidth the width of the rectangle in viewport coordinate system.
  11158. * @param {Number} pointHeight the height of the rectangle in viewport coordinate system.
  11159. */
  11160. viewportToImageRectangle: function( viewerX, viewerY, pointWidth, pointHeight ) {
  11161. var coordA,
  11162. coordB,
  11163. rect;
  11164. if ( arguments.length == 1 ) {
  11165. //they passed a rectangle instead of individual components
  11166. rect = viewerX;
  11167. return this.viewportToImageRectangle(
  11168. rect.x, rect.y, rect.width, rect.height
  11169. );
  11170. }
  11171. coordA = this.viewportToImageCoordinates( viewerX, viewerY );
  11172. coordB = this.viewportToImageCoordinates( pointWidth, pointHeight );
  11173. return new $.Rect(
  11174. coordA.x,
  11175. coordA.y,
  11176. coordB.x,
  11177. coordB.y
  11178. );
  11179. },
  11180. /**
  11181. * Convert pixel coordinates relative to the viewer element to image
  11182. * coordinates.
  11183. * @param {OpenSeadragon.Point} pixel
  11184. * @returns {OpenSeadragon.Point}
  11185. */
  11186. viewerElementToImageCoordinates: function( pixel ) {
  11187. var point = this.pointFromPixel( pixel, true );
  11188. return this.viewportToImageCoordinates( point );
  11189. },
  11190. /**
  11191. * Convert pixel coordinates relative to the image to
  11192. * viewer element coordinates.
  11193. * @param {OpenSeadragon.Point} point
  11194. * @returns {OpenSeadragon.Point}
  11195. */
  11196. imageToViewerElementCoordinates: function( point ) {
  11197. var pixel = this.pixelFromPoint( point, true );
  11198. return this.imageToViewportCoordinates( pixel );
  11199. },
  11200. /**
  11201. * Convert pixel coordinates relative to the window to image coordinates.
  11202. * @param {OpenSeadragon.Point} pixel
  11203. * @returns {OpenSeadragon.Point}
  11204. */
  11205. windowToImageCoordinates: function( pixel ) {
  11206. var viewerCoordinates = pixel.minus(
  11207. OpenSeadragon.getElementPosition( this.viewer.element ));
  11208. return this.viewerElementToImageCoordinates( viewerCoordinates );
  11209. },
  11210. /**
  11211. * Convert image coordinates to pixel coordinates relative to the window.
  11212. * @param {OpenSeadragon.Point} pixel
  11213. * @returns {OpenSeadragon.Point}
  11214. */
  11215. imageToWindowCoordinates: function( pixel ) {
  11216. var viewerCoordinates = this.imageToViewerElementCoordinates( pixel );
  11217. return viewerCoordinates.plus(
  11218. OpenSeadragon.getElementPosition( this.viewer.element ));
  11219. },
  11220. /**
  11221. * Convert pixel coordinates relative to the viewer element to viewport
  11222. * coordinates.
  11223. * @param {OpenSeadragon.Point} pixel
  11224. * @returns {OpenSeadragon.Point}
  11225. */
  11226. viewerElementToViewportCoordinates: function( pixel ) {
  11227. return this.pointFromPixel( pixel, true );
  11228. },
  11229. /**
  11230. * Convert viewport coordinates to pixel coordinates relative to the
  11231. * viewer element.
  11232. * @param {OpenSeadragon.Point} point
  11233. * @returns {OpenSeadragon.Point}
  11234. */
  11235. viewportToViewerElementCoordinates: function( point ) {
  11236. return this.pixelFromPoint( point, true );
  11237. },
  11238. /**
  11239. * Convert pixel coordinates relative to the window to viewport coordinates.
  11240. * @param {OpenSeadragon.Point} pixel
  11241. * @returns {OpenSeadragon.Point}
  11242. */
  11243. windowToViewportCoordinates: function( pixel ) {
  11244. var viewerCoordinates = pixel.minus(
  11245. OpenSeadragon.getElementPosition( this.viewer.element ));
  11246. return this.viewerElementToViewportCoordinates( viewerCoordinates );
  11247. },
  11248. /**
  11249. * Convert viewport coordinates to pixel coordinates relative to the window.
  11250. * @param {OpenSeadragon.Point} point
  11251. * @returns {OpenSeadragon.Point}
  11252. */
  11253. viewportToWindowCoordinates: function( point ) {
  11254. var viewerCoordinates = this.viewportToViewerElementCoordinates( point );
  11255. return viewerCoordinates.plus(
  11256. OpenSeadragon.getElementPosition( this.viewer.element ));
  11257. },
  11258. /**
  11259. * Convert a viewport zoom to an image zoom.
  11260. * Image zoom: ratio of the original image size to displayed image size.
  11261. * 1 means original image size, 0.5 half size...
  11262. * Viewport zoom: ratio of the displayed image's width to viewport's width.
  11263. * 1 means identical width, 2 means image's width is twice the viewport's width...
  11264. * @function
  11265. * @param {Number} viewportZoom The viewport zoom
  11266. * target zoom.
  11267. * @returns {Number} imageZoom The image zoom
  11268. */
  11269. viewportToImageZoom: function( viewportZoom ) {
  11270. var imageWidth = this.viewer.source.dimensions.x;
  11271. var containerWidth = this.getContainerSize().x;
  11272. var viewportToImageZoomRatio = containerWidth / imageWidth;
  11273. return viewportZoom * viewportToImageZoomRatio;
  11274. },
  11275. /**
  11276. * Convert an image zoom to a viewport zoom.
  11277. * Image zoom: ratio of the original image size to displayed image size.
  11278. * 1 means original image size, 0.5 half size...
  11279. * Viewport zoom: ratio of the displayed image's width to viewport's width.
  11280. * 1 means identical width, 2 means image's width is twice the viewport's width...
  11281. * @function
  11282. * @param {Number} imageZoom The image zoom
  11283. * target zoom.
  11284. * @returns {Number} viewportZoom The viewport zoom
  11285. */
  11286. imageToViewportZoom: function( imageZoom ) {
  11287. var imageWidth = this.viewer.source.dimensions.x;
  11288. var containerWidth = this.getContainerSize().x;
  11289. var viewportToImageZoomRatio = imageWidth / containerWidth;
  11290. return imageZoom * viewportToImageZoomRatio;
  11291. }
  11292. };
  11293. }( OpenSeadragon ));