diff --git a/plugins/arIiifPlugin/INSTALL.md b/plugins/arIiifPlugin/INSTALL.md new file mode 100644 index 0000000000..2cdaee3229 --- /dev/null +++ b/plugins/arIiifPlugin/INSTALL.md @@ -0,0 +1,420 @@ +# Installation Guide for arIiifPlugin + +This guide provides detailed instructions for installing the IIIF Image Carousel plugin in AtoM. + +## Prerequisites + +- AtoM 2.x or later installed at `/usr/share/nginx/atom` (or adjust paths accordingly) +- Root or sudo access to the server +- PHP 5.6+ (PHP 7.x recommended) +- IIIF-compliant image server (optional but recommended) + +## Quick Installation + +### Option 1: Automated Installation (Recommended) + +```bash +# Navigate to the plugin directory +cd arIiifPlugin + +# Run the installation script +sudo ./install.sh +``` + +The script will: +- Copy plugin files to AtoM plugins directory +- Download and install OpenSeadragon +- Set correct permissions +- Enable the plugin +- Clear AtoM cache +- Restart services + +### Option 2: Manual Installation + +Follow these steps if the automated installation doesn't work or you need more control: + +#### 1. Copy Plugin Files + +```bash +# Copy the plugin to AtoM plugins directory +sudo cp -r arIiifPlugin /usr/share/nginx/atom/plugins/ + +# Set correct ownership +sudo chown -R www-data:www-data /usr/share/nginx/atom/plugins/arIiifPlugin + +# Set correct permissions +sudo chmod -R 755 /usr/share/nginx/atom/plugins/arIiifPlugin +``` + +#### 2. Install OpenSeadragon + +```bash +# Download OpenSeadragon +cd /tmp +wget https://github.com/openseadragon/openseadragon/releases/download/v4.1.0/openseadragon-bin-4.1.0.zip + +# Extract +unzip openseadragon-bin-4.1.0.zip + +# Copy to plugin vendor directory +sudo cp openseadragon-bin-4.1.0/openseadragon.min.js \ + /usr/share/nginx/atom/plugins/arIiifPlugin/vendor/openseadragon/ + +sudo cp -r openseadragon-bin-4.1.0/images/* \ + /usr/share/nginx/atom/plugins/arIiifPlugin/vendor/openseadragon/images/ + +# Set permissions +sudo chown -R www-data:www-data /usr/share/nginx/atom/plugins/arIiifPlugin/vendor +sudo chmod -R 755 /usr/share/nginx/atom/plugins/arIiifPlugin/vendor +``` + +#### 3. Enable Plugin in AtoM + +```bash +cd /usr/share/nginx/atom + +# Enable the plugin +sudo -u www-data php symfony tools:enable-plugin arIiifPlugin + +# Verify plugin is enabled +sudo -u www-data php symfony tools:list-plugins +``` + +#### 4. Configure Plugin + +Edit your AtoM configuration: + +```bash +sudo nano /usr/share/nginx/atom/apps/qubit/config/app.yml +``` + +Add the IIIF configuration (adjust as needed): + +```yaml +all: + iiif: + # Your IIIF Image Server base URL + base_url: https://your-iiif-server.com/iiif/2 + + # IIIF API version + api_version: 2 + + # Enable IIIF Presentation API manifests + enable_manifests: true + + # Carousel settings + carousel: + auto_rotate: true + rotate_interval: 5000 + show_navigation: true + show_thumbnails: false + viewer_height: 600 + + # Viewer settings + viewer: + enable_zoom: true + enable_rotation: true + enable_fullscreen: true + max_zoom_level: 4 +``` + +#### 5. Clear Cache and Restart Services + +```bash +# Clear AtoM cache +sudo -u www-data php symfony cc + +# Clear symfony cache +sudo rm -rf /usr/share/nginx/atom/cache/* + +# Restart PHP-FPM (adjust version as needed) +sudo systemctl restart php7.4-fpm + +# Restart Nginx +sudo systemctl restart nginx +``` + +## Post-Installation Configuration + +### 1. Setting up IIIF Image Server (Cantaloupe Example) + +If you don't have a IIIF image server, here's how to set up Cantaloupe: + +```bash +# Install Java (if not already installed) +sudo apt-get update +sudo apt-get install -y openjdk-11-jre-headless + +# Download Cantaloupe +cd /opt +sudo wget https://github.com/cantaloupe-project/cantaloupe/releases/download/v5.0.5/cantaloupe-5.0.5.zip +sudo unzip cantaloupe-5.0.5.zip +cd cantaloupe-5.0.5 + +# Create configuration +sudo cp cantaloupe.properties.sample cantaloupe.properties +sudo nano cantaloupe.properties +``` + +Edit these key settings in `cantaloupe.properties`: + +```properties +# HTTP port +http.port = 8182 + +# Enable IIIF Image API 2.0 +endpoint.iiif.2.enabled = true + +# Disable IIIF Image API 3.0 (unless needed) +endpoint.iiif.3.enabled = false + +# Set source for images +FilesystemSource.BasicLookupStrategy.path_prefix = /usr/share/nginx/atom/uploads/ + +# Enable caching +cache.server.derivative.enabled = true +cache.server.derivative = FilesystemCache +FilesystemCache.pathname = /var/cache/cantaloupe +``` + +Create systemd service: + +```bash +sudo nano /etc/systemd/system/cantaloupe.service +``` + +```ini +[Unit] +Description=Cantaloupe IIIF Image Server +After=network.target + +[Service] +Type=simple +User=www-data +WorkingDirectory=/opt/cantaloupe-5.0.5 +ExecStart=/usr/bin/java -Dcantaloupe.config=/opt/cantaloupe-5.0.5/cantaloupe.properties -Xmx2g -jar /opt/cantaloupe-5.0.5/cantaloupe-5.0.5.jar +Restart=on-failure +RestartSec=10 + +[Install] +WantedBy=multi-user.target +``` + +Start and enable: + +```bash +# Create cache directory +sudo mkdir -p /var/cache/cantaloupe +sudo chown www-data:www-data /var/cache/cantaloupe + +# Start service +sudo systemctl daemon-reload +sudo systemctl enable cantaloupe +sudo systemctl start cantaloupe + +# Check status +sudo systemctl status cantaloupe +``` + +Update `app.yml` with Cantaloupe URL: + +```yaml +all: + iiif: + base_url: http://localhost:8182/iiif/2 +``` + +### 2. Configure Nginx Proxy (Optional but Recommended) + +To serve IIIF over HTTPS alongside AtoM: + +```bash +sudo nano /etc/nginx/sites-available/atom +``` + +Add inside the server block: + +```nginx +# IIIF Image Server Proxy +location /iiif/2/ { + proxy_pass http://localhost:8182/iiif/2/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # CORS headers + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'Origin, X-Requested-With, Content-Type, Accept' always; + + if ($request_method = 'OPTIONS') { + return 204; + } +} +``` + +Test and reload: + +```bash +sudo nginx -t +sudo systemctl reload nginx +``` + +Update `app.yml`: + +```yaml +all: + iiif: + base_url: https://your-atom-domain.com/iiif/2 +``` + +## Verification + +### 1. Check Plugin Status + +```bash +cd /usr/share/nginx/atom +sudo -u www-data php symfony tools:list-plugins +``` + +You should see `arIiifPlugin` in the enabled plugins list. + +### 2. Check Files + +```bash +# Check plugin directory +ls -la /usr/share/nginx/atom/plugins/arIiifPlugin/ + +# Check OpenSeadragon +ls -la /usr/share/nginx/atom/plugins/arIiifPlugin/vendor/openseadragon/ + +# Check assets +ls -la /usr/share/nginx/atom/plugins/arIiifPlugin/js/ +ls -la /usr/share/nginx/atom/plugins/arIiifPlugin/css/ +``` + +### 3. Test IIIF Server (if installed) + +```bash +# Test Cantaloupe is running +curl http://localhost:8182/iiif/2/ + +# Should return JSON with IIIF server info +``` + +### 4. Test in Browser + +1. Log into your AtoM instance +2. Navigate to an information object with digital objects +3. Add the component to a template (see Usage section in README.md) +4. View the page to see the carousel + +## Troubleshooting Installation + +### Plugin Not Listed + +```bash +# Check plugin directory exists +ls -la /usr/share/nginx/atom/plugins/ | grep arIiif + +# Check permissions +ls -la /usr/share/nginx/atom/plugins/arIiifPlugin/ + +# Try re-enabling +sudo -u www-data php symfony tools:enable-plugin arIiifPlugin +``` + +### OpenSeadragon Not Loading + +```bash +# Verify OpenSeadragon files exist +ls -la /usr/share/nginx/atom/plugins/arIiifPlugin/vendor/openseadragon/openseadragon.min.js + +# Check file permissions +sudo chmod 755 /usr/share/nginx/atom/plugins/arIiifPlugin/vendor/openseadragon/openseadragon.min.js +``` + +### Cache Issues + +```bash +# Clear all caches thoroughly +cd /usr/share/nginx/atom +sudo -u www-data php symfony cc +sudo rm -rf cache/* +sudo -u www-data php symfony tools:clear-cache + +# Restart services +sudo systemctl restart php7.4-fpm nginx +``` + +### Permission Issues + +```bash +# Fix all permissions +sudo chown -R www-data:www-data /usr/share/nginx/atom/plugins/arIiifPlugin +sudo chmod -R 755 /usr/share/nginx/atom/plugins/arIiifPlugin + +# Fix web directory permissions +sudo chown -R www-data:www-data /usr/share/nginx/atom/ +``` + +### IIIF Server Connection Issues + +```bash +# Test local connection +curl http://localhost:8182/iiif/2/ + +# Check if Cantaloupe is running +sudo systemctl status cantaloupe + +# Check logs +sudo journalctl -u cantaloupe -f +``` + +### PHP Errors + +```bash +# Check PHP error log +sudo tail -f /var/log/php7.4-fpm.log + +# Check Nginx error log +sudo tail -f /var/log/nginx/error.log + +# Check AtoM logs +tail -f /usr/share/nginx/atom/log/*.log +``` + +## Uninstallation + +If you need to remove the plugin: + +```bash +# Disable plugin +cd /usr/share/nginx/atom +sudo -u www-data php symfony tools:disable-plugin arIiifPlugin + +# Remove plugin files +sudo rm -rf /usr/share/nginx/atom/plugins/arIiifPlugin + +# Clear cache +sudo -u www-data php symfony cc + +# Restart services +sudo systemctl restart php7.4-fpm nginx +``` + +## Getting Help + +- Check the README.md in the plugin directory +- Review AtoM documentation: https://www.accesstomemory.org/docs/ +- IIIF specifications: https://iiif.io/ +- OpenSeadragon documentation: https://openseadragon.github.io/ + +## Next Steps + +After successful installation, see README.md for: +- Usage examples +- Configuration options +- Integration with your templates +- IIIF manifest generation diff --git a/plugins/arIiifPlugin/README.md b/plugins/arIiifPlugin/README.md new file mode 100644 index 0000000000..2c447ca19e --- /dev/null +++ b/plugins/arIiifPlugin/README.md @@ -0,0 +1,420 @@ +# arIiifPlugin - IIIF Image Carousel Plugin for AtoM + +A comprehensive IIIF (International Image Interoperability Framework) plugin for Access to Memory (AtoM) that provides rotating image carousels, deep zoom viewers, and IIIF Presentation API manifest generation. + +## Features + +- **Auto-rotating Image Carousel** with configurable intervals +- **OpenSeadragon Integration** for deep zoom and pan capabilities +- **IIIF Presentation API 2.1** manifest generation +- **Multiple viewing modes**: carousel and single viewer +- **Navigation controls**: previous, next, play/pause +- **Keyboard navigation** (arrow keys) +- **Optional thumbnail strip** +- **Fully responsive design** +- **Configurable via app.yml** + +## Requirements + +- AtoM 2.x or later (Symfony 1.4) +- PHP 5.6+ (7.x recommended) +- IIIF-compliant image server (Cantaloupe, IIPImage, Loris, etc.) +- OpenSeadragon library (included) + +## Installation + +### 1. Copy Plugin to AtoM + +```bash +# Copy the plugin to the AtoM plugins directory +sudo cp -r arIiifPlugin /usr/share/nginx/atom/plugins/ + +# Set correct permissions +sudo chown -R www-data:www-data /usr/share/nginx/atom/plugins/arIiifPlugin +sudo chmod -R 755 /usr/share/nginx/atom/plugins/arIiifPlugin +``` + +### 2. Download OpenSeadragon + +```bash +# Download OpenSeadragon +cd /tmp +wget https://github.com/openseadragon/openseadragon/releases/download/v4.1.0/openseadragon-bin-4.1.0.zip +unzip openseadragon-bin-4.1.0.zip + +# Copy to plugin vendor directory +sudo cp openseadragon-bin-4.1.0/openseadragon.min.js /usr/share/nginx/atom/plugins/arIiifPlugin/vendor/openseadragon/ +sudo cp -r openseadragon-bin-4.1.0/images/* /usr/share/nginx/atom/plugins/arIiifPlugin/vendor/openseadragon/images/ + +# Set permissions +sudo chown -R www-data:www-data /usr/share/nginx/atom/plugins/arIiifPlugin/vendor +``` + +### 3. Configure Plugin + +Edit your AtoM configuration to include the IIIF server URL: + +```bash +sudo nano /usr/share/nginx/atom/apps/qubit/config/app.yml +``` + +Add or modify the following configuration: + +```yaml +all: + iiif: + # Base URL for your IIIF Image Server + base_url: https://your-iiif-server.com/iiif/2 + + # IIIF API version (2 or 3) + api_version: 2 + + # Image server type + server_type: cantaloupe + + # Enable IIIF Presentation API manifest generation + enable_manifests: true + + # Carousel default settings + carousel: + auto_rotate: true + rotate_interval: 5000 + show_navigation: true + show_thumbnails: false + viewer_height: 600 + + # Viewer settings + viewer: + enable_zoom: true + enable_rotation: true + enable_fullscreen: true + max_zoom_level: 4 +``` + +### 4. Enable Plugin in AtoM + +```bash +# Enable the plugin +sudo php /usr/share/nginx/atom/symfony tools:enable-plugin arIiifPlugin + +# Clear cache +sudo php /usr/share/nginx/atom/symfony cc + +# Rebuild assets +sudo php /usr/share/nginx/atom/symfony tools:clear-cache +``` + +### 5. Restart Services + +```bash +sudo systemctl restart php7.4-fpm # Adjust PHP version as needed +sudo systemctl restart nginx +``` + +## Usage + +### Method 1: Using Component in Templates + +#### Display Carousel for Information Object + +In your template file (e.g., `apps/qubit/modules/informationobject/templates/showSuccess.php`): + +```php + $resource, + 'autoRotate' => true, + 'rotateInterval' => 5000, + 'showNavigation' => true, + 'showThumbnails' => true, + 'viewerHeight' => 600 +)); +?> +``` + +#### Display Single Viewer for Digital Object + +```php + $digitalObject, + 'viewerHeight' => 600 +)); +?> +``` + +### Method 2: Custom Implementation + +You can also manually specify IIIF images: + +```php + 'https://iiif.example.com/image/1/info.json', + 'label' => 'First Image' + ), + array( + 'url' => 'https://iiif.example.com/image/2/info.json', + 'label' => 'Second Image' + ) +); + +include_component('arIiifPlugin', 'carousel', array( + 'images' => $images, + 'autoRotate' => true, + 'showThumbnails' => true +)); +?> +``` + +## Configuration Options + +### Carousel Component + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `resource` | object | required | QubitInformationObject or QubitDigitalObject | +| `images` | array | null | Manual array of IIIF image URLs (alternative to resource) | +| `autoRotate` | boolean | true | Enable automatic rotation | +| `rotateInterval` | integer | 5000 | Rotation interval in milliseconds | +| `showNavigation` | boolean | true | Show navigation controls | +| `showThumbnails` | boolean | false | Show thumbnail strip | +| `viewerHeight` | integer | 600 | Height of viewer in pixels | + +### Viewer Component + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `resource` | object | required | QubitDigitalObject | +| `viewerHeight` | integer | 600 | Height of viewer in pixels | + +## IIIF Manifest API + +The plugin provides IIIF Presentation API 2.1 manifests: + +### Information Object Manifest + +``` +GET /iiif/{slug}/manifest +``` + +Example: +``` +https://your-atom-site.com/iiif/my-collection/manifest +``` + +### Digital Object Manifest + +``` +GET /iiif/object/{id}/manifest +``` + +Example: +``` +https://your-atom-site.com/iiif/object/123/manifest +``` + +### Canvas Endpoint + +``` +GET /iiif/{slug}/canvas/{canvas_index} +``` + +## Digital Object Configuration + +### Option 1: Add IIIF Property + +Add a property to your digital object: + +```php +$property = new QubitProperty(); +$property->objectId = $digitalObject->id; +$property->name = 'iiifManifestUrl'; +$property->value = 'https://your-iiif-server.com/iiif/2/image123/info.json'; +$property->save(); +``` + +### Option 2: Auto-generate from File Path + +The plugin can automatically generate IIIF URLs from digital object file paths if your IIIF server is configured in `app.yml`. + +## Setting up IIIF Image Server + +### Recommended: Cantaloupe + +1. **Download Cantaloupe:** +```bash +cd /opt +sudo wget https://github.com/cantaloupe-project/cantaloupe/releases/download/v5.0.5/cantaloupe-5.0.5.zip +sudo unzip cantaloupe-5.0.5.zip +cd cantaloupe-5.0.5 +``` + +2. **Configure Cantaloupe:** +```bash +sudo cp cantaloupe.properties.sample cantaloupe.properties +sudo nano cantaloupe.properties +``` + +Set: +```properties +FilesystemSource.BasicLookupStrategy.path_prefix = /usr/share/nginx/atom/uploads/ +endpoint.iiif.2.enabled = true +endpoint.iiif.3.enabled = false +``` + +3. **Run Cantaloupe:** +```bash +java -Dcantaloupe.config=cantaloupe.properties -Xmx2g -jar cantaloupe-5.0.5.jar +``` + +4. **Create systemd service:** +```bash +sudo nano /etc/systemd/system/cantaloupe.service +``` + +```ini +[Unit] +Description=Cantaloupe IIIF Image Server +After=network.target + +[Service] +Type=simple +User=www-data +WorkingDirectory=/opt/cantaloupe-5.0.5 +ExecStart=/usr/bin/java -Dcantaloupe.config=/opt/cantaloupe-5.0.5/cantaloupe.properties -Xmx2g -jar /opt/cantaloupe-5.0.5/cantaloupe-5.0.5.jar +Restart=on-failure + +[Install] +WantedBy=multi-user.target +``` + +```bash +sudo systemctl enable cantaloupe +sudo systemctl start cantaloupe +``` + +## Troubleshooting + +### Images Not Loading + +1. **Check IIIF server is running:** +```bash +curl http://localhost:8182/iiif/2/ +``` + +2. **Verify CORS headers** on IIIF server + +3. **Check browser console** for errors + +4. **Verify file permissions:** +```bash +sudo ls -la /usr/share/nginx/atom/uploads/ +``` + +### OpenSeadragon Not Found + +Check that OpenSeadragon files exist: +```bash +ls -la /usr/share/nginx/atom/plugins/arIiifPlugin/vendor/openseadragon/ +``` + +### Plugin Not Enabled + +```bash +# Check enabled plugins +php /usr/share/nginx/atom/symfony tools:list-plugins + +# Enable if needed +sudo php /usr/share/nginx/atom/symfony tools:enable-plugin arIiifPlugin +sudo php /usr/share/nginx/atom/symfony cc +``` + +### Clear Cache Issues + +```bash +# Clear all caches +sudo php /usr/share/nginx/atom/symfony cc +sudo rm -rf /usr/share/nginx/atom/cache/* +sudo php /usr/share/nginx/atom/symfony tools:clear-cache + +# Restart services +sudo systemctl restart php7.4-fpm +sudo systemctl restart nginx +``` + +## File Structure + +``` +arIiifPlugin/ +├── config/ +│ ├── arIiifPluginConfiguration.class.php +│ └── app.yml +├── css/ +│ └── iiif-carousel.css +├── js/ +│ └── iiif-carousel.js +├── lib/ +│ └── arIiifPluginComponents.class.php +├── modules/ +│ ├── arIiifPlugin/ +│ │ └── templates/ +│ │ ├── _carousel.php +│ │ └── _viewer.php +│ └── iiif/ +│ └── actions/ +│ └── actions.class.php +├── vendor/ +│ └── openseadragon/ +│ ├── openseadragon.min.js +│ └── images/ +└── README.md +``` + +## Example Integration + +Replace the default digital object display in `showSuccess.php`: + +```php +getDigitalObjectCount() > 0): ?> +
+

+ + $resource, + 'showThumbnails' => true, + 'autoRotate' => true + )); + ?> + +
+ +``` + +## Browser Support + +- Chrome 90+ +- Firefox 88+ +- Safari 14+ +- Edge 90+ + +## License + +AGPL-3.0 (same as AtoM) + +## Support + +- AtoM Documentation: https://www.accesstomemory.org/docs/ +- IIIF Specifications: https://iiif.io/ +- OpenSeadragon: https://openseadragon.github.io/ + +## Credits + +- Built for Access to Memory (AtoM) +- Uses OpenSeadragon for deep zoom +- Implements IIIF Image API and Presentation API diff --git a/plugins/arIiifPlugin/config/app.yml b/plugins/arIiifPlugin/config/app.yml new file mode 100644 index 0000000000..0c72c28298 --- /dev/null +++ b/plugins/arIiifPlugin/config/app.yml @@ -0,0 +1,39 @@ +# arIiifPlugin application configuration + +all: + # IIIF Image Server Configuration + iiif: + # Base URL for your IIIF Image Server + # Example: https://iiif.example.com/iiif/2 + base_url: ~ + + # IIIF API version (2 or 3) + api_version: 2 + + # Image server type (cantaloupe, iipimage, loris, etc.) + server_type: cantaloupe + + # Enable IIIF Presentation API manifest generation + enable_manifests: true + + # Carousel default settings + carousel: + auto_rotate: true + rotate_interval: 5000 # milliseconds + show_navigation: true + show_thumbnails: false + viewer_height: 600 # pixels + + # Viewer settings + viewer: + enable_zoom: true + enable_rotation: true + enable_fullscreen: true + max_zoom_level: 4 + + # Thumbnail settings + thumbnail: + width: 150 + height: ~ # null = auto + quality: default + format: jpg diff --git a/plugins/arIiifPlugin/config/arIiifPluginConfiguration.class.php b/plugins/arIiifPlugin/config/arIiifPluginConfiguration.class.php new file mode 100644 index 0000000000..c3434af8b0 --- /dev/null +++ b/plugins/arIiifPlugin/config/arIiifPluginConfiguration.class.php @@ -0,0 +1,83 @@ +. + */ + +/** + * arIiifPlugin configuration. + * + * IIIF Image Carousel Plugin for AtoM + * Modified by Johan Pieterse The Archive and Heritage Group + */ +class arIiifPluginConfiguration extends sfPluginConfiguration +{ + // Summary and version + public static $summary = 'IIIF Image Carousel and Viewer plugin for AtoM'; + public static $version = '1.0.0'; + + /** + * Plugin installation hook. + */ + public function contextLoadFactories() + { + // Nothing to do here for now + } + + /** + * Initialize plugin. + */ + public function initialize() + { + $this->dispatcher->connect('routing.load_configuration', [$this, 'listenToRoutingLoadConfigurationEvent']); + } + + /** + * Listen to routing.load_configuration event. + * + * @param sfEvent $event + */ + public function listenToRoutingLoadConfigurationEvent(sfEvent $event) + { + $routing = $event->getSubject(); + + // Load plugin routing rules + $routing->prependRoute('iiif_manifest', new sfRoute( + '/iiif/:slug/manifest', + ['module' => 'iiif', 'action' => 'manifest'] + )); + + $routing->prependRoute('iiif_object_manifest', new sfRoute( + '/iiif/object/:id/manifest', + ['module' => 'iiif', 'action' => 'objectManifest'] + )); + + $routing->prependRoute('iiif_canvas', new sfRoute( + '/iiif/:slug/canvas/:canvas', + ['module' => 'iiif', 'action' => 'canvas'] + )); + } + + /** + * Establish plugin version. + * + * @return string + */ + public static function getVersion() + { + return self::$version; + } +} \ No newline at end of file diff --git a/plugins/arIiifPlugin/config/iiif.yml b/plugins/arIiifPlugin/config/iiif.yml new file mode 100644 index 0000000000..3ef43d8fa7 --- /dev/null +++ b/plugins/arIiifPlugin/config/iiif.yml @@ -0,0 +1,27 @@ +# arIiifPlugin IIIF Configuration +# This file is processed by sfDefineEnvironmentConfigHandler + +# IIIF Image Server Configuration +iiif_base_url: ~ +iiif_api_version: 2 +iiif_server_type: cantaloupe +iiif_enable_manifests: true + +# Carousel Settings +iiif_carousel_auto_rotate: true +iiif_carousel_rotate_interval: 5000 +iiif_carousel_show_navigation: true +iiif_carousel_show_thumbnails: false +iiif_carousel_viewer_height: 600 + +# Viewer Settings +iiif_viewer_enable_zoom: true +iiif_viewer_enable_rotation: true +iiif_viewer_enable_fullscreen: true +iiif_viewer_max_zoom_level: 4 + +# Thumbnail Settings +iiif_thumbnail_width: 150 +iiif_thumbnail_height: ~ +iiif_thumbnail_quality: default +iiif_thumbnail_format: jpg \ No newline at end of file diff --git a/plugins/arIiifPlugin/css/iiif-carousel.css b/plugins/arIiifPlugin/css/iiif-carousel.css new file mode 100644 index 0000000000..118927d140 --- /dev/null +++ b/plugins/arIiifPlugin/css/iiif-carousel.css @@ -0,0 +1,209 @@ +/** + * IIIF Image Carousel Styles for AtoM + */ + +.iiif-carousel-wrapper { + position: relative; + width: 100%; + background: #000; + border-radius: 4px; + overflow: hidden; +} + +.iiif-viewer-container { + width: 100%; + height: 600px; + background: #1a1a1a; + position: relative; +} + +/* Navigation Controls */ +.iiif-carousel-nav { + position: absolute; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + display: flex; + align-items: center; + gap: 15px; + background: rgba(0, 0, 0, 0.7); + padding: 10px 20px; + border-radius: 25px; + z-index: 1000; +} + +.iiif-nav-btn { + background: transparent; + border: 2px solid #fff; + color: #fff; + width: 40px; + height: 40px; + border-radius: 50%; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s ease; + font-size: 18px; +} + +.iiif-nav-btn:hover { + background: #fff; + color: #000; + transform: scale(1.1); +} + +.iiif-nav-btn:active { + transform: scale(0.95); +} + +.iiif-carousel-counter { + color: #fff; + font-size: 14px; + font-weight: 500; + padding: 0 10px; + white-space: nowrap; +} + +.iiif-play-pause { + background: transparent; + border: 2px solid #fff; + color: #fff; + width: 40px; + height: 40px; + border-radius: 50%; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s ease; + font-size: 16px; +} + +.iiif-play-pause:hover { + background: #fff; + color: #000; +} + +/* Thumbnails */ +.iiif-carousel-thumbnails { + display: flex; + gap: 10px; + padding: 15px; + background: #2a2a2a; + overflow-x: auto; + overflow-y: hidden; +} + +.iiif-thumbnail { + flex-shrink: 0; + width: 120px; + height: 80px; + cursor: pointer; + border: 3px solid transparent; + border-radius: 4px; + overflow: hidden; + transition: all 0.3s ease; + opacity: 0.6; +} + +.iiif-thumbnail:hover { + opacity: 1; + border-color: #4a90e2; + transform: scale(1.05); +} + +.iiif-thumbnail.active { + opacity: 1; + border-color: #fff; +} + +.iiif-thumbnail img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} + +/* Icon styles (assuming Font Awesome or similar) */ +.icon-left-arrow::before { + content: '‹'; + font-size: 24px; +} + +.icon-right-arrow::before { + content: '›'; + font-size: 24px; +} + +.icon-pause::before { + content: '❚❚'; + font-size: 12px; +} + +.icon-play::before { + content: '▶'; + font-size: 14px; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .iiif-viewer-container { + height: 400px; + } + + .iiif-carousel-nav { + bottom: 10px; + padding: 8px 15px; + gap: 10px; + } + + .iiif-nav-btn, + .iiif-play-pause { + width: 35px; + height: 35px; + font-size: 16px; + } + + .iiif-carousel-counter { + font-size: 12px; + } + + .iiif-thumbnail { + width: 80px; + height: 60px; + } +} + +@media (max-width: 480px) { + .iiif-viewer-container { + height: 300px; + } + + .iiif-carousel-thumbnails { + padding: 10px; + gap: 8px; + } +} + +/* Loading state */ +.iiif-carousel-wrapper.loading::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 50px; + height: 50px; + margin: -25px 0 0 -25px; + border: 4px solid rgba(255, 255, 255, 0.3); + border-top-color: #fff; + border-radius: 50%; + animation: iiif-spin 1s linear infinite; + z-index: 999; +} + +@keyframes iiif-spin { + to { + transform: rotate(360deg); + } +} diff --git a/plugins/arIiifPlugin/install.sh b/plugins/arIiifPlugin/install.sh new file mode 100644 index 0000000000..a53b71c895 --- /dev/null +++ b/plugins/arIiifPlugin/install.sh @@ -0,0 +1,139 @@ +#!/bin/bash + +# arIiifPlugin Installation Script for AtoM +# This script automates the installation of the IIIF plugin + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Configuration +ATOM_PATH="/usr/share/nginx/atom" +PLUGIN_NAME="arIiifPlugin" +PLUGIN_PATH="$ATOM_PATH/plugins/$PLUGIN_NAME" +OPENSEADRAGON_VERSION="4.1.0" + +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}arIiifPlugin Installation Script${NC}" +echo -e "${GREEN}========================================${NC}" +echo "" + +# Check if running as root or with sudo +if [ "$EUID" -ne 0 ]; then + echo -e "${RED}Error: This script must be run as root or with sudo${NC}" + exit 1 +fi + +# Check if AtoM directory exists +if [ ! -d "$ATOM_PATH" ]; then + echo -e "${RED}Error: AtoM directory not found at $ATOM_PATH${NC}" + echo "Please specify the correct AtoM path by editing this script" + exit 1 +fi + +echo -e "${YELLOW}Step 1: Copying plugin files...${NC}" +if [ -d "$PLUGIN_PATH" ]; then + echo -e "${YELLOW}Plugin directory already exists. Backing up...${NC}" + mv "$PLUGIN_PATH" "${PLUGIN_PATH}.backup.$(date +%Y%m%d_%H%M%S)" +fi + +cp -r "$(dirname "$0")" "$PLUGIN_PATH" +echo -e "${GREEN}✓ Plugin files copied${NC}" + +echo "" +echo -e "${YELLOW}Step 2: Setting permissions...${NC}" +chown -R www-data:www-data "$PLUGIN_PATH" +chmod -R 755 "$PLUGIN_PATH" +echo -e "${GREEN}✓ Permissions set${NC}" + +echo "" +echo -e "${YELLOW}Step 3: Downloading OpenSeadragon...${NC}" +cd /tmp + +if [ ! -f "openseadragon-bin-${OPENSEADRAGON_VERSION}.zip" ]; then + wget -q "https://github.com/openseadragon/openseadragon/releases/download/v${OPENSEADRAGON_VERSION}/openseadragon-bin-${OPENSEADRAGON_VERSION}.zip" + if [ $? -ne 0 ]; then + echo -e "${YELLOW}Warning: Could not download OpenSeadragon automatically${NC}" + echo "Please download it manually from: https://openseadragon.github.io/" + else + unzip -q "openseadragon-bin-${OPENSEADRAGON_VERSION}.zip" + + # Copy OpenSeadragon files + mkdir -p "$PLUGIN_PATH/vendor/openseadragon/images" + cp "openseadragon-bin-${OPENSEADRAGON_VERSION}/openseadragon.min.js" "$PLUGIN_PATH/vendor/openseadragon/" + cp -r "openseadragon-bin-${OPENSEADRAGON_VERSION}/images/"* "$PLUGIN_PATH/vendor/openseadragon/images/" + + # Set permissions + chown -R www-data:www-data "$PLUGIN_PATH/vendor" + chmod -R 755 "$PLUGIN_PATH/vendor" + + echo -e "${GREEN}✓ OpenSeadragon installed${NC}" + fi +else + echo -e "${GREEN}✓ OpenSeadragon already downloaded${NC}" +fi + +echo "" +echo -e "${YELLOW}Step 4: Enabling plugin in AtoM...${NC}" +cd "$ATOM_PATH" + +# Check if plugin is already enabled +if sudo -u www-data php symfony tools:list-plugins | grep -q "$PLUGIN_NAME"; then + echo -e "${GREEN}✓ Plugin already enabled${NC}" +else + sudo -u www-data php symfony tools:enable-plugin "$PLUGIN_NAME" + echo -e "${GREEN}✓ Plugin enabled${NC}" +fi + +echo "" +echo -e "${YELLOW}Step 5: Clearing cache...${NC}" +sudo -u www-data php symfony cc +echo -e "${GREEN}✓ Cache cleared${NC}" + +echo "" +echo -e "${YELLOW}Step 6: Restarting services...${NC}" + +# Detect PHP-FPM version +PHP_FPM_SERVICE=$(systemctl list-units --type=service | grep -o 'php[0-9.]*-fpm' | head -1) + +if [ -n "$PHP_FPM_SERVICE" ]; then + systemctl restart "$PHP_FPM_SERVICE" + echo -e "${GREEN}✓ $PHP_FPM_SERVICE restarted${NC}" +else + echo -e "${YELLOW}Warning: Could not detect PHP-FPM service${NC}" +fi + +if systemctl is-active --quiet nginx; then + systemctl restart nginx + echo -e "${GREEN}✓ Nginx restarted${NC}" +else + echo -e "${YELLOW}Warning: Nginx service not found${NC}" +fi + +echo "" +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}Installation Complete!${NC}" +echo -e "${GREEN}========================================${NC}" +echo "" +echo -e "${YELLOW}Next Steps:${NC}" +echo "1. Configure your IIIF server URL in:" +echo " $ATOM_PATH/apps/qubit/config/app.yml" +echo "" +echo "2. Add the following configuration:" +echo " ${GREEN}all:${NC}" +echo " ${GREEN} iiif:${NC}" +echo " ${GREEN} base_url: https://your-iiif-server.com/iiif/2${NC}" +echo " ${GREEN} enable_manifests: true${NC}" +echo "" +echo "3. Clear cache again:" +echo " ${GREEN}sudo php $ATOM_PATH/symfony cc${NC}" +echo "" +echo "4. Use the component in your templates:" +echo " ${GREEN} \$resource)); ?>${NC}" +echo "" +echo -e "For more information, see: ${GREEN}$PLUGIN_PATH/README.md${NC}" +echo "" diff --git a/plugins/arIiifPlugin/js/iiif-carousel.js b/plugins/arIiifPlugin/js/iiif-carousel.js new file mode 100644 index 0000000000..b7831dff1d --- /dev/null +++ b/plugins/arIiifPlugin/js/iiif-carousel.js @@ -0,0 +1,271 @@ +/** + * IIIF Image Carousel for AtoM + * Rotating IIIF images display using OpenSeadragon + */ + +(function($) { + 'use strict'; + + var IIIFCarousel = function(element, options) { + this.element = element; + this.$element = $(element); + this.options = $.extend({}, IIIFCarousel.DEFAULTS, options); + this.currentIndex = 0; + this.viewer = null; + this.autoRotateInterval = null; + this.init(); + }; + + IIIFCarousel.DEFAULTS = { + autoRotate: true, + rotateInterval: 5000, // 5 seconds + showNavigation: true, + showThumbnails: false, + images: [] // Array of IIIF manifest URLs or info.json URLs + }; + + IIIFCarousel.prototype = { + init: function() { + this.buildCarousel(); + this.initializeViewer(); + this.bindEvents(); + + if (this.options.autoRotate) { + this.startAutoRotate(); + } + }, + + buildCarousel: function() { + var html = ''; + + this.$element.html(html); + this.$viewerContainer = this.$element.find('.iiif-viewer-container'); + this.viewerId = this.$viewerContainer.attr('id'); + }, + + initializeViewer: function() { + if (typeof OpenSeadragon === 'undefined') { + console.error('OpenSeadragon library is required for IIIF viewer'); + return; + } + + this.viewer = OpenSeadragon({ + id: this.viewerId, + prefixUrl: '/plugins/arDominionPlugin/images/openseadragon/', + tileSources: this.getTileSource(0), + showNavigationControl: true, + navigationControlAnchor: OpenSeadragon.ControlAnchor.TOP_RIGHT, + showRotationControl: true, + showHomeControl: true, + showFullPageControl: true, + showZoomControl: true, + sequenceMode: false, + preserveViewport: false, + constrainDuringPan: true, + visibilityRatio: 1.0, + minZoomImageRatio: 0.8, + maxZoomPixelRatio: 2 + }); + + this.loadThumbnails(); + }, + + getTileSource: function(index) { + if (!this.options.images[index]) { + return null; + } + + var imageUrl = this.options.images[index]; + + // Check if it's a full info.json URL or just the base IIIF URL + if (imageUrl.indexOf('info.json') === -1) { + imageUrl = imageUrl + '/info.json'; + } + + return imageUrl; + }, + + loadThumbnails: function() { + if (!this.options.showThumbnails) { + return; + } + + var $thumbnailContainer = this.$element.find('.iiif-carousel-thumbnails'); + var self = this; + + this.options.images.forEach(function(imageUrl, index) { + var thumbnailUrl = imageUrl.replace('/info.json', '') + '/full/150,/0/default.jpg'; + + var $thumb = $('
' + + 'Thumbnail ' + (index + 1) + '' + + '
'); + + $thumb.on('click', function() { + self.goToSlide(index); + }); + + $thumbnailContainer.append($thumb); + }); + }, + + bindEvents: function() { + var self = this; + + this.$element.find('.iiif-prev').on('click', function(e) { + e.preventDefault(); + self.prev(); + }); + + this.$element.find('.iiif-next').on('click', function(e) { + e.preventDefault(); + self.next(); + }); + + this.$element.find('.iiif-play-pause').on('click', function(e) { + e.preventDefault(); + self.toggleAutoRotate(); + }); + + // Keyboard navigation + $(document).on('keydown', function(e) { + if (self.$element.is(':visible')) { + if (e.keyCode === 37) { // Left arrow + self.prev(); + } else if (e.keyCode === 39) { // Right arrow + self.next(); + } + } + }); + }, + + next: function() { + this.currentIndex = (this.currentIndex + 1) % this.options.images.length; + this.updateViewer(); + }, + + prev: function() { + this.currentIndex = (this.currentIndex - 1 + this.options.images.length) % this.options.images.length; + this.updateViewer(); + }, + + goToSlide: function(index) { + this.currentIndex = index; + this.updateViewer(); + }, + + updateViewer: function() { + if (this.viewer) { + this.viewer.open(this.getTileSource(this.currentIndex)); + this.updateCounter(); + this.updateThumbnails(); + } + }, + + updateCounter: function() { + this.$element.find('.current-slide').text(this.currentIndex + 1); + }, + + updateThumbnails: function() { + if (this.options.showThumbnails) { + this.$element.find('.iiif-thumbnail').removeClass('active'); + this.$element.find('.iiif-thumbnail').eq(this.currentIndex).addClass('active'); + } + }, + + startAutoRotate: function() { + var self = this; + this.autoRotateInterval = setInterval(function() { + self.next(); + }, this.options.rotateInterval); + + this.$element.find('.iiif-play-pause .icon-pause').removeClass('icon-pause').addClass('icon-play'); + }, + + stopAutoRotate: function() { + if (this.autoRotateInterval) { + clearInterval(this.autoRotateInterval); + this.autoRotateInterval = null; + } + + this.$element.find('.iiif-play-pause .icon-play').removeClass('icon-play').addClass('icon-pause'); + }, + + toggleAutoRotate: function() { + if (this.autoRotateInterval) { + this.stopAutoRotate(); + } else { + this.startAutoRotate(); + } + }, + + destroy: function() { + this.stopAutoRotate(); + if (this.viewer) { + this.viewer.destroy(); + } + this.$element.empty(); + } + }; + + // jQuery plugin definition + $.fn.iiifCarousel = function(option) { + return this.each(function() { + var $this = $(this); + var data = $this.data('iiif.carousel'); + var options = typeof option === 'object' && option; + + if (!data) { + $this.data('iiif.carousel', (data = new IIIFCarousel(this, options))); + } + + if (typeof option === 'string') { + data[option](); + } + }); + }; + + $.fn.iiifCarousel.Constructor = IIIFCarousel; + +})(jQuery); + +// Initialize on document ready +jQuery(document).ready(function($) { + // Auto-initialize any elements with data-iiif-carousel attribute + $('[data-iiif-carousel]').each(function() { + var $this = $(this); + var images = $this.data('iiif-images') || []; + + $this.iiifCarousel({ + images: images, + autoRotate: $this.data('auto-rotate') !== false, + rotateInterval: $this.data('rotate-interval') || 5000, + showNavigation: $this.data('show-navigation') !== false, + showThumbnails: $this.data('show-thumbnails') === true + }); + }); +}); diff --git a/plugins/arIiifPlugin/lib/arIiifPluginComponents.class.php b/plugins/arIiifPlugin/lib/arIiifPluginComponents.class.php new file mode 100644 index 0000000000..6e7423fc0d --- /dev/null +++ b/plugins/arIiifPlugin/lib/arIiifPluginComponents.class.php @@ -0,0 +1,184 @@ +. + * + * arIiifPlugin components + * + * IIIF Image Carousel Plugin for AtoM + * Modified by Johan Pieterse The Archive and Heritage Group + */ + +class arIiifPluginComponents extends sfComponents +{ + /** + * Display IIIF image carousel. + * + * @param mixed $request + */ + public function executeCarousel($request) + { + if (!isset($this->resource)) { + return sfView::NONE; + } + + $this->images = []; + + if ($this->resource instanceof QubitDigitalObject) { + $this->images = $this->getDigitalObjectIIIFImages($this->resource); + } elseif ($this->resource instanceof QubitInformationObject) { + $this->images = $this->getInformationObjectIIIFImages($this->resource); + } + + if (empty($this->images)) { + return sfView::NONE; + } + + $config = sfConfig::get('app_iiif_carousel', [ + 'auto_rotate' => true, + 'rotate_interval' => 5000, + 'show_navigation' => true, + 'show_thumbnails' => false, + 'viewer_height' => 600, + ]); + + $this->autoRotate = isset($this->autoRotate) ? $this->autoRotate : $config['auto_rotate']; + $this->rotateInterval = isset($this->rotateInterval) ? $this->rotateInterval : $config['rotate_interval']; + $this->showNavigation = isset($this->showNavigation) ? $this->showNavigation : $config['show_navigation']; + $this->showThumbnails = isset($this->showThumbnails) ? $this->showThumbnails : $config['show_thumbnails']; + $this->viewerHeight = isset($this->viewerHeight) ? $this->viewerHeight : $config['viewer_height']; + + $this->carouselId = 'iiif-carousel-'.uniqid(); + $this->addAssets(); + } + + /** + * Display simple IIIF viewer. + * + * @param mixed $request + */ + public function executeViewer($request) + { + if (!isset($this->resource) || !$this->resource instanceof QubitDigitalObject) { + return sfView::NONE; + } + + $images = $this->getDigitalObjectIIIFImages($this->resource); + + if (empty($images)) { + return sfView::NONE; + } + + $this->iiifUrl = $images[0]['url']; + $this->imageLabel = isset($images[0]['label']) ? $images[0]['label'] : ''; + + $config = sfConfig::get('app_iiif_carousel', ['viewer_height' => 600]); + $this->viewerHeight = isset($this->viewerHeight) ? $this->viewerHeight : $config['viewer_height']; + $this->viewerId = 'iiif-viewer-'.uniqid(); + $this->addAssets(); + } + + /** + * Get IIIF images from a digital object. + * + * @param mixed $digitalObject + */ + protected function getDigitalObjectIIIFImages($digitalObject) + { + $images = []; + + // Check for IIIF manifest URL property using Criteria + $criteria = new Criteria(); + $criteria->add(QubitProperty::OBJECT_ID, $digitalObject->id); + $criteria->add(QubitProperty::NAME, 'iiifManifestUrl'); + + foreach (QubitProperty::get($criteria) as $property) { + $images[] = [ + 'url' => $property->value, + 'label' => $digitalObject->name, + 'identifier' => $digitalObject->id, + ]; + + return $images; + } + + // Construct IIIF URL from file path + if (null !== $digitalObject->path) { + $iiifBaseUrl = sfConfig::get('app_iiif_base_url'); + + if (!empty($iiifBaseUrl)) { + $identifier = $this->getIIIFIdentifier($digitalObject); + + $images[] = [ + 'url' => rtrim($iiifBaseUrl, '/').'/'.$identifier.'/info.json', + 'label' => $digitalObject->name, + 'identifier' => $digitalObject->id, + 'path' => $digitalObject->path, + ]; + } + } + + return $images; + } + + /** + * Get IIIF images from an information object. + * + * @param mixed $informationObject + */ + protected function getInformationObjectIIIFImages($informationObject) + { + $images = []; + + $criteria = new Criteria(); + $criteria->add(QubitDigitalObject::OBJECT_ID, $informationObject->id); + $criteria->addAscendingOrderByColumn(QubitDigitalObject::SEQUENCE); + + foreach (QubitDigitalObject::get($criteria) as $digitalObject) { + $digitalObjectImages = $this->getDigitalObjectIIIFImages($digitalObject); + $images = array_merge($images, $digitalObjectImages); + } + + return $images; + } + + /** + * Generate IIIF identifier from digital object. + * + * @param mixed $digitalObject + */ + protected function getIIIFIdentifier($digitalObject) + { + if (null !== $digitalObject->checksum) { + return $digitalObject->checksum; + } + + $filename = pathinfo($digitalObject->path, PATHINFO_FILENAME); + + return preg_replace('/[^a-zA-Z0-9_-]/', '_', $filename); + } + + /** + * Add required CSS and JavaScript assets. + */ + protected function addAssets() + { + $response = $this->getResponse(); + $response->addJavaScript('/plugins/arIiifPlugin/vendor/openseadragon/openseadragon.min.js', 'last'); + $response->addJavaScript('/plugins/arIiifPlugin/js/iiif-carousel.js', 'last'); + $response->addStylesheet('/plugins/arIiifPlugin/css/iiif-carousel.css', 'last'); + } +} diff --git a/plugins/arIiifPlugin/modules/arIiifPlugin/actions/components.class.php b/plugins/arIiifPlugin/modules/arIiifPlugin/actions/components.class.php new file mode 100644 index 0000000000..6e7423fc0d --- /dev/null +++ b/plugins/arIiifPlugin/modules/arIiifPlugin/actions/components.class.php @@ -0,0 +1,184 @@ +. + * + * arIiifPlugin components + * + * IIIF Image Carousel Plugin for AtoM + * Modified by Johan Pieterse The Archive and Heritage Group + */ + +class arIiifPluginComponents extends sfComponents +{ + /** + * Display IIIF image carousel. + * + * @param mixed $request + */ + public function executeCarousel($request) + { + if (!isset($this->resource)) { + return sfView::NONE; + } + + $this->images = []; + + if ($this->resource instanceof QubitDigitalObject) { + $this->images = $this->getDigitalObjectIIIFImages($this->resource); + } elseif ($this->resource instanceof QubitInformationObject) { + $this->images = $this->getInformationObjectIIIFImages($this->resource); + } + + if (empty($this->images)) { + return sfView::NONE; + } + + $config = sfConfig::get('app_iiif_carousel', [ + 'auto_rotate' => true, + 'rotate_interval' => 5000, + 'show_navigation' => true, + 'show_thumbnails' => false, + 'viewer_height' => 600, + ]); + + $this->autoRotate = isset($this->autoRotate) ? $this->autoRotate : $config['auto_rotate']; + $this->rotateInterval = isset($this->rotateInterval) ? $this->rotateInterval : $config['rotate_interval']; + $this->showNavigation = isset($this->showNavigation) ? $this->showNavigation : $config['show_navigation']; + $this->showThumbnails = isset($this->showThumbnails) ? $this->showThumbnails : $config['show_thumbnails']; + $this->viewerHeight = isset($this->viewerHeight) ? $this->viewerHeight : $config['viewer_height']; + + $this->carouselId = 'iiif-carousel-'.uniqid(); + $this->addAssets(); + } + + /** + * Display simple IIIF viewer. + * + * @param mixed $request + */ + public function executeViewer($request) + { + if (!isset($this->resource) || !$this->resource instanceof QubitDigitalObject) { + return sfView::NONE; + } + + $images = $this->getDigitalObjectIIIFImages($this->resource); + + if (empty($images)) { + return sfView::NONE; + } + + $this->iiifUrl = $images[0]['url']; + $this->imageLabel = isset($images[0]['label']) ? $images[0]['label'] : ''; + + $config = sfConfig::get('app_iiif_carousel', ['viewer_height' => 600]); + $this->viewerHeight = isset($this->viewerHeight) ? $this->viewerHeight : $config['viewer_height']; + $this->viewerId = 'iiif-viewer-'.uniqid(); + $this->addAssets(); + } + + /** + * Get IIIF images from a digital object. + * + * @param mixed $digitalObject + */ + protected function getDigitalObjectIIIFImages($digitalObject) + { + $images = []; + + // Check for IIIF manifest URL property using Criteria + $criteria = new Criteria(); + $criteria->add(QubitProperty::OBJECT_ID, $digitalObject->id); + $criteria->add(QubitProperty::NAME, 'iiifManifestUrl'); + + foreach (QubitProperty::get($criteria) as $property) { + $images[] = [ + 'url' => $property->value, + 'label' => $digitalObject->name, + 'identifier' => $digitalObject->id, + ]; + + return $images; + } + + // Construct IIIF URL from file path + if (null !== $digitalObject->path) { + $iiifBaseUrl = sfConfig::get('app_iiif_base_url'); + + if (!empty($iiifBaseUrl)) { + $identifier = $this->getIIIFIdentifier($digitalObject); + + $images[] = [ + 'url' => rtrim($iiifBaseUrl, '/').'/'.$identifier.'/info.json', + 'label' => $digitalObject->name, + 'identifier' => $digitalObject->id, + 'path' => $digitalObject->path, + ]; + } + } + + return $images; + } + + /** + * Get IIIF images from an information object. + * + * @param mixed $informationObject + */ + protected function getInformationObjectIIIFImages($informationObject) + { + $images = []; + + $criteria = new Criteria(); + $criteria->add(QubitDigitalObject::OBJECT_ID, $informationObject->id); + $criteria->addAscendingOrderByColumn(QubitDigitalObject::SEQUENCE); + + foreach (QubitDigitalObject::get($criteria) as $digitalObject) { + $digitalObjectImages = $this->getDigitalObjectIIIFImages($digitalObject); + $images = array_merge($images, $digitalObjectImages); + } + + return $images; + } + + /** + * Generate IIIF identifier from digital object. + * + * @param mixed $digitalObject + */ + protected function getIIIFIdentifier($digitalObject) + { + if (null !== $digitalObject->checksum) { + return $digitalObject->checksum; + } + + $filename = pathinfo($digitalObject->path, PATHINFO_FILENAME); + + return preg_replace('/[^a-zA-Z0-9_-]/', '_', $filename); + } + + /** + * Add required CSS and JavaScript assets. + */ + protected function addAssets() + { + $response = $this->getResponse(); + $response->addJavaScript('/plugins/arIiifPlugin/vendor/openseadragon/openseadragon.min.js', 'last'); + $response->addJavaScript('/plugins/arIiifPlugin/js/iiif-carousel.js', 'last'); + $response->addStylesheet('/plugins/arIiifPlugin/css/iiif-carousel.css', 'last'); + } +} diff --git a/plugins/arIiifPlugin/modules/arIiifPlugin/templates/_carousel.php b/plugins/arIiifPlugin/modules/arIiifPlugin/templates/_carousel.php new file mode 100644 index 0000000000..1c015f4659 --- /dev/null +++ b/plugins/arIiifPlugin/modules/arIiifPlugin/templates/_carousel.php @@ -0,0 +1,96 @@ + + + + + + + +
+ + +
+ diff --git a/plugins/arIiifPlugin/modules/arIiifPlugin/templates/_viewer.php b/plugins/arIiifPlugin/modules/arIiifPlugin/templates/_viewer.php new file mode 100644 index 0000000000..50840bf13e --- /dev/null +++ b/plugins/arIiifPlugin/modules/arIiifPlugin/templates/_viewer.php @@ -0,0 +1,147 @@ +'; ?> + +\n"; + +// Normalize +$clean = preg_replace('#^/uploads/#', '', $raw); +$clean = trim($clean, '/'); + +// Extract +$filename = basename($clean); +$folder = dirname($clean); + +// Encode folder +$encodedFolder = str_replace('/', '_SL_', $folder); + +// Final ID +$encoded = $encodedFolder.'_SL_'.$filename; + +// IIIF URL +$base = rtrim(sfConfig::get('app_base_url'), '/'); +$iiifInfo = $base.'/iiif/2/'.$encoded.'/info.json'; + +// DEBUG dump +echo "\n"; +echo "\n"; +echo "\n"; +echo "\n"; +echo "\n"; +?> + +
+ IIIF DEBUG PANEL
+ RAW:
+ CLEAN:
+ FOLDER:
+ FILE:
+ ENCODED ID:
+ IIIF INFO.JSON: +
+ +
+
+ Initializing IIIF Viewer… +
+
+ + + + + diff --git a/plugins/arIiifPlugin/modules/iiif/actions/actions.class.php b/plugins/arIiifPlugin/modules/iiif/actions/actions.class.php new file mode 100644 index 0000000000..d65adef80b --- /dev/null +++ b/plugins/arIiifPlugin/modules/iiif/actions/actions.class.php @@ -0,0 +1,368 @@ +. + * + * IIIF module actions. + * + * IIIF Image Carousel Plugin for AtoM + * Modified by Johan Pieterse The Archive and Heritage Group + */ + +class iiifActions extends sfActions +{ + /** + * Generate IIIF Presentation API manifest for an information object. + * + * @param sfWebRequest $request + */ + public function executeManifest(sfWebRequest $request) + { + // Get information object by slug + $this->resource = QubitInformationObject::getBySlug($request->slug); + + if (null === $this->resource) { + $this->forward404(); + } + + // Check if manifests are enabled + if (!sfConfig::get('app_iiif_enable_manifests', false)) { + $this->forward404(); + } + + // Get all digital objects for this information object + $this->digitalObjects = $this->getDigitalObjects($this->resource); + + if (empty($this->digitalObjects)) { + $this->forward404(); + } + + // Set response content type + $this->getResponse()->setContentType('application/json'); + $this->getResponse()->setHttpHeader('Access-Control-Allow-Origin', '*'); + + // Build manifest + $manifest = $this->buildManifest($this->resource, $this->digitalObjects); + + $this->getResponse()->setContent(json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + + return sfView::NONE; + } + + /** + * Generate IIIF manifest for a single digital object. + * + * @param sfWebRequest $request + */ + public function executeObjectManifest(sfWebRequest $request) + { + // Get digital object by ID + $this->resource = QubitDigitalObject::getById($request->id); + + if (null === $this->resource) { + $this->forward404(); + } + + // Check if manifests are enabled + if (!sfConfig::get('app_iiif_enable_manifests', false)) { + $this->forward404(); + } + + // Set response content type + $this->getResponse()->setContentType('application/json'); + $this->getResponse()->setHttpHeader('Access-Control-Allow-Origin', '*'); + + // Build manifest for single object + $manifest = $this->buildSingleObjectManifest($this->resource); + + $this->getResponse()->setContent(json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + + return sfView::NONE; + } + + /** + * Canvas endpoint. + * + * @param sfWebRequest $request + */ + public function executeCanvas(sfWebRequest $request) + { + $this->resource = QubitInformationObject::getBySlug($request->slug); + + if (null === $this->resource) { + $this->forward404(); + } + + // Get canvas index + $canvasIndex = (int) $request->canvas; + + // Get digital objects + $digitalObjects = $this->getDigitalObjects($this->resource); + + if (!isset($digitalObjects[$canvasIndex])) { + $this->forward404(); + } + + $this->getResponse()->setContentType('application/json'); + $this->getResponse()->setHttpHeader('Access-Control-Allow-Origin', '*'); + + // Build canvas + $canvas = $this->buildCanvas($digitalObjects[$canvasIndex], $canvasIndex); + + $this->getResponse()->setContent(json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + + return sfView::NONE; + } + + /** + * Get digital objects for an information object. + * + * @param QubitInformationObject $resource + * + * @return array + */ + protected function getDigitalObjects($resource) + { + $digitalObjects = []; + + $criteria = new Criteria(); + $criteria->add(QubitDigitalObject::OBJECT_ID, $resource->id); + $criteria->addAscendingOrderByColumn(QubitDigitalObject::SEQUENCE); + + foreach (QubitDigitalObject::get($criteria) as $digitalObject) { + // Check if this object has a valid path or IIIF URL + if (null !== $digitalObject->path || $this->hasIIIFUrl($digitalObject)) { + $digitalObjects[] = $digitalObject; + } + } + + return $digitalObjects; + } + + /** + * Check if digital object has IIIF URL. + * + * @param QubitDigitalObject $digitalObject + * + * @return bool + */ + protected function hasIIIFUrl($digitalObject) + { + $criteria = new Criteria(); + $criteria->add(QubitProperty::OBJECT_ID, $digitalObject->id); + $criteria->add(QubitProperty::NAME, 'iiifManifestUrl'); + + return QubitProperty::get($criteria)->count() > 0; + } + + /** + * Build IIIF Presentation API 2.1 manifest. + * + * @param QubitInformationObject $resource + * @param array $digitalObjects + * + * @return array + */ + protected function buildManifest($resource, $digitalObjects) + { + $baseUrl = $this->getContext()->getRequest()->getUriPrefix(). + $this->getContext()->getRequest()->getRelativeUrlRoot(); + + $manifestUrl = $baseUrl.'/iiif/'.$resource->slug.'/manifest'; + $iiifBaseUrl = sfConfig::get('app_iiif_base_url'); + + $manifest = [ + '@context' => 'http://iiif.io/api/presentation/2/context.json', + '@id' => $manifestUrl, + '@type' => 'sc:Manifest', + 'label' => $resource->getTitle(['cultureFallback' => true]), + 'metadata' => $this->buildMetadata($resource), + 'description' => strip_tags($resource->getScopeAndContent(['cultureFallback' => true])), + 'attribution' => $resource->getRepository(['cultureFallback' => true]), + 'logo' => $baseUrl.'/uploads/r/repository/logo/logo.png', + 'sequences' => [ + [ + '@type' => 'sc:Sequence', + 'label' => 'Image sequence', + 'canvases' => [], + ], + ], + ]; + + // Build canvases + foreach ($digitalObjects as $index => $digitalObject) { + $manifest['sequences'][0]['canvases'][] = $this->buildCanvas($digitalObject, $index); + } + + return $manifest; + } + + /** + * Build manifest for single digital object. + * + * @param QubitDigitalObject $digitalObject + * + * @return array + */ + protected function buildSingleObjectManifest($digitalObject) + { + $baseUrl = $this->getContext()->getRequest()->getUriPrefix(). + $this->getContext()->getRequest()->getRelativeUrlRoot(); + + $manifestUrl = $baseUrl.'/iiif/object/'.$digitalObject->id.'/manifest'; + + return [ + '@context' => 'http://iiif.io/api/presentation/2/context.json', + '@id' => $manifestUrl, + '@type' => 'sc:Manifest', + 'label' => $digitalObject->name, + 'sequences' => [ + [ + '@type' => 'sc:Sequence', + 'canvases' => [ + $this->buildCanvas($digitalObject, 0), + ], + ], + ], + ]; + } + + /** + * Build canvas for a digital object. + * + * @param QubitDigitalObject $digitalObject + * @param int $index + * + * @return array + */ + protected function buildCanvas($digitalObject, $index) + { + $baseUrl = $this->getContext()->getRequest()->getUriPrefix(). + $this->getContext()->getRequest()->getRelativeUrlRoot(); + + $iiifBaseUrl = sfConfig::get('app_iiif_base_url'); + + // Get IIIF identifier + $identifier = $this->getIIIFIdentifier($digitalObject); + $imageUrl = rtrim($iiifBaseUrl, '/').'/'.$identifier; + + // Get image dimensions (you may need to adjust this based on your setup) + $width = 1000; // Default width + $height = 1000; // Default height + + // Try to get actual dimensions if available + if (file_exists($digitalObject->getAbsolutePath())) { + $imageSize = @getimagesize($digitalObject->getAbsolutePath()); + if (false !== $imageSize) { + $width = $imageSize[0]; + $height = $imageSize[1]; + } + } + + return [ + '@id' => $baseUrl.'/iiif/canvas/'.$digitalObject->id, + '@type' => 'sc:Canvas', + 'label' => $digitalObject->name ?: 'Image '.($index + 1), + 'width' => $width, + 'height' => $height, + 'images' => [ + [ + '@type' => 'oa:Annotation', + 'motivation' => 'sc:painting', + 'resource' => [ + '@id' => $imageUrl.'/full/full/0/default.jpg', + '@type' => 'dctypes:Image', + 'format' => 'image/jpeg', + 'width' => $width, + 'height' => $height, + 'service' => [ + '@context' => 'http://iiif.io/api/image/2/context.json', + '@id' => $imageUrl, + 'profile' => 'http://iiif.io/api/image/2/level2.json', + ], + ], + 'on' => $baseUrl.'/iiif/canvas/'.$digitalObject->id, + ], + ], + ]; + } + + /** + * Build metadata array. + * + * @param QubitInformationObject $resource + * + * @return array + */ + protected function buildMetadata($resource) + { + $metadata = []; + + // Add reference code + if ($refCode = $resource->referenceCode) { + $metadata[] = [ + 'label' => 'Reference code', + 'value' => $refCode, + ]; + } + + // Add creation date + if ($dates = $resource->getDates()) { + foreach ($dates as $date) { + $metadata[] = [ + 'label' => 'Date', + 'value' => $date->getDate(['cultureFallback' => true]), + ]; + } + } + + // Add level of description + if ($levelOfDescription = $resource->getLevelOfDescription()) { + $metadata[] = [ + 'label' => 'Level of description', + 'value' => $levelOfDescription->__toString(), + ]; + } + + return $metadata; + } + + /** + * Generate IIIF identifier from digital object. + * + * @param QubitDigitalObject $digitalObject + * + * @return string + */ + protected function getIIIFIdentifier($digitalObject) + { + $criteria = new Criteria(); + $criteria->add(QubitProperty::OBJECT_ID, $digitalObject->id); + $criteria->add(QubitProperty::NAME, 'iiifIdentifier'); + + foreach (QubitProperty::get($criteria) as $property) { + return $property->value; + } + + if (null !== $digitalObject->checksum) { + return $digitalObject->checksum; + } + + $filename = pathinfo($digitalObject->path, PATHINFO_FILENAME); + + return preg_replace('/[^a-zA-Z0-9_-]/', '_', $filename); + } +} diff --git a/plugins/arIiifPlugin/modules/iiif/templates/canvasSuccess.php b/plugins/arIiifPlugin/modules/iiif/templates/canvasSuccess.php new file mode 100644 index 0000000000..284aad7606 --- /dev/null +++ b/plugins/arIiifPlugin/modules/iiif/templates/canvasSuccess.php @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/plugins/arIiifPlugin/modules/iiif/templates/manifestSuccess.php b/plugins/arIiifPlugin/modules/iiif/templates/manifestSuccess.php new file mode 100644 index 0000000000..2fd57d83b3 --- /dev/null +++ b/plugins/arIiifPlugin/modules/iiif/templates/manifestSuccess.php @@ -0,0 +1,3 @@ +=i.x&&t.x=i.y},getMousePosition:function(e){if("number"==typeof e.pageX)u.getMousePosition=function(e){var t=new u.Point;t.x=e.pageX;t.y=e.pageY;return t};else{if("number"!=typeof e.clientX)throw new Error("Unknown event mouse position, no known technique.");u.getMousePosition=function(e){var t=new u.Point;t.x=e.clientX+document.body.scrollLeft+document.documentElement.scrollLeft;t.y=e.clientY+document.body.scrollTop+document.documentElement.scrollTop;return t}}return u.getMousePosition(e)},getPageScroll:function(){var e=document.documentElement||{},t=document.body||{};if("number"==typeof window.pageXOffset)u.getPageScroll=function(){return new u.Point(window.pageXOffset,window.pageYOffset)};else if(t.scrollLeft||t.scrollTop)u.getPageScroll=function(){return new u.Point(document.body.scrollLeft,document.body.scrollTop)};else{if(!e.scrollLeft&&!e.scrollTop)return new u.Point(0,0);u.getPageScroll=function(){return new u.Point(document.documentElement.scrollLeft,document.documentElement.scrollTop)}}return u.getPageScroll()},setPageScroll:function(e){if(void 0!==window.scrollTo)u.setPageScroll=function(e){window.scrollTo(e.x,e.y)};else{var t=u.getPageScroll();if(t.x===e.x&&t.y===e.y)return;document.body.scrollLeft=e.x;document.body.scrollTop=e.y;var i=u.getPageScroll();if(i.x!==t.x&&i.y!==t.y){u.setPageScroll=function(e){document.body.scrollLeft=e.x;document.body.scrollTop=e.y};return}document.documentElement.scrollLeft=e.x;document.documentElement.scrollTop=e.y;if((i=u.getPageScroll()).x!==t.x&&i.y!==t.y){u.setPageScroll=function(e){document.documentElement.scrollLeft=e.x;document.documentElement.scrollTop=e.y};return}u.setPageScroll=function(e){}}u.setPageScroll(e)},getWindowSize:function(){var e=document.documentElement||{},t=document.body||{};if("number"==typeof window.innerWidth)u.getWindowSize=function(){return new u.Point(window.innerWidth,window.innerHeight)};else if(e.clientWidth||e.clientHeight)u.getWindowSize=function(){return new u.Point(document.documentElement.clientWidth,document.documentElement.clientHeight)};else{if(!t.clientWidth&&!t.clientHeight)throw new Error("Unknown window size, no known technique.");u.getWindowSize=function(){return new u.Point(document.body.clientWidth,document.body.clientHeight)}}return u.getWindowSize()},makeCenteredNode:function(e){e=u.getElement(e);var t=[u.makeNeutralElement("div"),u.makeNeutralElement("div"),u.makeNeutralElement("div")];u.extend(t[0].style,{display:"table",height:"100%",width:"100%"});u.extend(t[1].style,{display:"table-row"});u.extend(t[2].style,{display:"table-cell",verticalAlign:"middle",textAlign:"center"});t[0].appendChild(t[1]);t[1].appendChild(t[2]);t[2].appendChild(e);return t[0]},makeNeutralElement:function(e){var t=document.createElement(e),e=t.style;e.background="transparent none";e.border="none";e.margin="0px";e.padding="0px";e.position="static";return t},now:function(){Date.now?u.now=Date.now:u.now=function(){return(new Date).getTime()};return u.now()},makeTransparentImage:function(e){var t=u.makeNeutralElement("img");t.src=e;return t},setElementOpacity:function(e,t,i){e=u.getElement(e);i&&!u.Browser.alpha&&(t=Math.round(t));if(u.Browser.opacity)e.style.opacity=t<1?t:"";else if(t<1){t=Math.round(100*t);e.style.filter="alpha(opacity="+t+")"}else e.style.filter=""},setElementTouchActionNone:function(e){void 0!==(e=u.getElement(e)).style.touchAction?e.style.touchAction="none":void 0!==e.style.msTouchAction&&(e.style.msTouchAction="none")},setElementPointerEvents:function(e,t){void 0!==(e=u.getElement(e)).style&&void 0!==e.style.pointerEvents&&(e.style.pointerEvents=t)},setElementPointerEventsNone:function(e){u.setElementPointerEvents(e,"none")},addClass:function(e,t){(e=u.getElement(e)).className?-1===(" "+e.className+" ").indexOf(" "+t+" ")&&(e.className+=" "+t):e.className=t},indexOf:function(e,t,i){Array.prototype.indexOf?this.indexOf=function(e,t,i){return e.indexOf(t,i)}:this.indexOf=function(e,t,i){var n,o,i=i||0;if(!e)throw new TypeError;if(0===(o=e.length)||o<=i)return-1;for(n=i=i<0?o-Math.abs(i):i;nt.touches.length-r&&c.console.warn("Tracked touch contact count doesn't match event.touches.length");var a={originalEvent:t,eventType:"pointerdown",pointerType:"touch",isEmulated:!1};B(e,a);for(n=0;n\s*$/))n=m.parseXml(n);else if(n.match(/^\s*[{[].*[}\]]\s*$/))try{var e=m.parseJSON(n);n=e}catch(e){}function l(e,t){if(e.ready)r(e);else{e.addHandler("ready",function(){r(e)});e.addHandler("open-failed",function(e){s({message:e.message,source:t})})}}setTimeout(function(){if("string"===m.type(n))(n=new m.TileSource({url:n,crossOriginPolicy:(void 0!==o.crossOriginPolicy?o:i).crossOriginPolicy,ajaxWithCredentials:i.ajaxWithCredentials,ajaxHeaders:o.ajaxHeaders||i.ajaxHeaders,splitHashDataForPost:i.splitHashDataForPost,useCanvas:i.useCanvas,success:function(e){r(e.tileSource)}})).addHandler("open-failed",function(e){s(e)});else if(m.isPlainObject(n)||n.nodeType){void 0!==n.crossOriginPolicy||void 0===o.crossOriginPolicy&&void 0===i.crossOriginPolicy||(n.crossOriginPolicy=(void 0!==o.crossOriginPolicy?o:i).crossOriginPolicy);void 0===n.ajaxWithCredentials&&(n.ajaxWithCredentials=i.ajaxWithCredentials);void 0===n.useCanvas&&(n.useCanvas=i.useCanvas);if(m.isFunction(n.getTileUrl)){var e=new m.TileSource(n);e.getTileUrl=n.getTileUrl;r(e)}else{var t=m.TileSource.determineType(a,n);if(t){e=t.prototype.configure.apply(a,[n]);l(new t(e),n)}else s({message:"Unable to load TileSource",source:n})}}else l(n,n)})}(this,i.tileSource,i,function(e){o.tileSource=e;s()},function(e){e.options=i;t(e);s()})}function s(){var e,t;for(;n._loadQueue.length&&(e=n._loadQueue[0]).tileSource;){n._loadQueue.splice(0,1);if(e.options.replace){var i=n.world.getIndexOfItem(e.options.replaceItem);-1!==i&&(e.options.index=i);n.world.removeItem(e.options.replaceItem)}t=new m.TiledImage({viewer:n,source:e.tileSource,viewport:n.viewport,drawer:n.drawer,tileCache:n.tileCache,imageLoader:n.imageLoader,x:e.options.x,y:e.options.y,width:e.options.width,height:e.options.height,fitBounds:e.options.fitBounds,fitBoundsPlacement:e.options.fitBoundsPlacement,clip:e.options.clip,placeholderFillStyle:e.options.placeholderFillStyle,opacity:e.options.opacity,preload:e.options.preload,degrees:e.options.degrees,flipped:e.options.flipped,compositeOperation:e.options.compositeOperation,springStiffness:n.springStiffness,animationTime:n.animationTime,minZoomImageRatio:n.minZoomImageRatio,wrapHorizontal:n.wrapHorizontal,wrapVertical:n.wrapVertical,immediateRender:n.immediateRender,blendTime:n.blendTime,alwaysBlend:n.alwaysBlend,minPixelRatio:n.minPixelRatio,smoothTileEdgesMinZoom:n.smoothTileEdgesMinZoom,iOSDevice:n.iOSDevice,crossOriginPolicy:e.options.crossOriginPolicy,ajaxWithCredentials:e.options.ajaxWithCredentials,loadTilesWithAjax:e.options.loadTilesWithAjax,ajaxHeaders:e.options.ajaxHeaders,debugMode:n.debugMode,subPixelRoundingForTransparency:n.subPixelRoundingForTransparency});n.collectionMode&&n.world.setAutoRefigureSizes(!1);if(n.navigator){i=m.extend({},e.options,{replace:!1,originalTiledImage:t,tileSource:e.tileSource});n.navigator.addTiledImage(i)}n.world.addItem(t,{index:e.options.index});0===n._loadQueue.length&&r(e);1!==n.world.getItemCount()||n.preserveViewport||n.viewport.goHome(!0);e.options.success&&e.options.success({item:t})}}},addSimpleImage:function(e){m.console.assert(e,"[Viewer.addSimpleImage] options is required");m.console.assert(e.url,"[Viewer.addSimpleImage] options.url is required");e=m.extend({},e,{tileSource:{type:"image",url:e.url}});delete e.url;this.addTiledImage(e)},addLayer:function(t){var i=this;m.console.error("[Viewer.addLayer] this function is deprecated; use Viewer.addTiledImage() instead.");var e=m.extend({},t,{success:function(e){i.raiseEvent("add-layer",{options:t,drawer:e.item})},error:function(e){i.raiseEvent("add-layer-failed",e)}});this.addTiledImage(e);return this},getLayerAtLevel:function(e){m.console.error("[Viewer.getLayerAtLevel] this function is deprecated; use World.getItemAt() instead.");return this.world.getItemAt(e)},getLevelOfLayer:function(e){m.console.error("[Viewer.getLevelOfLayer] this function is deprecated; use World.getIndexOfItem() instead.");return this.world.getIndexOfItem(e)},getLayersCount:function(){m.console.error("[Viewer.getLayersCount] this function is deprecated; use World.getItemCount() instead.");return this.world.getItemCount()},setLayerLevel:function(e,t){m.console.error("[Viewer.setLayerLevel] this function is deprecated; use World.setItemIndex() instead.");return this.world.setItemIndex(e,t)},removeLayer:function(e){m.console.error("[Viewer.removeLayer] this function is deprecated; use World.removeItem() instead.");return this.world.removeItem(e)},forceRedraw:function(){c[this.hash].forceRedraw=!0;return this},forceResize:function(){c[this.hash].needsResize=!0;c[this.hash].forceResize=!0},bindSequenceControls:function(){var e=m.delegate(this,v),t=m.delegate(this,f),i=m.delegate(this,this.goToNextPage),n=m.delegate(this,this.goToPreviousPage),o=this.navImages,r=!0;if(this.showSequenceControl){(this.previousButton||this.nextButton)&&(r=!1);this.previousButton=new m.Button({element:this.previousButton?m.getElement(this.previousButton):null,clickTimeThreshold:this.clickTimeThreshold,clickDistThreshold:this.clickDistThreshold,tooltip:m.getString("Tooltips.PreviousPage"),srcRest:B(this.prefixUrl,o.previous.REST),srcGroup:B(this.prefixUrl,o.previous.GROUP),srcHover:B(this.prefixUrl,o.previous.HOVER),srcDown:B(this.prefixUrl,o.previous.DOWN),onRelease:n,onFocus:e,onBlur:t});this.nextButton=new m.Button({element:this.nextButton?m.getElement(this.nextButton):null,clickTimeThreshold:this.clickTimeThreshold,clickDistThreshold:this.clickDistThreshold,tooltip:m.getString("Tooltips.NextPage"),srcRest:B(this.prefixUrl,o.next.REST),srcGroup:B(this.prefixUrl,o.next.GROUP),srcHover:B(this.prefixUrl,o.next.HOVER),srcDown:B(this.prefixUrl,o.next.DOWN),onRelease:i,onFocus:e,onBlur:t});this.navPrevNextWrap||this.previousButton.disable();this.tileSources&&this.tileSources.length||this.nextButton.disable();if(r){this.paging=new m.ButtonGroup({buttons:[this.previousButton,this.nextButton],clickTimeThreshold:this.clickTimeThreshold,clickDistThreshold:this.clickDistThreshold});this.pagingControl=this.paging.element;this.toolbar?this.toolbar.addControl(this.pagingControl,{anchor:m.ControlAnchor.BOTTOM_RIGHT}):this.addControl(this.pagingControl,{anchor:this.sequenceControlAnchor||m.ControlAnchor.TOP_LEFT})}}return this},bindStandardControls:function(){var e=m.delegate(this,L),t=m.delegate(this,M),i=m.delegate(this,N),n=m.delegate(this,F),o=m.delegate(this,A),r=m.delegate(this,U),s=m.delegate(this,V),a=m.delegate(this,j),l=m.delegate(this,G),h=m.delegate(this,q),c=m.delegate(this,v),u=m.delegate(this,f),d=this.navImages,p=[],g=!0;if(this.showNavigationControl){(this.zoomInButton||this.zoomOutButton||this.homeButton||this.fullPageButton||this.rotateLeftButton||this.rotateRightButton||this.flipButton)&&(g=!1);if(this.showZoomControl){p.push(this.zoomInButton=new m.Button({element:this.zoomInButton?m.getElement(this.zoomInButton):null,clickTimeThreshold:this.clickTimeThreshold,clickDistThreshold:this.clickDistThreshold,tooltip:m.getString("Tooltips.ZoomIn"),srcRest:B(this.prefixUrl,d.zoomIn.REST),srcGroup:B(this.prefixUrl,d.zoomIn.GROUP),srcHover:B(this.prefixUrl,d.zoomIn.HOVER),srcDown:B(this.prefixUrl,d.zoomIn.DOWN),onPress:e,onRelease:t,onClick:i,onEnter:e,onExit:t,onFocus:c,onBlur:u}));p.push(this.zoomOutButton=new m.Button({element:this.zoomOutButton?m.getElement(this.zoomOutButton):null,clickTimeThreshold:this.clickTimeThreshold,clickDistThreshold:this.clickDistThreshold,tooltip:m.getString("Tooltips.ZoomOut"),srcRest:B(this.prefixUrl,d.zoomOut.REST),srcGroup:B(this.prefixUrl,d.zoomOut.GROUP),srcHover:B(this.prefixUrl,d.zoomOut.HOVER),srcDown:B(this.prefixUrl,d.zoomOut.DOWN),onPress:n,onRelease:t,onClick:o,onEnter:n,onExit:t,onFocus:c,onBlur:u}))}this.showHomeControl&&p.push(this.homeButton=new m.Button({element:this.homeButton?m.getElement(this.homeButton):null,clickTimeThreshold:this.clickTimeThreshold,clickDistThreshold:this.clickDistThreshold,tooltip:m.getString("Tooltips.Home"),srcRest:B(this.prefixUrl,d.home.REST),srcGroup:B(this.prefixUrl,d.home.GROUP),srcHover:B(this.prefixUrl,d.home.HOVER),srcDown:B(this.prefixUrl,d.home.DOWN),onRelease:r,onFocus:c,onBlur:u}));this.showFullPageControl&&p.push(this.fullPageButton=new m.Button({element:this.fullPageButton?m.getElement(this.fullPageButton):null,clickTimeThreshold:this.clickTimeThreshold,clickDistThreshold:this.clickDistThreshold,tooltip:m.getString("Tooltips.FullPage"),srcRest:B(this.prefixUrl,d.fullpage.REST),srcGroup:B(this.prefixUrl,d.fullpage.GROUP),srcHover:B(this.prefixUrl,d.fullpage.HOVER),srcDown:B(this.prefixUrl,d.fullpage.DOWN),onRelease:s,onFocus:c,onBlur:u}));if(this.showRotationControl){p.push(this.rotateLeftButton=new m.Button({element:this.rotateLeftButton?m.getElement(this.rotateLeftButton):null,clickTimeThreshold:this.clickTimeThreshold,clickDistThreshold:this.clickDistThreshold,tooltip:m.getString("Tooltips.RotateLeft"),srcRest:B(this.prefixUrl,d.rotateleft.REST),srcGroup:B(this.prefixUrl,d.rotateleft.GROUP),srcHover:B(this.prefixUrl,d.rotateleft.HOVER),srcDown:B(this.prefixUrl,d.rotateleft.DOWN),onRelease:a,onFocus:c,onBlur:u}));p.push(this.rotateRightButton=new m.Button({element:this.rotateRightButton?m.getElement(this.rotateRightButton):null,clickTimeThreshold:this.clickTimeThreshold,clickDistThreshold:this.clickDistThreshold,tooltip:m.getString("Tooltips.RotateRight"),srcRest:B(this.prefixUrl,d.rotateright.REST),srcGroup:B(this.prefixUrl,d.rotateright.GROUP),srcHover:B(this.prefixUrl,d.rotateright.HOVER),srcDown:B(this.prefixUrl,d.rotateright.DOWN),onRelease:l,onFocus:c,onBlur:u}))}this.showFlipControl&&p.push(this.flipButton=new m.Button({element:this.flipButton?m.getElement(this.flipButton):null,clickTimeThreshold:this.clickTimeThreshold,clickDistThreshold:this.clickDistThreshold,tooltip:m.getString("Tooltips.Flip"),srcRest:B(this.prefixUrl,d.flip.REST),srcGroup:B(this.prefixUrl,d.flip.GROUP),srcHover:B(this.prefixUrl,d.flip.HOVER),srcDown:B(this.prefixUrl,d.flip.DOWN),onRelease:h,onFocus:c,onBlur:u}));if(g){this.buttonGroup=new m.ButtonGroup({buttons:p,clickTimeThreshold:this.clickTimeThreshold,clickDistThreshold:this.clickDistThreshold});this.navControl=this.buttonGroup.element;this.addHandler("open",m.delegate(this,W));(this.toolbar||this).addControl(this.navControl,{anchor:this.navigationControlAnchor||m.ControlAnchor.TOP_LEFT})}else this.customButtons=p}return this},currentPage:function(){return this._sequenceIndex},goToPage:function(e){if(this.tileSources&&0<=e&&e=this.tileSources.length&&(e=0);this.goToPage(e)},isAnimating:function(){return c[this.hash].animating}});function r(e){e=m.getElement(e);return new m.Point(0===e.clientWidth?1:e.clientWidth,0===e.clientHeight?1:e.clientHeight)}function h(e,t){if(t instanceof m.Overlay)return t;var i=null;if(t.element)i=m.getElement(t.element);else{var n=t.id||"openseadragon-overlay-"+Math.floor(1e7*Math.random());(i=m.getElement(t.id))||((i=document.createElement("a")).href="#/overlay/"+n);i.id=n;m.addClass(i,t.className||"openseadragon-overlay")}var o=t.location;var r=t.width;var s=t.height;if(!o){n=t.x;var a=t.y;if(void 0!==t.px){e=e.viewport.imageToViewportRectangle(new m.Rect(t.px,t.py,r||0,s||0));n=e.x;a=e.y;r=void 0!==r?e.width:void 0;s=void 0!==s?e.height:void 0}o=new m.Point(n,a)}a=t.placement;a&&"string"===m.type(a)&&(a=m.Placement[t.placement.toUpperCase()]);return new m.Overlay({element:i,location:o,placement:a,onDraw:t.onDraw,checkResize:t.checkResize,width:r,height:s,rotationMode:t.rotationMode})}function s(e,t){var i;for(i=e.length-1;0<=i;i--)if(e[i].element===t)return i;return-1}function a(e,t){return m.requestAnimationFrame(function(){t(e)})}function l(e){m.requestAnimationFrame(function(){!function(e){var t,i,n;if(e.controlsShouldFade){t=m.now();t=t-e.controlsFadeBeginTime;i=1-t/e.controlsFadeLength;i=Math.min(1,i);i=Math.max(0,i);for(n=e.controls.length-1;0<=n;n--)e.controls[n].autoFade&&e.controls[n].setOpacity(i);0=t.flickMinSpeed){var n=0;this.panHorizontal&&(n=t.flickMomentum*e.speed*Math.cos(e.direction));i=0;this.panVertical&&(i=t.flickMomentum*e.speed*Math.sin(e.direction));e=this.viewport.pixelFromPoint(this.viewport.getCenter(!0));i=this.viewport.pointFromPixel(new m.Point(e.x-n,e.y-i));this.viewport.panTo(i,!1)}this.viewport.applyConstraints()}t.dblClickDragToZoom&&!0===c[this.hash].draggingToZoom&&(c[this.hash].draggingToZoom=!1)}function S(e){this.raiseEvent("canvas-enter",{tracker:e.eventSource,pointerType:e.pointerType,position:e.position,buttons:e.buttons,pointers:e.pointers,insideElementPressed:e.insideElementPressed,buttonDownAny:e.buttonDownAny,originalEvent:e.originalEvent})}function E(e){this.raiseEvent("canvas-exit",{tracker:e.eventSource,pointerType:e.pointerType,position:e.position,buttons:e.buttons,pointers:e.pointers,insideElementPressed:e.insideElementPressed,buttonDownAny:e.buttonDownAny,originalEvent:e.originalEvent})}function P(e){this.raiseEvent("canvas-press",{tracker:e.eventSource,pointerType:e.pointerType,position:e.position,insideElementPressed:e.insideElementPressed,insideElementReleased:e.insideElementReleased,originalEvent:e.originalEvent});if(this.gestureSettingsByDeviceType(e.pointerType).dblClickDragToZoom){var t=c[this.hash].lastClickTime;e=m.now();if(null!==t){e-tthis.minScrollDeltaTime){this._lastScrollTime=n;t={tracker:e.eventSource,position:e.position,scroll:e.scroll,shift:e.shift,originalEvent:e.originalEvent,preventDefaultAction:!1,preventDefault:!0};this.raiseEvent("canvas-scroll",t);if(!t.preventDefaultAction&&this.viewport){this.viewport.flipped&&(e.position.x=this.viewport.getContainerSize().x-e.position.x);if((i=this.gestureSettingsByDeviceType(e.pointerType)).scrollToZoom){n=Math.pow(this.zoomPerScroll,e.scroll);this.viewport.zoomBy(n,i.zoomToRefPoint?this.viewport.pointFromPixel(e.position,!0):null);this.viewport.applyConstraints()}}e.preventDefault=t.preventDefault}else e.preventDefault=!0}function k(e){c[this.hash].mouseInside=!0;n(this);this.raiseEvent("container-enter",{tracker:e.eventSource,pointerType:e.pointerType,position:e.position,buttons:e.buttons,pointers:e.pointers,insideElementPressed:e.insideElementPressed,buttonDownAny:e.buttonDownAny,originalEvent:e.originalEvent})}function H(e){if(e.pointers<1){c[this.hash].mouseInside=!1;c[this.hash].animating||u(this)}this.raiseEvent("container-exit",{tracker:e.eventSource,pointerType:e.pointerType,position:e.position,buttons:e.buttons,pointers:e.pointers,insideElementPressed:e.insideElementPressed,buttonDownAny:e.buttonDownAny,originalEvent:e.originalEvent})}function z(e){!function(e){if(!e._opening&&c[e.hash]){if(e.autoResize||c[e.hash].forceResize){if(e._autoResizePolling){i=r(e.container);var t=c[e.hash].prevContainerSize;i.equals(t)||(c[e.hash].needsResize=!0)}c[e.hash].needsResize&&function(e,t){var i=e.viewport;var n=i.getZoom();var o=i.getCenter();i.resize(t,e.preserveImageSizeOnResize);i.panTo(o,!0);var r;if(e.preserveImageSizeOnResize)r=c[e.hash].prevContainerSize.x/t.x;else{var s=new m.Point(0,0);o=new m.Point(c[e.hash].prevContainerSize.x,c[e.hash].prevContainerSize.y).distanceTo(s);s=new m.Point(t.x,t.y).distanceTo(s);r=s/o*c[e.hash].prevContainerSize.x/t.x}i.zoomTo(n*r,null,!0);c[e.hash].prevContainerSize=t;c[e.hash].forceRedraw=!0;c[e.hash].needsResize=!1;c[e.hash].forceResize=!1}(e,i||r(e.container))}t=e.viewport.update();var i=e.world.update()||t;t&&e.raiseEvent("viewport-change");e.referenceStrip&&(i=e.referenceStrip.update(e.viewport)||i);t=c[e.hash].animating;if(!t&&i){e.raiseEvent("animation-start");n(e)}t=t&&!i;t&&(c[e.hash].animating=!1);if(i||t||c[e.hash].forceRedraw||e.world.needsDraw()){!function(e){e.imageLoader.clear();e.drawer.clear();e.world.draw();e.raiseEvent("update-viewport",{})}(e);e._drawOverlays();e.navigator&&e.navigator.update(e.viewport);c[e.hash].forceRedraw=!1;i&&e.raiseEvent("animation")}if(t){e.raiseEvent("animation-finish");c[e.hash].mouseInside||u(e)}c[e.hash].animating=i}}(e);e.isOpen()?e._updateRequestId=a(e,z):e._updateRequestId=!1}function B(e,t){return e?e+t:t}function L(){c[this.hash].lastZoomTime=m.now();c[this.hash].zoomFactor=this.zoomPerSecond;c[this.hash].zooming=!0;i(this)}function F(){c[this.hash].lastZoomTime=m.now();c[this.hash].zoomFactor=1/this.zoomPerSecond;c[this.hash].zooming=!0;i(this)}function M(){c[this.hash].zooming=!1}function i(e){m.requestAnimationFrame(m.delegate(e,t))}function t(){var e,t;if(c[this.hash].zooming&&this.viewport){t=(e=m.now())-c[this.hash].lastZoomTime;t=Math.pow(c[this.hash].zoomFactor,t/1e3);this.viewport.zoomBy(t);this.viewport.applyConstraints();c[this.hash].lastZoomTime=e;i(this)}}function N(){if(this.viewport){c[this.hash].zooming=!1;this.viewport.zoomBy(+this.zoomPerClick);this.viewport.applyConstraints()}}function A(){if(this.viewport){c[this.hash].zooming=!1;this.viewport.zoomBy(1/this.zoomPerClick);this.viewport.applyConstraints()}}function W(){if(this.buttonGroup){this.buttonGroup.emulateEnter();this.buttonGroup.emulateLeave()}}function U(){this.viewport&&this.viewport.goHome()}function V(){this.isFullPage()&&!m.isFullScreen()?this.setFullPage(!1):this.setFullScreen(!this.isFullPage());this.buttonGroup&&this.buttonGroup.emulateLeave();this.fullPageButton.element.focus();this.viewport&&this.viewport.applyConstraints()}function j(){if(this.viewport){var e=this.viewport.getRotation();this.viewport.flipped?e+=this.rotationIncrement:e-=this.rotationIncrement;this.viewport.setRotation(e)}}function G(){if(this.viewport){var e=this.viewport.getRotation();this.viewport.flipped?e-=this.rotationIncrement:e+=this.rotationIncrement;this.viewport.setRotation(e)}}function q(){this.viewport.toggleFlip()}}(OpenSeadragon);!function(r){r.Navigator=function(i){var e,t=i.viewer,n=this;if(i.element||i.id){if(i.element){i.id&&r.console.warn("Given option.id for Navigator was ignored since option.element was provided and is being used instead.");i.element.id?i.id=i.element.id:i.id="navigator-"+r.now();this.element=i.element}else this.element=document.getElementById(i.id);i.controlOptions={anchor:r.ControlAnchor.NONE,attachToViewer:!1,autoFade:!1}}else{i.id="navigator-"+r.now();this.element=r.makeNeutralElement("div");i.controlOptions={anchor:r.ControlAnchor.TOP_RIGHT,attachToViewer:!0,autoFade:i.autoFade};if(i.position)if("BOTTOM_RIGHT"===i.position)i.controlOptions.anchor=r.ControlAnchor.BOTTOM_RIGHT;else if("BOTTOM_LEFT"===i.position)i.controlOptions.anchor=r.ControlAnchor.BOTTOM_LEFT;else if("TOP_RIGHT"===i.position)i.controlOptions.anchor=r.ControlAnchor.TOP_RIGHT;else if("TOP_LEFT"===i.position)i.controlOptions.anchor=r.ControlAnchor.TOP_LEFT;else if("ABSOLUTE"===i.position){i.controlOptions.anchor=r.ControlAnchor.ABSOLUTE;i.controlOptions.top=i.top;i.controlOptions.left=i.left;i.controlOptions.height=i.height;i.controlOptions.width=i.width}}this.element.id=i.id;this.element.className+=" navigator";(i=r.extend(!0,{sizeRatio:r.DEFAULT_SETTINGS.navigatorSizeRatio},i,{element:this.element,tabIndex:-1,showNavigator:!1,mouseNavEnabled:!1,showNavigationControl:!1,showSequenceControl:!1,immediateRender:!0,blendTime:0,animationTime:i.animationTime,autoResize:!1,minZoomImageRatio:1,background:i.background,opacity:i.opacity,borderColor:i.borderColor,displayRegionColor:i.displayRegionColor})).minPixelRatio=this.minPixelRatio=t.minPixelRatio;r.setElementTouchActionNone(this.element);this.borderWidth=2;this.fudge=new r.Point(1,1);this.totalBorderWidths=new r.Point(2*this.borderWidth,2*this.borderWidth).minus(this.fudge);i.controlOptions.anchor!==r.ControlAnchor.NONE&&function(e,t){e.margin="0px";e.border=t+"px solid "+i.borderColor;e.padding="0px";e.background=i.background;e.opacity=i.opacity;e.overflow="hidden"}(this.element.style,this.borderWidth);this.displayRegion=r.makeNeutralElement("div");this.displayRegion.id=this.element.id+"-displayregion";this.displayRegion.className="displayregion";!function(e,t){e.position="relative";e.top="0px";e.left="0px";e.fontSize="0px";e.overflow="hidden";e.border=t+"px solid "+i.displayRegionColor;e.margin="0px";e.padding="0px";e.background="transparent";e.float="left";e.cssFloat="left";e.styleFloat="left";e.zIndex=999999999;e.cursor="default";e.boxSizing="content-box"}(this.displayRegion.style,this.borderWidth);r.setElementPointerEventsNone(this.displayRegion);r.setElementTouchActionNone(this.displayRegion);this.displayRegionContainer=r.makeNeutralElement("div");this.displayRegionContainer.id=this.element.id+"-displayregioncontainer";this.displayRegionContainer.className="displayregioncontainer";this.displayRegionContainer.style.width="100%";this.displayRegionContainer.style.height="100%";r.setElementPointerEventsNone(this.displayRegionContainer);r.setElementTouchActionNone(this.displayRegionContainer);t.addControl(this.element,i.controlOptions);this._resizeWithViewer=i.controlOptions.anchor!==r.ControlAnchor.ABSOLUTE&&i.controlOptions.anchor!==r.ControlAnchor.NONE;if(i.width&&i.height){this.setWidth(i.width);this.setHeight(i.height)}else if(this._resizeWithViewer){e=r.getElementSize(t.element);this.element.style.height=Math.round(e.y*i.sizeRatio)+"px";this.element.style.width=Math.round(e.x*i.sizeRatio)+"px";this.oldViewerSize=e;e=r.getElementSize(this.element);this.elementArea=e.x*e.y}this.oldContainerSize=new r.Point(0,0);r.Viewer.apply(this,[i]);this.displayRegionContainer.appendChild(this.displayRegion);this.element.getElementsByTagName("div")[0].appendChild(this.displayRegionContainer);function o(e,t){c(n.displayRegionContainer,e);c(n.displayRegion,-e);n.viewport.setRotation(e,t)}if(i.navigatorRotate){o(i.viewer.viewport?i.viewer.viewport.getRotation():i.viewer.degrees||0,!0);i.viewer.addHandler("rotate",function(e){o(e.degrees,e.immediately)})}this.innerTracker.destroy();this.innerTracker=new r.MouseTracker({userData:"Navigator.innerTracker",element:this.element,dragHandler:r.delegate(this,a),clickHandler:r.delegate(this,s),releaseHandler:r.delegate(this,l),scrollHandler:r.delegate(this,h),preProcessEventHandler:function(e){"wheel"===e.eventType&&(e.preventDefault=!0)}});this.outerTracker.userData="Navigator.outerTracker";r.setElementPointerEventsNone(this.canvas);r.setElementPointerEventsNone(this.container);this.addHandler("reset-size",function(){n.viewport&&n.viewport.goHome(!0)});t.world.addHandler("item-index-change",function(t){window.setTimeout(function(){var e=n.world.getItemAt(t.previousIndex);n.world.setItemIndex(e,t.newIndex)},1)});t.world.addHandler("remove-item",function(e){e=e.item;e=n._getMatchingItem(e);e&&n.world.removeItem(e)});this.update(t.viewport)};r.extend(r.Navigator.prototype,r.EventSource.prototype,r.Viewer.prototype,{updateSize:function(){if(this.viewport){var e=new r.Point(0===this.container.clientWidth?1:this.container.clientWidth,0===this.container.clientHeight?1:this.container.clientHeight);if(!e.equals(this.oldContainerSize)){this.viewport.resize(e,!0);this.viewport.goHome(!0);this.oldContainerSize=e;this.drawer.clear();this.world.draw()}}},setWidth:function(e){this.width=e;this.element.style.width="number"==typeof e?e+"px":e;this._resizeWithViewer=!1;this.updateSize()},setHeight:function(e){this.height=e;this.element.style.height="number"==typeof e?e+"px":e;this._resizeWithViewer=!1;this.updateSize()},setFlip:function(e){this.viewport.setFlip(e);this.setDisplayTransform(this.viewer.viewport.getFlip()?"scale(-1,1)":"scale(1,1)");return this},setDisplayTransform:function(e){i(this.displayRegion,e);i(this.canvas,e);i(this.element,e)},update:function(e){var t,i;t=r.getElementSize(this.viewer.element);if(this._resizeWithViewer&&t.x&&t.y&&!t.equals(this.oldViewerSize)){this.oldViewerSize=t;if(this.maintainSizeRatio||!this.elementArea){i=t.x*this.sizeRatio;o=t.y*this.sizeRatio}else{i=Math.sqrt(this.elementArea*(t.x/t.y));o=this.elementArea/i}this.element.style.width=Math.round(i)+"px";this.element.style.height=Math.round(o)+"px";this.elementArea||(this.elementArea=i*o);this.updateSize()}if(e&&this.viewport){i=e.getBoundsNoRotate(!0);o=this.viewport.pixelFromPointNoRotate(i.getTopLeft(),!1);i=this.viewport.pixelFromPointNoRotate(i.getBottomRight(),!1).minus(this.totalBorderWidths);if(!this.navigatorRotate){var n=e.getRotation(!0);c(this.displayRegion,-n)}e=this.displayRegion.style;e.display=this.world.getItemCount()?"block":"none";e.top=o.y.toFixed(2)+"px";e.left=o.x.toFixed(2)+"px";n=i.x-o.x;var o=i.y-o.y;e.width=Math.round(Math.max(n,0))+"px";e.height=Math.round(Math.max(o,0))+"px"}},addTiledImage:function(e){var n=this;var o=e.originalTiledImage;delete e.original;e=r.extend({},e,{success:function(e){var t=e.item;t._originalForNavigator=o;n._matchBounds(t,o,!0);n._matchOpacity(t,o);n._matchCompositeOperation(t,o);function i(){n._matchBounds(t,o)}o.addHandler("bounds-change",i);o.addHandler("clip-change",i);o.addHandler("opacity-change",function(){n._matchOpacity(t,o)});o.addHandler("composite-operation-change",function(){n._matchCompositeOperation(t,o)})}});return r.Viewer.prototype.addTiledImage.apply(this,[e])},destroy:function(){return r.Viewer.prototype.destroy.apply(this)},_getMatchingItem:function(e){var t=this.world.getItemCount();var i;for(var n=0;n=1/this.aspectRatio-1e-15&&(n=this.getNumTiles(e).y-1);return new h.Point(i,n)},getTileBounds:function(e,t,i,n){var o=this.dimensions.times(this.getLevelScale(e)),r=this.getTileWidth(e),s=this.getTileHeight(e),a=0===t?0:r*t-this.tileOverlap,e=0===i?0:s*i-this.tileOverlap,t=r+(0===t?1:2)*this.tileOverlap,s=s+(0===i?1:2)*this.tileOverlap,i=1/o.x;t=Math.min(t,o.x-a);s=Math.min(s,o.y-e);return n?new h.Rect(0,0,t,s):new h.Rect(a*i,e*i,t*i,s*i)},getImageInfo:function(n){var t,i,e,o,r,s=this;n&&-1<(r=(o=(e=n.split("/"))[e.length-1]).lastIndexOf("."))&&(e[e.length-1]=o.slice(0,r));var a=null;if(this.splitHashDataForPost){var l=n.indexOf("#");if(-1!==l){a=n.substring(l+1);n=n.substr(0,l)}}t=function(e){"string"==typeof e&&(e=h.parseXml(e));var t=h.TileSource.determineType(s,e,n);if(t){void 0===(i=t.prototype.configure.apply(s,[e,n,a])).ajaxWithCredentials&&(i.ajaxWithCredentials=s.ajaxWithCredentials);i=new t(i);s.ready=!0;s.raiseEvent("ready",{tileSource:i})}else s.raiseEvent("open-failed",{message:"Unable to load TileSource",source:n})};if(n.match(/\.js$/)){l=n.split("/").pop().replace(".js","");h.jsonp({url:n,async:!1,callbackName:l,callback:t})}else h.makeAjaxRequest({url:n,postData:a,withCredentials:this.ajaxWithCredentials,headers:this.ajaxHeaders,success:function(e){e=function(t){var e,i,n=t.responseText,o=t.status;{if(!t)throw new Error(h.getString("Errors.Security"));if(200!==t.status&&0!==t.status){o=t.status;e=404===o?"Not Found":t.statusText;throw new Error(h.getString("Errors.Status",o,e))}}if(n.match(/^\s*<.*/))try{i=t.responseXML&&t.responseXML.documentElement?t.responseXML:h.parseXml(n)}catch(e){i=t.responseText}else if(n.match(/\s*[{[].*/))try{i=h.parseJSON(n)}catch(e){i=n}else i=n;return i}(e);t(e)},error:function(e,t){var i;try{i="HTTP "+e.status+" attempting to load TileSource: "+n}catch(e){i=(void 0!==t&&t.toString?t.toString():"Unknown error")+" attempting to load TileSource: "+n}h.console.error(i);s.raiseEvent("open-failed",{message:i,source:n,postData:a})}})},supports:function(e,t){return!1},configure:function(e,t,i){throw new Error("Method not implemented.")},getTileUrl:function(e,t,i){throw new Error("Method not implemented.")},getTilePostData:function(e,t,i){return null},getTileAjaxHeaders:function(e,t,i){return{}},getTileHashKey:function(e,t,i,n,o,r){function s(e){return o?e+"+"+JSON.stringify(o):e}return s("string"!=typeof n?e+"/"+t+"_"+i:n)},tileExists:function(e,t,i){var n=this.getNumTiles(e);return e>=this.minLevel&&e<=this.maxLevel&&0<=t&&0<=i&&tthis.maxLevel)return!1;if(!h||!h.length)return!0;for(l=h.length-1;0<=l;l--)if(!(e<(n=h[l]).minLevel||e>n.maxLevel)){a=this.getLevelScale(e);o=n.x*a;r=n.y*a;s=o+n.width*a;a=r+n.height*a;o=Math.floor(o/this._tileWidth);r=Math.floor(r/this._tileWidth);s=Math.ceil(s/this._tileWidth);a=Math.ceil(a/this._tileWidth);if(o<=t&&t=this.minLevel&&e<=this.maxLevel?this.levels[e].width/this.levels[this.maxLevel].width:t}return h.TileSource.prototype.getLevelScale.call(this,e)},getNumTiles:function(e){if(this.emulateLegacyImagePyramid)return this.getLevelScale(e)?new h.Point(1,1):new h.Point(0,0);if(this.levelSizes){var t=this.levelSizes[e];var i=Math.ceil(t.width/this.getTileWidth(e)),t=Math.ceil(t.height/this.getTileHeight(e));return new h.Point(i,t)}return h.TileSource.prototype.getNumTiles.call(this,e)},getTileAtPoint:function(e,t){if(this.emulateLegacyImagePyramid)return new h.Point(0,0);if(this.levelSizes){var i=0<=t.x&&t.x<=1&&0<=t.y&&t.y<=1/this.aspectRatio;h.console.assert(i,"[TileSource.getTileAtPoint] must be called with a valid point.");var n=this.levelSizes[e].width;i=t.x*n;n=t.y*n;i=Math.floor(i/this.getTileWidth(e));n=Math.floor(n/this.getTileHeight(e));1<=t.x&&(i=this.getNumTiles(e).x-1);t.y>=1/this.aspectRatio-1e-15&&(n=this.getNumTiles(e).y-1);return new h.Point(i,n)}return h.TileSource.prototype.getTileAtPoint.call(this,e,t)},getTileUrl:function(e,t,i){if(this.emulateLegacyImagePyramid){var n=null;return n=0=this.minLevel&&e<=this.maxLevel?this.levels[e].url:n}var o,r,s,a,l,h,c,u,d=Math.pow(.5,this.maxLevel-e);if(this.levelSizes){o=this.levelSizes[e].width;r=this.levelSizes[e].height}else{o=Math.ceil(this.width*d);r=Math.ceil(this.height*d)}c=this.getTileWidth(e);u=this.getTileHeight(e);a=Math.round(c/d);l=Math.round(u/d);n=1===this.version?"native."+this.tileFormat:"default."+this.tileFormat;if(oe.tileSize||parseInt(t.y,10)>e.tileSize;){t.x=Math.floor(t.x/2);t.y=Math.floor(t.y/2);e.imageSizes.push({x:t.x,y:t.y});e.gridSize.push(this._getGridSize(t.x,t.y,e.tileSize))}e.imageSizes.reverse();e.gridSize.reverse();e.minLevel=0;e.maxLevel=e.gridSize.length-1;OpenSeadragon.TileSource.apply(this,[e])};e.extend(e.ZoomifyTileSource.prototype,e.TileSource.prototype,{_getGridSize:function(e,t,i){return{x:Math.ceil(e/i),y:Math.ceil(t/i)}},_calculateAbsoluteTileNumber:function(e,t,i){var n=0;var o={};for(var r=0;r");return n.sort(function(e,t){return e.height-t.height})}(t.levels);if(0=this.minLevel&&e<=this.maxLevel?this.levels[e].width/this.levels[this.maxLevel].width:t},getNumTiles:function(e){return this.getLevelScale(e)?new a.Point(1,1):new a.Point(0,0)},getTileUrl:function(e,t,i){var n=null;return n=0=this.minLevel&&e<=this.maxLevel?this.levels[e].url:n}})}(OpenSeadragon);!function(a){a.ImageTileSource=function(e){e=a.extend({buildPyramid:!0,crossOriginPolicy:!1,ajaxWithCredentials:!1,useCanvas:!0},e);a.TileSource.apply(this,[e])};a.extend(a.ImageTileSource.prototype,a.TileSource.prototype,{supports:function(e,t){return e.type&&"image"===e.type},configure:function(e,t,i){return e},getImageInfo:function(e){var t=this._image=new Image;var i=this;this.crossOriginPolicy&&(t.crossOrigin=this.crossOriginPolicy);this.ajaxWithCredentials&&(t.useCredentials=this.ajaxWithCredentials);a.addEvent(t,"load",function(){i.width=t.naturalWidth;i.height=t.naturalHeight;i.aspectRatio=i.width/i.height;i.dimensions=new a.Point(i.width,i.height);i._tileWidth=i.width;i._tileHeight=i.height;i.tileOverlap=0;i.minLevel=0;i.levels=i._buildLevels();i.maxLevel=i.levels.length-1;i.ready=!0;i.raiseEvent("ready",{tileSource:i})});a.addEvent(t,"error",function(){i.raiseEvent("open-failed",{message:"Error loading image at "+e,source:e})});t.src=e},getLevelScale:function(e){var t=NaN;return t=e>=this.minLevel&&e<=this.maxLevel?this.levels[e].width/this.levels[this.maxLevel].width:t},getNumTiles:function(e){return this.getLevelScale(e)?new a.Point(1,1):new a.Point(0,0)},getTileUrl:function(e,t,i){var n=null;return n=e>=this.minLevel&&e<=this.maxLevel?this.levels[e].url:n},getContext2D:function(e,t,i){var n=null;return n=e>=this.minLevel&&e<=this.maxLevel?this.levels[e].context2D:n},destroy:function(){this._freeupCanvasMemory()},_buildLevels:function(){var e=[{url:this._image.src,width:this._image.naturalWidth,height:this._image.naturalHeight}];if(!this.buildPyramid||!a.supportsCanvas||!this.useCanvas){delete this._image;return e}var t=this._image.naturalWidth;var i=this._image.naturalHeight;var n=document.createElement("canvas");var o=n.getContext("2d");n.width=t;n.height=i;o.drawImage(this._image,0,0,t,i);e[0].context2D=o;delete this._image;if(a.isCanvasTainted(n))return e;for(;2<=t&&2<=i;){t=Math.floor(t/2);i=Math.floor(i/2);var r=document.createElement("canvas");var s=r.getContext("2d");r.width=t;r.height=i;s.drawImage(n,0,0,t,i);e.splice(0,0,{context2D:s,width:t,height:i});n=r;o=s}return e},_freeupCanvasMemory:function(){for(var e=0;e=i.ButtonState.GROUP&&e.currentState===i.ButtonState.REST){!function(e){e.shouldFade=!1;e.imgGroup&&i.setElementOpacity(e.imgGroup,1,!0)}(e);e.currentState=i.ButtonState.GROUP}if(t>=i.ButtonState.HOVER&&e.currentState===i.ButtonState.GROUP){e.imgHover&&(e.imgHover.style.visibility="");e.currentState=i.ButtonState.HOVER}if(t>=i.ButtonState.DOWN&&e.currentState===i.ButtonState.HOVER){e.imgDown&&(e.imgDown.style.visibility="");e.currentState=i.ButtonState.DOWN}}}function r(e,t){if(!e.element.disabled){if(t<=i.ButtonState.HOVER&&e.currentState===i.ButtonState.DOWN){e.imgDown&&(e.imgDown.style.visibility="hidden");e.currentState=i.ButtonState.HOVER}if(t<=i.ButtonState.GROUP&&e.currentState===i.ButtonState.HOVER){e.imgHover&&(e.imgHover.style.visibility="hidden");e.currentState=i.ButtonState.GROUP}if(t<=i.ButtonState.REST&&e.currentState===i.ButtonState.GROUP){!function(e){e.shouldFade=!0;e.fadeBeginTime=i.now()+e.fadeDelay;window.setTimeout(function(){n(e)},e.fadeDelay)}(e);e.currentState=i.ButtonState.REST}}}}(OpenSeadragon);!function(o){o.ButtonGroup=function(e){o.extend(!0,this,{buttons:[],clickTimeThreshold:o.DEFAULT_SETTINGS.clickTimeThreshold,clickDistThreshold:o.DEFAULT_SETTINGS.clickDistThreshold,labelText:""},e);var t,i=this.buttons.concat([]),n=this;this.element=e.element||o.makeNeutralElement("div");if(!e.group){this.element.style.display="inline-block";for(t=0;tu&&(u=m.x);m.yp&&(p=m.y)}return new v.Rect(c,d,u-c,p-d)},_getSegments:function(){var e=this.getTopLeft();var t=this.getTopRight();var i=this.getBottomLeft();var n=this.getBottomRight();return[[e,t],[t,n],[n,i],[i,e]]},rotate:function(e,t){if(0===(e=v.positiveModulo(e,360)))return this.clone();t=t||this.getCenter();var i=this.getTopLeft().rotate(e,t);e=this.getTopRight().rotate(e,t).minus(i);e=e.apply(function(e){return Math.abs(e)<1e-15?0:e});t=Math.atan(e.y/e.x);e.x<0?t+=Math.PI:e.y<0&&(t+=2*Math.PI);return new v.Rect(i.x,i.y,this.width,this.height,t/Math.PI*180)},getBoundingBox:function(){if(0===this.degrees)return this.clone();var e=this.getTopLeft();var t=this.getTopRight();var i=this.getBottomLeft();var n=this.getBottomRight();var o=Math.min(e.x,t.x,i.x,n.x);var r=Math.max(e.x,t.x,i.x,n.x);var s=Math.min(e.y,t.y,i.y,n.y);n=Math.max(e.y,t.y,i.y,n.y);return new v.Rect(o,s,r-o,n-s)},getIntegerBoundingBox:function(){var e=this.getBoundingBox();var t=Math.floor(e.x);var i=Math.floor(e.y);var n=Math.ceil(e.width+e.x-t);e=Math.ceil(e.height+e.y-i);return new v.Rect(t,i,n,e)},containsPoint:function(e,t){t=t||0;var i=this.getTopLeft();var n=this.getTopRight();var o=this.getBottomLeft();var r=n.minus(i);var s=o.minus(i);return(e.x-i.x)*r.x+(e.y-i.y)*r.y>=-t&&(e.x-n.x)*r.x+(e.y-n.y)*r.y<=t&&(e.x-i.x)*s.x+(e.y-i.y)*s.y>=-t&&(e.x-o.x)*s.x+(e.y-o.y)*s.y<=t},toString:function(){return"["+Math.round(100*this.x)/100+", "+Math.round(100*this.y)/100+", "+Math.round(100*this.width)/100+"x"+Math.round(100*this.height)/100+", "+Math.round(100*this.degrees)/100+"deg]"}}}(OpenSeadragon);!function(h){var s={};h.ReferenceStrip=function(e){var t,i,n,o=e.viewer,r=h.getElementSize(o.element);if(!e.id){e.id="referencestrip-"+h.now();this.element=h.makeNeutralElement("div");this.element.id=e.id;this.element.className="referencestrip"}e=h.extend(!0,{sizeRatio:h.DEFAULT_SETTINGS.referenceStripSizeRatio,position:h.DEFAULT_SETTINGS.referenceStripPosition,scroll:h.DEFAULT_SETTINGS.referenceStripScroll,clickTimeThreshold:h.DEFAULT_SETTINGS.clickTimeThreshold},e,{element:this.element});h.extend(this,e);s[this.id]={animating:!1};this.minPixelRatio=this.viewer.minPixelRatio;this.element.tabIndex=0;(i=this.element.style).marginTop="0px";i.marginRight="0px";i.marginBottom="0px";i.marginLeft="0px";i.left="0px";i.bottom="0px";i.border="0px";i.background="#000";i.position="relative";h.setElementTouchActionNone(this.element);h.setElementOpacity(this.element,.8);this.viewer=o;this.tracker=new h.MouseTracker({userData:"ReferenceStrip.tracker",element:this.element,clickHandler:h.delegate(this,a),dragHandler:h.delegate(this,l),scrollHandler:h.delegate(this,c),enterHandler:h.delegate(this,d),leaveHandler:h.delegate(this,p),keyDownHandler:h.delegate(this,g),keyHandler:h.delegate(this,m),preProcessEventHandler:function(e){"wheel"===e.eventType&&(e.preventDefault=!0)}});if(e.width&&e.height){this.element.style.width=e.width+"px";this.element.style.height=e.height+"px";o.addControl(this.element,{anchor:h.ControlAnchor.BOTTOM_LEFT})}else if("horizontal"===e.scroll){this.element.style.width=r.x*e.sizeRatio*o.tileSources.length+12*o.tileSources.length+"px";this.element.style.height=r.y*e.sizeRatio+"px";o.addControl(this.element,{anchor:h.ControlAnchor.BOTTOM_LEFT})}else{this.element.style.height=r.y*e.sizeRatio*o.tileSources.length+12*o.tileSources.length+"px";this.element.style.width=r.x*e.sizeRatio+"px";o.addControl(this.element,{anchor:h.ControlAnchor.TOP_LEFT})}this.panelWidth=r.x*this.sizeRatio+8;this.panelHeight=r.y*this.sizeRatio+8;this.panels=[];this.miniViewers={};for(n=0;ns+n.x-this.panelWidth){t=Math.min(t,o-n.x);this.element.style.marginLeft=-t+"px";u(this,n.x,-t)}else if(ta+n.y-this.panelHeight){t=Math.min(t,r-n.y);this.element.style.marginTop=-t+"px";u(this,n.y,-t)}else if(t-(n-r.x)){this.element.style.marginLeft=t+2*e.delta.x+"px";u(this,r.x,t+2*e.delta.x)}}else if(-e.delta.x<0&&t<0){this.element.style.marginLeft=t+2*e.delta.x+"px";u(this,r.x,t+2*e.delta.x)}}else if(0<-e.delta.y){if(i>-(o-r.y)){this.element.style.marginTop=i+2*e.delta.y+"px";u(this,r.y,i+2*e.delta.y)}}else if(-e.delta.y<0&&i<0){this.element.style.marginTop=i+2*e.delta.y+"px";u(this,r.y,i+2*e.delta.y)}}}function c(e){if(this.element){var t=Number(this.element.style.marginLeft.replace("px","")),i=Number(this.element.style.marginTop.replace("px","")),n=Number(this.element.style.width.replace("px","")),o=Number(this.element.style.height.replace("px","")),r=h.getElementSize(this.viewer.canvas);if("horizontal"===this.scroll){if(0-(n-r.x)){this.element.style.marginLeft=t-60*e.scroll+"px";u(this,r.x,t-60*e.scroll)}}else if(e.scroll<0&&t<0){this.element.style.marginLeft=t-60*e.scroll+"px";u(this,r.x,t-60*e.scroll)}}else if(e.scroll<0){if(i>r.y-o){this.element.style.marginTop=i+60*e.scroll+"px";u(this,r.y,i+60*e.scroll)}}else if(0=this.target.time?t:e+(t-e)*(n=this.springStiffness,i=(this.current.time-this.start.time)/(this.target.time-this.start.time),(1-Math.exp(n*-i))/(1-Math.exp(-n)));var i;var n=this.current.value;this._exponential?this.current.value=Math.exp(i):this.current.value=i;return n!==this.current.value},isAtTargetValue:function(){return this.current.value===this.target.value}}}(OpenSeadragon);!function(n){n.ImageJob=function(e){n.extend(!0,this,{timeout:n.DEFAULT_SETTINGS.timeout,jobId:null,tries:0},e);this.data=null;this.userData={};this.errorMsg=null};n.ImageJob.prototype={start:function(){this.tries++;var e=this;var t=this.abort;this.jobId=window.setTimeout(function(){e.finish(null,null,"Image load exceeded timeout ("+e.timeout+" ms)")},this.timeout);this.abort=function(){e.source.downloadTileAbort(e);"function"==typeof t&&t()};this.source.downloadTileStart(this)},finish:function(e,t,i){this.data=e;this.request=t;this.errorMsg=i;this.jobId&&window.clearTimeout(this.jobId);this.callback(this)}};n.ImageLoader=function(e){n.extend(!0,this,{jobLimit:n.DEFAULT_SETTINGS.imageLoaderLimit,timeout:n.DEFAULT_SETTINGS.timeout,jobQueue:[],failedTiles:[],jobsInProgress:0},e)};n.ImageLoader.prototype={addJob:function(t){if(!t.source){n.console.error("ImageLoader.prototype.addJob() requires [options.source]. TileSource since new API defines how images are fetched. Creating a dummy TileSource.");var e=n.TileSource.prototype;t.source={downloadTileStart:e.downloadTileStart,downloadTileAbort:e.downloadTileAbort}}var i=this,e={src:t.src,tile:t.tile||{},source:t.source,loadWithAjax:t.loadWithAjax,ajaxHeaders:t.loadWithAjax?t.ajaxHeaders:null,crossOriginPolicy:t.crossOriginPolicy,ajaxWithCredentials:t.ajaxWithCredentials,postData:t.postData,callback:function(e){!function(e,t,i){""!==t.errorMsg&&(null===t.data||void 0===t.data)&&t.tries<1+e.tileRetryMax&&e.failedTiles.push(t);var n;e.jobsInProgress--;if((!e.jobLimit||e.jobsInProgressthis.canvas.width&&(r.width=this.canvas.width-r.x);if(r.y<0){r.height+=r.y;r.y=0}r.y+r.height>this.canvas.height&&(r.height=this.canvas.height-r.y);this.context.drawImage(this.sketchCanvas,r.x,r.y,r.width,r.height,r.x,r.y,r.width,r.height)}else{t=o.scale||1;e=(i=o.translate)instanceof a.Point?i:new a.Point(0,0);n=0;r=0;if(i){o=this.sketchCanvas.width-this.canvas.width;i=this.sketchCanvas.height-this.canvas.height;n=Math.round(o/2);r=Math.round(i/2)}this.context.drawImage(this.sketchCanvas,e.x-n*t,e.y-r*t,(this.canvas.width+2*n)*t,(this.canvas.height+2*r)*t,-n,-r,this.canvas.width+2*n,this.canvas.height+2*r)}this.context.restore()}},drawDebugInfo:function(e,t,i,n){if(this.useCanvas){var o=this.viewer.world.getIndexOfItem(n)%this.debugGridColor.length;var r=this.context;r.save();r.lineWidth=2*a.pixelDensityRatio;r.font="small-caps bold "+13*a.pixelDensityRatio+"px arial";r.strokeStyle=this.debugGridColor[o];r.fillStyle=this.debugGridColor[o];this.viewport.getRotation(!0)%360!=0&&this._offsetForRotation({degrees:this.viewport.getRotation(!0)});n.getRotation(!0)%360!=0&&this._offsetForRotation({degrees:n.getRotation(!0),point:n.viewport.pixelFromPointNoRotate(n._getRotationPoint(!0),!0)});n.viewport.getRotation(!0)%360==0&&n.getRotation(!0)%360==0&&n._drawer.viewer.viewport.getFlip()&&n._drawer._flip();r.strokeRect(e.position.x*a.pixelDensityRatio,e.position.y*a.pixelDensityRatio,e.size.x*a.pixelDensityRatio,e.size.y*a.pixelDensityRatio);var s=(e.position.x+e.size.x/2)*a.pixelDensityRatio;o=(e.position.y+e.size.y/2)*a.pixelDensityRatio;r.translate(s,o);r.rotate(Math.PI/180*-this.viewport.getRotation(!0));r.translate(-s,-o);if(0===e.x&&0===e.y){r.fillText("Zoom: "+this.viewport.getZoom(),e.position.x*a.pixelDensityRatio,(e.position.y-30)*a.pixelDensityRatio);r.fillText("Pan: "+this.viewport.getBounds().toString(),e.position.x*a.pixelDensityRatio,(e.position.y-20)*a.pixelDensityRatio)}r.fillText("Level: "+e.level,(e.position.x+10)*a.pixelDensityRatio,(e.position.y+20)*a.pixelDensityRatio);r.fillText("Column: "+e.x,(e.position.x+10)*a.pixelDensityRatio,(e.position.y+30)*a.pixelDensityRatio);r.fillText("Row: "+e.y,(e.position.x+10)*a.pixelDensityRatio,(e.position.y+40)*a.pixelDensityRatio);r.fillText("Order: "+i+" of "+t,(e.position.x+10)*a.pixelDensityRatio,(e.position.y+50)*a.pixelDensityRatio);r.fillText("Size: "+e.size.toString(),(e.position.x+10)*a.pixelDensityRatio,(e.position.y+60)*a.pixelDensityRatio);r.fillText("Position: "+e.position.toString(),(e.position.x+10)*a.pixelDensityRatio,(e.position.y+70)*a.pixelDensityRatio);this.viewport.getRotation(!0)%360!=0&&this._restoreRotationChanges();n.getRotation(!0)%360!=0&&this._restoreRotationChanges();n.viewport.getRotation(!0)%360==0&&n.getRotation(!0)%360==0&&n._drawer.viewer.viewport.getFlip()&&n._drawer._flip();r.restore()}},debugRect:function(e){if(this.useCanvas){var t=this.context;t.save();t.lineWidth=2*a.pixelDensityRatio;t.strokeStyle=this.debugGridColor[0];t.fillStyle=this.debugGridColor[0];t.strokeRect(e.x*a.pixelDensityRatio,e.y*a.pixelDensityRatio,e.width*a.pixelDensityRatio,e.height*a.pixelDensityRatio);t.restore()}},setImageSmoothingEnabled:function(e){if(this.useCanvas){this._imageSmoothingEnabled=e;this._updateImageSmoothingEnabled(this.context);this.viewer.forceRedraw()}},_updateImageSmoothingEnabled:function(e){e.msImageSmoothingEnabled=this._imageSmoothingEnabled;e.imageSmoothingEnabled=this._imageSmoothingEnabled},getCanvasSize:function(e){e=this._getContext(e).canvas;return new a.Point(e.width,e.height)},getCanvasCenter:function(){return new a.Point(this.canvas.width/2,this.canvas.height/2)},_offsetForRotation:function(e){var t=e.point?e.point.times(a.pixelDensityRatio):this.getCanvasCenter();var i=this._getContext(e.useSketch);i.save();i.translate(t.x,t.y);if(this.viewer.viewport.flipped){i.rotate(Math.PI/180*-e.degrees);i.scale(-1,1)}else i.rotate(Math.PI/180*e.degrees);i.translate(-t.x,-t.y)},_flip:function(e){var t=(e=e||{}).point?e.point.times(a.pixelDensityRatio):this.getCanvasCenter();e=this._getContext(e.useSketch);e.translate(t.x,0);e.scale(-1,1);e.translate(-t.x,0)},_restoreRotationChanges:function(e){this._getContext(e).restore()},_calculateCanvasSize:function(){var e=a.pixelDensityRatio;var t=this.viewport.getContainerSize();return{x:Math.round(t.x*e),y:Math.round(t.y*e)}},_calculateSketchCanvasSize:function(){var e=this._calculateCanvasSize();if(0===this.viewport.getRotation())return e;e=Math.ceil(Math.sqrt(e.x*e.x+e.y*e.y));return{x:e,y:e}}}}(OpenSeadragon);!function(h){h.Viewport=function(e){var t=arguments;if((e=t.length&&t[0]instanceof h.Point?{containerSize:t[0],contentSize:t[1],config:t[2]}:e).config){h.extend(!0,e,e.config);delete e.config}this._margins=h.extend({left:0,top:0,right:0,bottom:0},e.margins||{});delete e.margins;e.initialDegrees=e.degrees;delete e.degrees;h.extend(!0,this,{containerSize:null,contentSize:null,zoomPoint:null,rotationPivot:null,viewer:null,springStiffness:h.DEFAULT_SETTINGS.springStiffness,animationTime:h.DEFAULT_SETTINGS.animationTime,minZoomImageRatio:h.DEFAULT_SETTINGS.minZoomImageRatio,maxZoomPixelRatio:h.DEFAULT_SETTINGS.maxZoomPixelRatio,visibilityRatio:h.DEFAULT_SETTINGS.visibilityRatio,wrapHorizontal:h.DEFAULT_SETTINGS.wrapHorizontal,wrapVertical:h.DEFAULT_SETTINGS.wrapVertical,defaultZoomLevel:h.DEFAULT_SETTINGS.defaultZoomLevel,minZoomLevel:h.DEFAULT_SETTINGS.minZoomLevel,maxZoomLevel:h.DEFAULT_SETTINGS.maxZoomLevel,initialDegrees:h.DEFAULT_SETTINGS.degrees,flipped:h.DEFAULT_SETTINGS.flipped,homeFillsViewer:h.DEFAULT_SETTINGS.homeFillsViewer,silenceMultiImageWarnings:h.DEFAULT_SETTINGS.silenceMultiImageWarnings},e);this._updateContainerInnerSize();this.centerSpringX=new h.Spring({initial:0,springStiffness:this.springStiffness,animationTime:this.animationTime});this.centerSpringY=new h.Spring({initial:0,springStiffness:this.springStiffness,animationTime:this.animationTime});this.zoomSpring=new h.Spring({exponential:!0,initial:1,springStiffness:this.springStiffness,animationTime:this.animationTime});this.degreesSpring=new h.Spring({initial:e.initialDegrees,springStiffness:this.springStiffness,animationTime:this.animationTime});this._oldCenterX=this.centerSpringX.current.value;this._oldCenterY=this.centerSpringY.current.value;this._oldZoom=this.zoomSpring.current.value;this._oldDegrees=this.degreesSpring.current.value;this._setContentBounds(new h.Rect(0,0,1,1),1);this.goHome(!0);this.update()};h.Viewport.prototype={get degrees(){h.console.warn("Accessing [Viewport.degrees] is deprecated. Use viewport.getRotation instead.");return this.getRotation()},set degrees(e){h.console.warn("Setting [Viewport.degrees] is deprecated. Use viewport.rotateTo, viewport.rotateBy, or viewport.setRotation instead.");this.rotateTo(e)},resetContentSize:function(e){h.console.assert(e,"[Viewport.resetContentSize] contentSize is required");h.console.assert(e instanceof h.Point,"[Viewport.resetContentSize] contentSize must be an OpenSeadragon.Point");h.console.assert(0i.width?this.visibilityRatio*i.width:this.visibilityRatio*t.width;r=i.x-r+a;s=s-t.x-a;if(a>i.width){t.x+=(r+s)/2;n=!0}else if(s<0){t.x+=s;n=!0}else if(0i.height?this.visibilityRatio*i.height:this.visibilityRatio*t.height;l=i.y-l+r;s=s-t.y-r;if(r>i.height){t.y+=(l+s)/2;o=!0}else if(s<0){t.y+=s;o=!0}else if(0=o?s.height=s.width/o:s.width=s.height*o;s.x=r.x-s.width/2;s.y=r.y-s.height/2;var a=1/s.width;if(i){this.panTo(r,!0);this.zoomTo(a,null,!0);n&&this.applyConstraints(!0);return this}var l=this.getCenter(!0);t=this.getZoom(!0);this.panTo(l,!0);this.zoomTo(t,null,!0);e=this.getBounds();o=this.getZoom();if(0===o||Math.abs(a/o-1)<1e-8){this.zoomTo(a,null,!0);this.panTo(r,i);n&&this.applyConstraints(!1);return this}if(n){this.panTo(r,!1);a=this._applyZoomConstraints(a);this.zoomTo(a,null,!1);r=this.getConstrainedBounds();this.panTo(l,!0);this.zoomTo(t,null,!0);this.fitBounds(r)}else{o=s.rotate(-this.getRotation()).getTopLeft().times(a).minus(e.getTopLeft().times(o)).divide(a-o);this.zoomTo(a,o,i)}return this},fitBounds:function(e,t){return this._fitBounds(e,{immediately:t,constraints:!1})},fitBoundsWithConstraints:function(e,t){return this._fitBounds(e,{immediately:t,constraints:!0})},fitVertically:function(e){var t=new h.Rect(this._contentBounds.x+this._contentBounds.width/2,this._contentBounds.y,0,this._contentBounds.height);return this.fitBounds(t,e)},fitHorizontally:function(e){var t=new h.Rect(this._contentBounds.x,this._contentBounds.y+this._contentBounds.height/2,this._contentBounds.width,0);return this.fitBounds(t,e)},getConstrainedBounds:function(e){e=this.getBounds(e);return this._applyBoundaryConstraints(e)},panBy:function(e,t){var i=new h.Point(this.centerSpringX.target.value,this.centerSpringY.target.value);return this.panTo(i.plus(e),t)},panTo:function(e,t){if(t){this.centerSpringX.resetTo(e.x);this.centerSpringY.resetTo(e.y)}else{this.centerSpringX.springTo(e.x);this.centerSpringY.springTo(e.y)}this.viewer&&this.viewer.raiseEvent("pan",{center:e,immediately:t});return this},zoomBy:function(e,t,i){return this.zoomTo(this.zoomSpring.target.value*e,t,i)},zoomTo:function(e,t,i){var n=this;this.zoomPoint=t instanceof h.Point&&!isNaN(t.x)&&!isNaN(t.y)?t:null;i?this._adjustCenterSpringsForZoomPoint(function(){n.zoomSpring.resetTo(e)}):this.zoomSpring.springTo(e);this.viewer&&this.viewer.raiseEvent("zoom",{zoom:e,refPoint:t,immediately:i});return this},setRotation:function(e,t){return this.rotateTo(e,null,t)},getRotation:function(e){return(e?this.degreesSpring.current:this.degreesSpring.target).value},setRotationWithPivot:function(e,t,i){return this.rotateTo(e,t,i)},rotateTo:function(e,t,i){if(!this.viewer||!this.viewer.drawer.canRotate())return this;if(this.degreesSpring.target.value===e&&this.degreesSpring.isAtTargetValue())return this;this.rotationPivot=t instanceof h.Point&&!isNaN(t.x)&&!isNaN(t.y)?t:null;if(i)if(this.rotationPivot){if(!(e-this._oldDegrees)){this.rotationPivot=null;return this}this._rotateAboutPivot(e)}else this.degreesSpring.resetTo(e);else{var n=h.positiveModulo(this.degreesSpring.current.value,360);var o=h.positiveModulo(e,360);t=o-n;180o){r=this._clip.x/this._clip.height*e.height;s=this._clip.y/this._clip.height*e.height}else{r=this._clip.x/this._clip.width*e.width;s=this._clip.y/this._clip.width*e.width}}if(e.getAspectRatio()>o){var l=e.height/t;t=0;n.isHorizontallyCentered?t=(e.width-e.height*o)/2:n.isRight&&(t=e.width-e.height*o);this.setPosition(new y.Point(e.x-r+t,e.y-s),i);this.setHeight(l,i)}else{l=e.width/a;a=0;n.isVerticallyCentered?a=(e.height-e.width/o)/2:n.isBottom&&(a=e.height-e.width/o);this.setPosition(new y.Point(e.x-r,e.y-s+a),i);this.setWidth(l,i)}},getClip:function(){return this._clip?this._clip.clone():null},setClip:function(e){y.console.assert(!e||e instanceof y.Rect,"[TiledImage.setClip] newClip must be an OpenSeadragon.Rect or null");e instanceof y.Rect?this._clip=e.clone():this._clip=null;this._needsDraw=!0;this.raiseEvent("clip-change")},getFlip:function(){return!!this.flipped},setFlip:function(e){this.flipped=!!e;this._needsDraw=!0;this._raiseBoundsChange()},getOpacity:function(){return this.opacity},setOpacity:function(e){if(e!==this.opacity){this.opacity=e;this._needsDraw=!0;this.raiseEvent("opacity-change",{opacity:this.opacity})}},getPreload:function(){return this._preload},setPreload:function(e){this._preload=!!e;this._needsDraw=!0},getRotation:function(e){return(e?this._degreesSpring.current:this._degreesSpring.target).value},setRotation:function(e,t){if(this._degreesSpring.target.value!==e||!this._degreesSpring.isAtTargetValue()){t?this._degreesSpring.resetTo(e):this._degreesSpring.springTo(e);this._needsDraw=!0;this._raiseBoundsChange()}},_getRotationPoint:function(e){return this.getBoundsNoRotate(e).getCenter()},getCompositeOperation:function(){return this.compositeOperation},setCompositeOperation:function(e){if(e!==this.compositeOperation){this.compositeOperation=e;this._needsDraw=!0;this.raiseEvent("composite-operation-change",{compositeOperation:this.compositeOperation})}},setAjaxHeaders:function(e,t){if(y.isPlainObject(e=null===e?{}:e)){this._ownAjaxHeaders=e;this._updateAjaxHeaders(t)}else console.error("[TiledImage.setAjaxHeaders] Ignoring invalid headers, must be a plain object")},_updateAjaxHeaders:function(e){void 0===e&&(e=!0);y.isPlainObject(this.viewer.ajaxHeaders)?this.ajaxHeaders=y.extend({},this.viewer.ajaxHeaders,this._ownAjaxHeaders):this.ajaxHeaders=this._ownAjaxHeaders;if(e){var t,i;for(var n in this.tilesMatrix){t=this.source.getNumTiles(n);for(var o in this.tilesMatrix[n]){i=(t.x+o%t.x)%t.x;for(var r in this.tilesMatrix[n][o]){s=(t.y+r%t.y)%t.y;(r=this.tilesMatrix[n][o][r]).loadWithAjax=this.loadTilesWithAjax;if(r.loadWithAjax){var s=this.source.getTileAjaxHeaders(n,i,s);r.ajaxHeaders=y.extend({},this.ajaxHeaders,s)}else r.ajaxHeaders=null}}}for(var a=0;a=this.minPixelRatio)r=l=!0;else if(!r)continue;var c=e.deltaPixelsFromPointsNoRotate(this.source.getPixelRatio(a),!1).x*this._scaleSpring.current.value;var u=e.deltaPixelsFromPointsNoRotate(this.source.getPixelRatio(Math.max(this.source.getClosestLevel(),0)),!1).x*this._scaleSpring.current.value;u=this.immediateRender?1:u;h=Math.min(1,(h-.5)/.5);c=u/Math.abs(u-c);o=this._updateLevel(r,l,a,h,c,t,s,o);if(this._providesCoverage(this.coverage,a))break}this._drawTiles(this.lastDrawn);if(o&&!o.context2D){this._loadTile(o,s);this._needsDraw=!0;this._setFullyLoaded(!1)}else this._setFullyLoaded(0===this._tilesLoading)},_getCornerTiles:function(e,t,i){var n;var o;if(this.wrapHorizontal){n=y.positiveModulo(t.x,1);o=y.positiveModulo(i.x,1)}else{n=Math.max(0,t.x);o=Math.min(1,i.x)}var r=1/this.source.aspectRatio;if(this.wrapVertical){s=y.positiveModulo(t.y,r);a=y.positiveModulo(i.y,r)}else{s=Math.max(0,t.y);a=Math.min(r,i.y)}var s=this.source.getTileAtPoint(e,new y.Point(n,s));var a=this.source.getTileAtPoint(e,new y.Point(o,a));e=this.source.getNumTiles(e);if(this.wrapHorizontal){s.x+=e.x*Math.floor(t.x);a.x+=e.x*Math.floor(i.x)}if(this.wrapVertical){s.y+=e.y*Math.floor(t.y/r);a.y+=e.y*Math.floor(i.y/r)}return{topLeft:s,bottomRight:a}},_updateLevel:function(e,t,i,n,o,r,s,a){var l=r.getBoundingBox().getTopLeft();var h=r.getBoundingBox().getBottomRight();this.viewer&&this.viewer.raiseEvent("update-level",{tiledImage:this,havedrawn:e,level:i,opacity:n,visibility:o,drawArea:r,topleft:l,bottomright:h,currenttime:s,best:a});this._resetCoverage(this.coverage,i);this._resetCoverage(this.loadingCoverage,i);h=this._getCornerTiles(i,l,h);var c=h.topLeft;var u=h.bottomRight;var d=this.source.getNumTiles(i);var p=this.viewport.pixelFromPoint(this.viewport.getCenter());if(this.getFlip()){u.x+=1;this.wrapHorizontal||(u.x=Math.min(u.x,d.x-1))}for(var g=c.x;g<=u.x;g++)for(var m=c.y;m<=u.y;m++){if(this.getFlip()){var v=(d.x+g%d.x)%d.x;v=g+d.x-v-v-1}else v=g;null!==r.intersection(this.getTileBounds(i,v,m))&&(a=this._updateTile(t,e,v,m,i,n,o,p,d,s,a))}return a},_updateTile:function(e,t,i,n,o,r,s,a,l,h,c){var u=this._getTile(i,n,o,h,l,this._worldWidthCurrent,this._worldHeightCurrent),l=t;this.viewer&&this.viewer.raiseEvent("update-tile",{tiledImage:this,tile:u});this._setCoverage(this.coverage,o,i,n,!1);t=u.loaded||u.loading||this._isCovered(this.loadingCoverage,o,i,n);this._setCoverage(this.loadingCoverage,o,i,n,t);if(!u.exists)return c;e&&!l&&(this._isCovered(this.coverage,o,i,n)?this._setCoverage(this.coverage,o,i,n,!0):l=!0);if(!l)return c;this._positionTile(u,this.source.tileOverlap,this.viewport,a,s);if(!u.loaded)if(u.context2D)this._setTileLoaded(u);else{s=this._tileCache.getImageRecord(u.cacheKey);s&&this._setTileLoaded(u,s.getData())}u.loaded?this._blendTile(u,i,n,o,r,h)&&(this._needsDraw=!0):u.loading?this._tilesLoading++:t||(c=this._compareTiles(c,u));return c},_getTile:function(e,t,i,n,o,r,s){var a,l,h,c,u,d,p,g,m,v=this.tilesMatrix,f=this.source;v[i]||(v[i]={});v[i][e]||(v[i][e]={});if(!v[i][e][t]||!v[i][e][t].flipped!=!this.flipped){a=(o.x+e%o.x)%o.x;l=(o.y+t%o.y)%o.y;h=this.getTileBounds(i,e,t);c=f.getTileBounds(i,a,l,!0);u=f.tileExists(i,a,l);d=f.getTileUrl(i,a,l);m=f.getTilePostData(i,a,l);if(this.loadTilesWithAjax){p=f.getTileAjaxHeaders(i,a,l);y.isPlainObject(this.ajaxHeaders)&&(p=y.extend({},this.ajaxHeaders,p))}else p=null;g=f.getContext2D?f.getContext2D(i,a,l):void 0;m=new y.Tile(i,e,t,h,u,d,g,this.loadTilesWithAjax,p,c,m,f.getTileHashKey(i,a,l,d,p,m));this.getFlip()?0==a&&(m.isRightMost=!0):a==o.x-1&&(m.isRightMost=!0);l==o.y-1&&(m.isBottomMost=!0);m.flipped=this.flipped;v[i][e][t]=m}(m=v[i][e][t]).lastTouchTime=n;return m},_loadTile:function(n,o){var r=this;n.loading=!0;this._imageLoader.addJob({src:n.getUrl(),tile:n,source:this.source,postData:n.postData,loadWithAjax:n.loadWithAjax,ajaxHeaders:n.ajaxHeaders,crossOriginPolicy:this.crossOriginPolicy,ajaxWithCredentials:this.ajaxWithCredentials,callback:function(e,t,i){r._onTileLoad(n,o,e,t,i)},abort:function(){n.loading=!1}})},_onTileLoad:function(t,e,i,n,o){if(i){t.exists=!0;if(ee.visibility||t.visibility===e.visibility&&t.squaredDistancethis.smoothTileEdgesMinZoom&&!this.iOSDevice&&this.getRotation(!0)%360==0&&y.supportsCanvas&&this.viewer.useCanvas){i=!0;n=t.getScaleForEdgeSmoothing();o=t.getTranslationForEdgeSmoothing(n,this._drawer.getCanvasSize(!1),this._drawer.getCanvasSize(!0))}var a;if(i){if(!n){a=this.viewport.viewportToViewerElementRectangle(this.getClippedBounds(!0)).getIntegerBoundingBox();this._drawer.viewer.viewport.getFlip()&&(this.viewport.getRotation(!0)%360==0&&this.getRotation(!0)%360==0||(a.x=this._drawer.viewer.container.clientWidth-(a.x+a.width)));a=a.times(y.pixelDensityRatio)}this._drawer._clear(!0,a)}if(!n){this.viewport.getRotation(!0)%360!=0&&this._drawer._offsetForRotation({degrees:this.viewport.getRotation(!0),useSketch:i});this.getRotation(!0)%360!=0&&this._drawer._offsetForRotation({degrees:this.getRotation(!0),point:this.viewport.pixelFromPointNoRotate(this._getRotationPoint(!0),!0),useSketch:i});this.viewport.getRotation(!0)%360==0&&this.getRotation(!0)%360==0&&this._drawer.viewer.viewport.getFlip()&&this._drawer._flip()}r=!1;if(this._clip){this._drawer.saveContext(i);s=this.imageToViewportRectangle(this._clip,!0);s=s.rotate(-this.getRotation(!0),this._getRotationPoint(!0));s=this._drawer.viewportToDrawerRectangle(s);n&&(s=s.times(n));o&&(s=s.translate(o));this._drawer.setClip(s,i);r=!0}if(this._croppingPolygons){var l=this;this._drawer.saveContext(i);try{var h=this._croppingPolygons.map(function(e){return e.map(function(e){e=l.imageToViewportCoordinates(e.x,e.y,!0).rotate(-l.getRotation(!0),l._getRotationPoint(!0));e=l._drawer.viewportCoordToDrawerCoord(e);n&&(e=e.times(n));return e=o?e.plus(o):e})});this._drawer.clipWithPolygons(h,i)}catch(e){y.console.error(e)}r=!0}if(this.placeholderFillStyle&&!1===this._hasOpaqueTile){h=this._drawer.viewportToDrawerRectangle(this.getBounds(!0));n&&(h=h.times(n));o&&(h=h.translate(o));var c=null;c="function"==typeof this.placeholderFillStyle?this.placeholderFillStyle(this,this._drawer.context):this.placeholderFillStyle;this._drawer.drawRectangle(h,c,i)}c=function(e){if("number"==typeof e)return m(e);if(!e||!y.Browser)return p;var t=e[y.Browser.vendor];g(t)&&(t=e["*"]);return m(t)}(this.subPixelRoundingForTransparency);var u=!1;c===y.SUBPIXEL_ROUNDING_OCCURRENCES.ALWAYS?u=!0:c===y.SUBPIXEL_ROUNDING_OCCURRENCES.ONLY_AT_REST&&(u=!(this.viewer&&this.viewer.isAnimating()));for(var d=e.length-1;0<=d;d--){t=e[d];this._drawer.drawTile(t,this._drawingHandler,i,n,o,u,this.source);t.beingDrawn=!0;this.viewer&&this.viewer.raiseEvent("tile-drawn",{tiledImage:this,tile:t})}r&&this._drawer.restoreContext(i);if(!n){this.getRotation(!0)%360!=0&&this._drawer._restoreRotationChanges(i);this.viewport.getRotation(!0)%360!=0&&this._drawer._restoreRotationChanges(i)}if(i){if(n){this.viewport.getRotation(!0)%360!=0&&this._drawer._offsetForRotation({degrees:this.viewport.getRotation(!0),useSketch:!1});this.getRotation(!0)%360!=0&&this._drawer._offsetForRotation({degrees:this.getRotation(!0),point:this.viewport.pixelFromPointNoRotate(this._getRotationPoint(!0),!0),useSketch:!1})}this._drawer.blendSketch({opacity:this.opacity,scale:n,translate:o,compositeOperation:this.compositeOperation,bounds:a});if(n){this.getRotation(!0)%360!=0&&this._drawer._restoreRotationChanges(!1);this.viewport.getRotation(!0)%360!=0&&this._drawer._restoreRotationChanges(!1)}}n||this.viewport.getRotation(!0)%360==0&&this.getRotation(!0)%360==0&&this._drawer.viewer.viewport.getFlip()&&this._drawer._flip();this._drawDebugInfo(e)}},_drawDebugInfo:function(e){if(this.debugMode)for(var t=e.length-1;0<=t;t--){var i=e[t];try{this._drawer.drawDebugInfo(i,e.length,t,this)}catch(e){y.console.error(e)}}},_providesCoverage:function(e,t,i,n){var o,r,s,a;if(!e[t])return!1;if(void 0!==i&&void 0!==n)return void 0===e[t][i]||void 0===e[t][i][n]||!0===e[t][i][n];for(s in o=e[t])if(Object.prototype.hasOwnProperty.call(o,s))for(a in r=o[s])if(Object.prototype.hasOwnProperty.call(r,a)&&!r[a])return!1;return!0},_isCovered:function(e,t,i,n){return void 0===i||void 0===n?this._providesCoverage(e,t+1):this._providesCoverage(e,t+1,2*i,2*n)&&this._providesCoverage(e,t+1,2*i,2*n+1)&&this._providesCoverage(e,t+1,2*i+1,2*n)&&this._providesCoverage(e,t+1,2*i+1,2*n+1)},_setCoverage:function(e,t,i,n,o){if(e[t]){e[t][i]||(e[t][i]={});e[t][i][n]=o}else y.console.warn("Setting coverage for a tile before its level's coverage has been reset: %s",t)},_resetCoverage:function(e,t){e[t]={}}});var p=y.SUBPIXEL_ROUNDING_OCCURRENCES.NEVER;function g(e){return e!==y.SUBPIXEL_ROUNDING_OCCURRENCES.ALWAYS&&e!==y.SUBPIXEL_ROUNDING_OCCURRENCES.ONLY_AT_REST&&e!==y.SUBPIXEL_ROUNDING_OCCURRENCES.NEVER}function m(e){return g(e)?p:e}}(OpenSeadragon);!function(g){function m(e){g.console.assert(e,"[TileCache.cacheTile] options is required");g.console.assert(e.tile,"[TileCache.cacheTile] options.tile is required");g.console.assert(e.tiledImage,"[TileCache.cacheTile] options.tiledImage is required");this.tile=e.tile;this.tiledImage=e.tiledImage}function v(e){g.console.assert(e,"[ImageRecord] options is required");g.console.assert(e.data,"[ImageRecord] options.data is required");this._tiles=[];e.create.apply(null,[this,e.data,e.ownerTile]);this._destroyImplementation=e.destroy.bind(null,this);this.getImage=e.getImage.bind(null,this);this.getData=e.getData.bind(null,this);this.getRenderedContext=e.getRenderedContext.bind(null,this)}v.prototype={destroy:function(){this._destroyImplementation();this._tiles=null},addTile:function(e){g.console.assert(e,"[ImageRecord.addTile] tile is required");this._tiles.push(e)},removeTile:function(e){for(var t=0;tthis._maxImageCacheCount){var o=null;var r=-1;var s=null;var a,l,h,c,u,d;for(var p=this._tilesLoaded.length-1;0<=p;p--)if(!((a=(d=this._tilesLoaded[p]).tile).level<=t||a.beingDrawn))if(o){c=a.lastTouchTime;l=o.lastTouchTime;u=a.level;h=o.level;if(c=this._items.length)throw new Error("Index bigger than number of layers.");if(t!==i&&-1!==i){this._items.splice(i,1);this._items.splice(t,0,e);this._needsDraw=!0;this.raiseEvent("item-index-change",{item:e,previousIndex:i,newIndex:t})}},removeItem:function(e){g.console.assert(e,"[World.removeItem] item is required");var t=g.indexOf(this._items,e);if(-1!==t){e.removeHandler("bounds-change",this._delegatedFigureSizes);e.removeHandler("clip-change",this._delegatedFigureSizes);e.destroy();this._items.splice(t,1);this._figureSizes();this._needsDraw=!0;this._raiseRemoveItem(e)}},removeAll:function(){this.viewer._cancelPendingImages();var e;var t;for(t=0;td.height?r:r*(d.width/d.height))*(d.height/d.width);d=new g.Point(l+(r-u)/2,h+(r-d)/2);c.setPosition(d,t);c.setWidth(u,t);"horizontal"===i?l+=s:h+=s}this.setAutoRefigureSizes(!0)},_figureSizes:function(){var e=this._homeBounds?this._homeBounds.clone():null;var t=this._contentSize?this._contentSize.clone():null;var i=this._contentFactor||0;if(this._items.length){var n=this._items[0];var o=n.getBounds();this._contentFactor=n.getContentSize().x/o.width;var r=n.getClippedBounds().getBoundingBox();var s=r.x;var a=r.y;var l=r.x+r.width;var h=r.y+r.height;for(var c=1;c