Code Duplication    Length = 35-1156 lines in 2 locations

docs/js/jquery.iviewer.js 1 location

@@ 14-1169 (lines=1156) @@
11
 * Version: 0.7.7
12
 */
13
14
( function( $, undefined ) {
15
16
//this code was taken from the https://github.com/furf/jquery-ui-touch-punch
17
var mouseEvents = {
18
        touchstart: 'mousedown',
19
        touchmove: 'mousemove',
20
        touchend: 'mouseup'
21
    },
22
    gesturesSupport = 'ongesturestart' in document.createElement('div');
23
24
25
/**
26
 * Convert a touch event to a mouse-like
27
 */
28
function makeMouseEvent (event) {
29
    var touch = event.originalEvent.changedTouches[0];
30
31
    return $.extend(event, {
32
        type:    mouseEvents[event.type],
33
        which:   1,
34
        pageX:   touch.pageX,
35
        pageY:   touch.pageY,
36
        screenX: touch.screenX,
37
        screenY: touch.screenY,
38
        clientX: touch.clientX,
39
        clientY: touch.clientY,
40
        isTouchEvent: true
41
    });
42
}
43
44
var mouseProto = $.ui.mouse.prototype,
45
    _mouseInit = $.ui.mouse.prototype._mouseInit;
46
47
mouseProto._mouseInit = function() {
48
    var self = this;
49
    self._touchActive = false;
50
51
    this.element.bind( 'touchstart.' + this.widgetName, function(event) {
52
        if (gesturesSupport && event.originalEvent.touches.length > 1) { return; }
53
        self._touchActive = true;
54
        return self._mouseDown(makeMouseEvent(event));
55
    })
56
57
    var self = this;
58
    // these delegates are required to keep context
59
    this._mouseMoveDelegate = function(event) {
60
        if (gesturesSupport && event.originalEvent.touches && event.originalEvent.touches.length > 1) { return; }
61
        if (self._touchActive) {
62
            return self._mouseMove(makeMouseEvent(event));
63
        }
64
    };
65
    this._mouseUpDelegate = function(event) {
66
        if (self._touchActive) {
67
            self._touchActive = false;
68
            return self._mouseUp(makeMouseEvent(event));
69
        }
70
    };
71
72
    $(document)
73
        .bind('touchmove.'+ this.widgetName, this._mouseMoveDelegate)
74
        .bind('touchend.' + this.widgetName, this._mouseUpDelegate);
75
76
    _mouseInit.apply(this);
77
}
78
79
/**
80
 * Simple implementation of jQuery like getters/setters
81
 * var val = something();
82
 * something(val);
83
 */
84
var setter = function(setter, getter) {
85
    return function(val) {
86
        if (arguments.length === 0) {
87
            return getter.apply(this);
88
        } else {
89
            setter.apply(this, arguments);
90
        }
91
    }
92
};
93
94
/**
95
 * Internet explorer rotates image relative left top corner, so we should
96
 * shift image when it's rotated.
97
 */
98
var ieTransforms = {
99
        '0': {
100
            marginLeft: 0,
101
            marginTop: 0,
102
            filter: 'progid:DXImageTransform.Microsoft.Matrix(M11=1, M12=0, M21=0, M22=1, SizingMethod="auto expand")'
103
        },
104
105
        '90': {
106
            marginLeft: -1,
107
            marginTop: 1,
108
            filter: 'progid:DXImageTransform.Microsoft.Matrix(M11=0, M12=-1, M21=1, M22=0, SizingMethod="auto expand")'
109
        },
110
111
        '180': {
112
            marginLeft: 0,
113
            marginTop: 0,
114
            filter: 'progid:DXImageTransform.Microsoft.Matrix(M11=-1, M12=0, M21=0, M22=-1, SizingMethod="auto expand")'
115
        },
116
117
        '270': {
118
            marginLeft: -1,
119
            marginTop: 1,
120
            filter: 'progid:DXImageTransform.Microsoft.Matrix(M11=0, M12=1, M21=-1, M22=0, SizingMethod="auto expand")'
121
        }
122
    },
123
    // this test is the inversion of the css filters test from the modernizr project
124
    useIeTransforms = function() {
125
        var modElem = document.createElement('modernizr'),
126
		mStyle = modElem.style,
127
		omPrefixes = 'Webkit Moz O ms',
128
		domPrefixes = omPrefixes.toLowerCase().split(' '),
129
        	props = ("transform" + ' ' + domPrefixes.join("Transform ") + "Transform").split(' ');
130
        for ( var i in props ) {
131
            var prop = props[i];
132
            if ( !$.contains(prop, "-") && mStyle[prop] !== undefined ) {
133
                return false;
134
            }
135
        }
136
        return true;
137
    }();
138
139
$.widget( "ui.iviewer", $.ui.mouse, {
140
    widgetEventPrefix: "iviewer",
141
    options : {
142
        /**
143
        * start zoom value for image, not used now
144
        * may be equal to "fit" to fit image into container or scale in %
145
        **/
146
        zoom: "fit",
147
        /**
148
        * base value to scale image
149
        **/
150
        zoom_base: 100,
151
        /**
152
        * maximum zoom
153
        **/
154
        zoom_max: 800,
155
        /**
156
        * minimum zoom
157
        **/
158
        zoom_min: 25,
159
        /**
160
        * base of rate multiplier.
161
        * zoom is calculated by formula: zoom_base * zoom_delta^rate
162
        **/
163
        zoom_delta: 1.4,
164
        /**
165
        * whether the zoom should be animated.
166
        */
167
        zoom_animation: true,
168
        /**
169
        * if true plugin doesn't add its own controls
170
        **/
171
        ui_disabled: false,
172
        /**
173
         * If false mousewheel will be disabled
174
         */
175
        mousewheel: true,
176
        /**
177
        * if false, plugin doesn't bind resize event on window and this must
178
        * be handled manually
179
        **/
180
        update_on_resize: true,
181
        /**
182
        * event is triggered when zoom value is changed
183
        * @param int new zoom value
184
        * @return boolean if false zoom action is aborted
185
        **/
186
        onZoom: jQuery.noop,
187
        /**
188
        * event is triggered when zoom value is changed after image is set to the new dimensions
189
        * @param int new zoom value
190
        * @return boolean if false zoom action is aborted
191
        **/
192
        onAfterZoom: jQuery.noop,
193
        /**
194
        * event is fired on drag begin
195
        * @param object coords mouse coordinates on the image
196
        * @return boolean if false is returned, drag action is aborted
197
        **/
198
        onStartDrag: jQuery.noop,
199
        /**
200
        * event is fired on drag action
201
        * @param object coords mouse coordinates on the image
202
        **/
203
        onDrag: jQuery.noop,
204
        /**
205
        * event is fired on drag stop
206
        * @param object coords mouse coordinates on the image
207
        **/
208
        onStopDrag: jQuery.noop,
209
        /**
210
        * event is fired when mouse moves over image
211
        * @param object coords mouse coordinates on the image
212
        **/
213
        onMouseMove: jQuery.noop,
214
        /**
215
        * mouse click event
216
        * @param object coords mouse coordinates on the image
217
        **/
218
        onClick: jQuery.noop,
219
        /**
220
        * event is fired when image starts to load
221
        */
222
        onStartLoad: null,
223
        /**
224
        * event is fired, when image is loaded and initially positioned
225
        */
226
        onFinishLoad: null,
227
        /**
228
        * event is fired when image load error occurs
229
        */
230
        onErrorLoad: null
231
    },
232
233
    _create: function() {
234
        var me = this;
235
236
        //drag variables
237
        this.dx = 0;
238
        this.dy = 0;
239
240
        /* object containing actual information about image
241
        *   @img_object.object - jquery img object
242
        *   @img_object.orig_{width|height} - original dimensions
243
        *   @img_object.display_{width|height} - actual dimensions
244
        */
245
        this.img_object = {};
246
247
        this.zoom_object = {}; //object to show zoom status
248
249
        this._angle = 0;
250
251
        this.current_zoom = this.options.zoom;
252
253
        if(this.options.src === null){
254
            return;
255
        }
256
257
        this.container = this.element;
258
259
        this._updateContainerInfo();
260
261
        //init container
262
        this.container.css("overflow","hidden");
263
264
        if (this.options.update_on_resize == true) {
265
            $(window).resize(function() {
266
                me.update();
267
            });
268
        }
269
270
        this.img_object = new $.ui.iviewer.ImageObject(this.options.zoom_animation);
271
272
        if (this.options.mousewheel) {
273
            this.container.bind('mousewheel.iviewer', function(ev, delta)
274
                {
275
                    //this event is there instead of containing div, because
276
                    //at opera it triggers many times on div
277
                    var zoom = (delta > 0)?1:-1,
278
                        container_offset = me.container.offset(),
279
                        mouse_pos = {
280
                            //jquery.mousewheel 3.1.0 uses strange MozMousePixelScroll event
281
                            //which is not being fixed by jQuery.Event
282
                            x: (ev.pageX || ev.originalEvent.pageX) - container_offset.left,
283
                            y: (ev.pageY || ev.originalEvent.pageX) - container_offset.top
284
                        };
285
286
                    me.zoom_by(zoom, mouse_pos);
287
                    return false;
288
                });
289
290
            if (gesturesSupport) {
291
                var gestureThrottle = +new Date();
292
                var originalScale, originalCenter;
293
                this.img_object.object()
294
                    // .bind('gesturestart', function(ev) {
295
                    .bind('touchstart', function(ev) {
296
                        originalScale = me.current_zoom;
297
                        var touches = ev.originalEvent.touches,
298
                            container_offset;
299
                        if (touches.length == 2) {
300
                            container_offset = me.container.offset();
301
                            originalCenter = {
302
                                x: (touches[0].pageX + touches[1].pageX) / 2  - container_offset.left,
303
                                y: (touches[0].pageY + touches[1].pageY) / 2 - container_offset.top
304
                            };
305
                        } else {
306
                            originalCenter = null;
307
                        }
308
                    }).bind('gesturechange', function(ev) {
309
                        //do not want to import throttle function from underscore
310
                        var d = +new Date();
311
                        if ((d - gestureThrottle) < 50) { return; }
312
                        gestureThrottle = d;
313
                        var zoom = originalScale * ev.originalEvent.scale;
314
                        me.set_zoom(zoom, originalCenter);
315
                        ev.preventDefault();
316
                    }).bind('gestureend', function(ev) {
317
                        originalCenter = null;
318
                    });
319
            }
320
        }
321
322
        //init object
323
        this.img_object.object()
324
            //bind mouse events
325
            .click(function(e){return me._click(e)})
326
                .prependTo(this.container);
327
328
        this.container.bind('mousemove', function(ev) { me._handleMouseMove(ev); });
329
330
        this.loadImage(this.options.src);
331
332
        if(!this.options.ui_disabled)
333
        {
334
            this.createui();
335
        }
336
337
        this._mouseInit();
338
    },
339
340
    destroy: function() {
341
        $.Widget.prototype.destroy.call( this );
342
        this._mouseDestroy();
343
        this.img_object.object().remove();
344
        this.container.off('.iviewer');
345
        this.container.css('overflow', ''); //cleanup styles on destroy
346
    },
347
348
    _updateContainerInfo: function()
349
    {
350
        this.options.height = this.container.height();
351
        this.options.width = this.container.width();
352
    },
353
354
    update: function()
355
    {
356
        this._updateContainerInfo()
357
        this.setCoords(this.img_object.x(), this.img_object.y());
358
    },
359
360
    loadImage: function( src )
361
    {
362
        this.current_zoom = this.options.zoom;
363
        var me = this;
364
365
        this._trigger('onStartLoad', 0, src);
366
367
        this.container.addClass("iviewer_loading");
368
        this.img_object.load(src, function() {
369
            me._imageLoaded(src);
370
        }, function() {
371
            me._trigger("onErrorLoad", 0, src);
372
        });
373
    },
374
375
    _imageLoaded: function(src) {
376
        this.container.removeClass("iviewer_loading");
377
        this.container.addClass("iviewer_cursor");
378
379
        if(this.options.zoom == "fit"){
380
            this.fit(true);
381
        }
382
        else {
383
            this.set_zoom(this.options.zoom, true);
384
        }
385
386
        this._trigger('onFinishLoad', 0, src);
387
    },
388
389
    /**
390
    * fits image in the container
391
    *
392
    * @param {boolean} skip_animation
393
    **/
394
    fit: function(skip_animation)
395
    {
396
        var aspect_ratio = this.img_object.orig_width() / this.img_object.orig_height();
397
        var window_ratio = this.options.width /  this.options.height;
398
        var choose_left = (aspect_ratio > window_ratio);
399
        var new_zoom = 0;
400
401
        if(choose_left){
402
            new_zoom = this.options.width / this.img_object.orig_width() * 100;
403
        }
404
        else {
405
            new_zoom = this.options.height / this.img_object.orig_height() * 100;
406
        }
407
408
      this.set_zoom(new_zoom, skip_animation);
409
    },
410
411
    /**
412
    * center image in container
413
    **/
414
    center: function()
415
    {
416
        this.setCoords(-Math.round((this.img_object.display_width() - this.options.width)/2),
417
                -Math.round((this.img_object.display_height() - this.options.height)/2));
418
    },
419
420
    /**
421
    *   move a point in container to the center of display area
422
    *   @param x a point in container
423
    *   @param y a point in container
424
    **/
425
    moveTo: function(x, y)
426
    {
427
        var dx = x-Math.round(this.options.width/2);
428
        var dy = y-Math.round(this.options.height/2);
429
430
        var new_x = this.img_object.x() - dx;
431
        var new_y = this.img_object.y() - dy;
432
433
        this.setCoords(new_x, new_y);
434
    },
435
436
    /**
437
     * Get container offset object.
438
     */
439
    getContainerOffset: function() {
440
        return jQuery.extend({}, this.container.offset());
441
    },
442
443
    /**
444
    * set coordinates of upper left corner of image object
445
    **/
446
    setCoords: function(x,y)
447
    {
448
        //do nothing while image is being loaded
449
        if(!this.img_object.loaded()) { return; }
450
451
        var coords = this._correctCoords(x,y);
452
        this.img_object.x(coords.x);
453
        this.img_object.y(coords.y);
454
    },
455
456
    _correctCoords: function( x, y )
457
    {
458
        x = parseInt(x, 10);
459
        y = parseInt(y, 10);
460
461
        //check new coordinates to be correct (to be in rect)
462
        if(y > 0){
463
            y = 0;
464
        }
465
        if(x > 0){
466
            x = 0;
467
        }
468
        if(y + this.img_object.display_height() < this.options.height){
469
            y = this.options.height - this.img_object.display_height();
470
        }
471
        if(x + this.img_object.display_width() < this.options.width){
472
            x = this.options.width - this.img_object.display_width();
473
        }
474
        if(this.img_object.display_width() <= this.options.width){
475
            x = -(this.img_object.display_width() - this.options.width)/2;
476
        }
477
        if(this.img_object.display_height() <= this.options.height){
478
            y = -(this.img_object.display_height() - this.options.height)/2;
479
        }
480
481
        return { x: x, y:y };
482
    },
483
484
485
    /**
486
    * convert coordinates on the container to the coordinates on the image (in original size)
487
    *
488
    * @return object with fields x,y according to coordinates or false
489
    * if initial coords are not inside image
490
    **/
491
    containerToImage : function (x,y)
492
    {
493
        var coords = { x : x - this.img_object.x(),
494
                 y :  y - this.img_object.y()
495
        };
496
497
        coords = this.img_object.toOriginalCoords(coords);
498
499
        return { x :  util.descaleValue(coords.x, this.current_zoom),
500
                 y :  util.descaleValue(coords.y, this.current_zoom)
501
        };
502
    },
503
504
    /**
505
    * convert coordinates on the image (in original size, and zero angle) to the coordinates on the container
506
    *
507
    * @return object with fields x,y according to coordinates
508
    **/
509
    imageToContainer : function (x,y)
510
    {
511
        var coords = {
512
                x : util.scaleValue(x, this.current_zoom),
513
                y : util.scaleValue(y, this.current_zoom)
514
            };
515
516
        return this.img_object.toRealCoords(coords);
517
    },
518
519
    /**
520
    * get mouse coordinates on the image
521
    * @param e - object containing pageX and pageY fields, e.g. mouse event object
522
    *
523
    * @return object with fields x,y according to coordinates or false
524
    * if initial coords are not inside image
525
    **/
526
    _getMouseCoords : function(e)
527
    {
528
        var containerOffset = this.container.offset();
529
            coords = this.containerToImage(e.pageX - containerOffset.left, e.pageY - containerOffset.top);
530
531
        return coords;
532
    },
533
534
    /**
535
    * set image scale to the new_zoom
536
    *
537
    * @param {number} new_zoom image scale in %
538
    * @param {boolean} skip_animation
539
    * @param {x: number, y: number} Coordinates of point the should not be moved on zoom. The default is the center of image.
540
    **/
541
    set_zoom: function(new_zoom, skip_animation, zoom_center)
542
    {
543
        if (this._trigger('onZoom', 0, new_zoom) == false) {
544
            return;
545
        }
546
547
        //do nothing while image is being loaded
548
        if(!this.img_object.loaded()) { return; }
549
550
        zoom_center = zoom_center || {
551
            x: Math.round(this.options.width/2),
552
            y: Math.round(this.options.height/2)
553
        }
554
555
        if(new_zoom <  this.options.zoom_min)
556
        {
557
            new_zoom = this.options.zoom_min;
558
        }
559
        else if(new_zoom > this.options.zoom_max)
560
        {
561
            new_zoom = this.options.zoom_max;
562
        }
563
564
        /* we fake these values to make fit zoom properly work */
565
        if(this.current_zoom == "fit")
566
        {
567
            var old_x = zoom_center.x + Math.round(this.img_object.orig_width()/2);
568
            var old_y = zoom_center.y + Math.round(this.img_object.orig_height()/2);
569
            this.current_zoom = 100;
570
        }
571
        else {
572
            var old_x = -this.img_object.x() + zoom_center.x;
573
            var old_y = -this.img_object.y() + zoom_center.y
574
        }
575
576
        var new_width = util.scaleValue(this.img_object.orig_width(), new_zoom);
577
        var new_height = util.scaleValue(this.img_object.orig_height(), new_zoom);
578
        var new_x = util.scaleValue( util.descaleValue(old_x, this.current_zoom), new_zoom);
579
        var new_y = util.scaleValue( util.descaleValue(old_y, this.current_zoom), new_zoom);
580
581
        new_x = zoom_center.x - new_x;
582
        new_y = zoom_center.y - new_y;
583
584
        new_width = Math.floor(new_width);
585
        new_height = Math.floor(new_height);
586
        new_x = Math.floor(new_x);
587
        new_y = Math.floor(new_y);
588
589
        this.img_object.display_width(new_width);
590
        this.img_object.display_height(new_height);
591
592
        var coords = this._correctCoords( new_x, new_y ),
593
            self = this;
594
595
        this.img_object.setImageProps(new_width, new_height, coords.x, coords.y,
596
                                        skip_animation, function() {
597
            self._trigger('onAfterZoom', 0, new_zoom );
598
        });
599
        this.current_zoom = new_zoom;
600
601
        this.update_status();
602
    },
603
604
    /**
605
    * changes zoom scale by delta
606
    * zoom is calculated by formula: zoom_base * zoom_delta^rate
607
    * @param Integer delta number to add to the current multiplier rate number
608
    * @param {x: number, y: number=} Coordinates of point the should not be moved on zoom.
609
    **/
610
    zoom_by: function(delta, zoom_center)
611
    {
612
        var closest_rate = this.find_closest_zoom_rate(this.current_zoom);
613
614
        var next_rate = closest_rate + delta;
615
        var next_zoom = this.options.zoom_base * Math.pow(this.options.zoom_delta, next_rate)
616
        if(delta > 0 && next_zoom < this.current_zoom)
617
        {
618
            next_zoom *= this.options.zoom_delta;
619
        }
620
621
        if(delta < 0 && next_zoom > this.current_zoom)
622
        {
623
            next_zoom /= this.options.zoom_delta;
624
        }
625
626
        this.set_zoom(next_zoom, undefined, zoom_center);
627
    },
628
629
    /**
630
    * Rotate image
631
    * @param {num} deg Degrees amount to rotate. Positive values rotate image clockwise.
632
    *     Currently 0, 90, 180, 270 and -90, -180, -270 values are supported
633
    *
634
    * @param {boolean} abs If the flag is true if, the deg parameter will be considered as
635
    *     a absolute value and relative otherwise.
636
    * @return {num|null} Method will return current image angle if called without any arguments.
637
    **/
638
    angle: function(deg, abs) {
639
        if (arguments.length === 0) { return this.img_object.angle(); }
640
641
        if (deg < -270 || deg > 270 || deg % 90 !== 0) { return; }
642
        if (!abs) { deg += this.img_object.angle(); }
643
        if (deg < 0) { deg += 360; }
644
        if (deg >= 360) { deg -= 360; }
645
646
        if (deg === this.img_object.angle()) { return; }
647
648
        this.img_object.angle(deg);
649
        //the rotate behavior is different in all editors. For now we  just center the
650
        //image. However, it will be better to try to keep the position.
651
        this.center();
652
        this._trigger('angle', 0, { angle: this.img_object.angle() });
653
    },
654
655
    /**
656
    * finds closest multiplier rate for value
657
    * basing on zoom_base and zoom_delta values from settings
658
    * @param Number value zoom value to examine
659
    **/
660
    find_closest_zoom_rate: function(value)
661
    {
662
        if(value == this.options.zoom_base)
663
        {
664
            return 0;
665
        }
666
667
        function div(val1,val2) { return val1 / val2 };
668
        function mul(val1,val2) { return val1 * val2 };
669
670
        var func = (value > this.options.zoom_base)?mul:div;
671
        var sgn = (value > this.options.zoom_base)?1:-1;
672
673
        var mltplr = this.options.zoom_delta;
674
        var rate = 1;
675
676
        while(Math.abs(func(this.options.zoom_base, Math.pow(mltplr,rate)) - value) >
677
              Math.abs(func(this.options.zoom_base, Math.pow(mltplr,rate+1)) - value))
678
        {
679
            rate++;
680
        }
681
682
        return sgn * rate;
683
    },
684
685
    /* update scale info in the container */
686
    update_status: function()
687
    {
688
        if(!this.options.ui_disabled)
689
        {
690
            var percent = Math.round(100*this.img_object.display_height()/this.img_object.orig_height());
691
            if(percent)
692
            {
693
                this.zoom_object.html(percent + "%");
694
            }
695
        }
696
    },
697
698
    /**
699
     * Get some information about the image.
700
     *     Currently orig_(width|height), display_(width|height), angle, zoom and src params are supported.
701
     *
702
     *  @param {string} parameter to check
703
     *  @param {boolean} withoutRotation if param is orig_width or orig_height and this flag is set to true,
704
     *      method will return original image width without considering rotation.
705
     *
706
     */
707
    info: function(param, withoutRotation) {
708
        if (!param) { return; }
709
710
        switch (param) {
711
            case 'orig_width':
712
            case 'orig_height':
713
                if (withoutRotation) {
714
                    return (this.img_object.angle() % 180 === 0 ? this.img_object[param]() :
715
                            param === 'orig_width' ? this.img_object.orig_height() :
716
                                                        this.img_object.orig_width());
717
                } else {
718
                    return this.img_object[param]();
719
                }
720
            case 'display_width':
721
            case 'display_height':
722
            case 'angle':
723
                return this.img_object[param]();
724
            case 'zoom':
725
                return this.current_zoom;
726
            case 'src':
727
                return this.img_object.object().attr('src');
728
            case 'coords':
729
                return {
730
                    x: this.img_object.x(),
731
                    y: this.img_object.y()
732
                };
733
        }
734
    },
735
736
    /**
737
    *   callback for handling mousdown event to start dragging image
738
    **/
739
    _mouseStart: function( e )
740
    {
741
        $.ui.mouse.prototype._mouseStart.call(this, e);
742
        if (this._trigger('onStartDrag', 0, this._getMouseCoords(e)) === false) {
743
            return false;
744
        }
745
746
        /* start drag event*/
747
        this.container.addClass("iviewer_drag_cursor");
748
749
        //#10: fix movement quirks for ipad
750
        this._dragInitialized = !(e.originalEvent.changedTouches && e.originalEvent.changedTouches.length==1);
751
752
        this.dx = e.pageX - this.img_object.x();
753
        this.dy = e.pageY - this.img_object.y();
754
        return true;
755
    },
756
757
    _mouseCapture: function( e ) {
758
        return true;
759
    },
760
761
    /**
762
     * Handle mouse move if needed. User can avoid using this callback, because
763
     *    he can get the same information through public methods.
764
     *  @param {jQuery.Event} e
765
     */
766
    _handleMouseMove: function(e) {
767
        this._trigger('onMouseMove', e, this._getMouseCoords(e));
768
    },
769
770
    /**
771
    *   callback for handling mousemove event to drag image
772
    **/
773
    _mouseDrag: function(e)
774
    {
775
        $.ui.mouse.prototype._mouseDrag.call(this, e);
776
777
        //#10: imitate mouseStart, because we can get here without it on iPad for some reason
778
        if (!this._dragInitialized) {
779
            this.dx = e.pageX - this.img_object.x();
780
            this.dy = e.pageY - this.img_object.y();
781
            this._dragInitialized = true;
782
        }
783
784
        var ltop =  e.pageY - this.dy;
785
        var lleft = e.pageX - this.dx;
786
787
        this.setCoords(lleft, ltop);
788
        this._trigger('onDrag', e, this._getMouseCoords(e));
789
        return false;
790
    },
791
792
    /**
793
    *   callback for handling stop drag
794
    **/
795
    _mouseStop: function(e)
796
    {
797
        $.ui.mouse.prototype._mouseStop.call(this, e);
798
        this.container.removeClass("iviewer_drag_cursor");
799
        this._trigger('onStopDrag', 0, this._getMouseCoords(e));
800
    },
801
802
    _click: function(e)
803
    {
804
        this._trigger('onClick', 0, this._getMouseCoords(e));
805
    },
806
807
    /**
808
    *   create zoom buttons info box
809
    **/
810
    createui: function()
811
    {
812
        var me=this;
813
814
        $("<div>", { 'class': "iviewer_zoom_in iviewer_common iviewer_button"})
815
                    .bind('mousedown touchstart',function(){me.zoom_by(1); return false;})
816
                    .appendTo(this.container);
817
818
        $("<div>", { 'class': "iviewer_zoom_out iviewer_common iviewer_button"})
819
                    .bind('mousedown touchstart',function(){me.zoom_by(- 1); return false;})
820
                    .appendTo(this.container);
821
822
        $("<div>", { 'class': "iviewer_zoom_zero iviewer_common iviewer_button"})
823
                    .bind('mousedown touchstart',function(){me.set_zoom(100); return false;})
824
                    .appendTo(this.container);
825
826
        $("<div>", { 'class': "iviewer_zoom_fit iviewer_common iviewer_button"})
827
                    .bind('mousedown touchstart',function(){me.fit(this); return false;})
828
                    .appendTo(this.container);
829
830
        this.zoom_object = $("<div>").addClass("iviewer_zoom_status iviewer_common")
831
                                    .appendTo(this.container);
832
833
        $("<div>", { 'class': "iviewer_rotate_left iviewer_common iviewer_button"})
834
                    .bind('mousedown touchstart',function(){me.angle(-90); return false;})
835
                    .appendTo(this.container);
836
837
        $("<div>", { 'class': "iviewer_rotate_right iviewer_common iviewer_button" })
838
                    .bind('mousedown touchstart',function(){me.angle(90); return false;})
839
                    .appendTo(this.container);
840
841
        this.update_status(); //initial status update
842
    }
843
844
} );
845
846
/**
847
 * @class $.ui.iviewer.ImageObject Class represents image and provides public api without
848
 *     extending image prototype.
849
 * @constructor
850
 * @param {boolean} do_anim Do we want to animate image on dimension changes?
851
 */
852
$.ui.iviewer.ImageObject = function(do_anim) {
853
    this._img = $("<img>")
854
            //this is needed, because chromium sets them auto otherwise
855
            .css({ position: "absolute", top :"0px", left: "0px"});
856
857
    this._loaded = false;
858
    this._swapDimensions = false;
859
    this._do_anim = do_anim || false;
860
    this.x(0, true);
861
    this.y(0, true);
862
    this.angle(0);
863
};
864
865
866
/** @lends $.ui.iviewer.ImageObject.prototype */
867
(function() {
868
    /**
869
     * Restore initial object state.
870
     *
871
     * @param {number} w Image width.
872
     * @param {number} h Image height.
873
     */
874
    this._reset = function(w, h) {
875
        this._angle = 0;
876
        this._swapDimensions = false;
877
        this.x(0);
878
        this.y(0);
879
880
        this.orig_width(w);
881
        this.orig_height(h);
882
        this.display_width(w);
883
        this.display_height(h);
884
    };
885
886
    /**
887
     * Check if image is loaded.
888
     *
889
     * @return {boolean}
890
     */
891
    this.loaded = function() { return this._loaded; };
892
893
    /**
894
     * Load image.
895
     *
896
     * @param {string} src Image url.
897
     * @param {Function=} loaded Function will be called on image load.
898
     */
899
    this.load = function(src, loaded, error) {
900
        var self = this;
901
902
        loaded = loaded || jQuery.noop;
903
        this._loaded = false;
904
905
        //If we assign new image url to the this._img IE9 fires onload event and image width and
906
        //height are set to zero. So, we create another image object and load image through it.
907
        var img = new Image();
908
        img.onload = function() {
909
            self._loaded = true;
910
            self._reset(this.width, this.height);
911
912
            self._img
913
                .removeAttr("width")
914
                .removeAttr("height")
915
                .removeAttr("style")
916
                //max-width is reset, because plugin breaks in the twitter bootstrap otherwise
917
                .css({ position: "absolute", top :"0px", left: "0px", maxWidth: "none"})
918
919
            self._img[0].src = src;
920
            loaded();
921
        };
922
923
        img.onerror = error;
924
925
        //we need this because sometimes internet explorer 8 fires onload event
926
        //right after assignment (synchronously)
927
        setTimeout(function() {
928
            img.src = src;
929
        }, 0);
930
931
        this.angle(0);
932
    };
933
934
    this._dimension = function(prefix, name) {
935
        var horiz = '_' + prefix + '_' + name,
936
            vert = '_' + prefix + '_' + (name === 'height' ? 'width' : 'height');
937
        return setter(function(val) {
938
                this[this._swapDimensions ? horiz: vert] = val;
939
            },
940
            function() {
941
                return this[this._swapDimensions ? horiz: vert];
942
            });
943
    };
944
945
    /**
946
     * Getters and setter for common image dimensions.
947
     *    display_ means real image tag dimensions
948
     *    orig_ means physical image dimensions.
949
     *  Note, that dimensions are swapped if image is rotated. It necessary,
950
     *  because as little as possible code should know about rotation.
951
     */
952
    this.display_width = this._dimension('display', 'width'),
953
    this.display_height = this._dimension('display', 'height'),
954
    this.display_diff = function() { return Math.floor( this.display_width() - this.display_height() ) };
955
    this.orig_width = this._dimension('orig', 'width'),
956
    this.orig_height = this._dimension('orig', 'height'),
957
958
    /**
959
     * Setter for  X coordinate. If image is rotated we need to additionaly shift an
960
     *     image to map image coordinate to the visual position.
961
     *
962
     * @param {number} val Coordinate value.
963
     * @param {boolean} skipCss If true, we only set the value and do not touch the dom.
964
     */
965
    this.x = setter(function(val, skipCss) {
966
            this._x = val;
967
            if (!skipCss) {
968
                this._finishAnimation();
969
                this._img.css("left",this._x + (this._swapDimensions ? this.display_diff() / 2 : 0) + "px");
970
            }
971
        },
972
        function() {
973
            return this._x;
974
        });
975
976
    /**
977
     * Setter for  Y coordinate. If image is rotated we need to additionaly shift an
978
     *     image to map image coordinate to the visual position.
979
     *
980
     * @param {number} val Coordinate value.
981
     * @param {boolean} skipCss If true, we only set the value and do not touch the dom.
982
     */
983
    this.y = setter(function(val, skipCss) {
984
            this._y = val;
985
            if (!skipCss) {
986
                this._finishAnimation();
987
                this._img.css("top",this._y - (this._swapDimensions ? this.display_diff() / 2 : 0) + "px");
988
            }
989
        },
990
       function() {
991
            return this._y;
992
       });
993
994
    /**
995
     * Perform image rotation.
996
     *
997
     * @param {number} deg Absolute image angle. The method will work with values 0, 90, 180, 270 degrees.
998
     */
999
    this.angle = setter(function(deg) {
1000
            var prevSwap = this._swapDimensions;
1001
1002
            this._angle = deg;
1003
            this._swapDimensions = deg % 180 !== 0;
1004
1005
            if (prevSwap !== this._swapDimensions) {
1006
                var verticalMod = this._swapDimensions ? -1 : 1;
1007
                this.x(this.x() - verticalMod * this.display_diff() / 2, true);
1008
                this.y(this.y() + verticalMod * this.display_diff() / 2, true);
1009
            };
1010
1011
            var cssVal = 'rotate(' + deg + 'deg)',
1012
                img = this._img;
1013
1014
            jQuery.each(['', '-webkit-', '-moz-', '-o-', '-ms-'], function(i, prefix) {
1015
                img.css(prefix + 'transform', cssVal);
1016
            });
1017
1018
            if (useIeTransforms) {
1019
                jQuery.each(['-ms-', ''], function(i, prefix) {
1020
                    img.css(prefix + 'filter', ieTransforms[deg].filter);
1021
                });
1022
1023
                img.css({
1024
                    marginLeft: ieTransforms[deg].marginLeft * this.display_diff() / 2,
1025
                    marginTop: ieTransforms[deg].marginTop * this.display_diff() / 2
1026
                });
1027
            }
1028
        },
1029
       function() { return this._angle; });
1030
1031
    /**
1032
     * Map point in the container coordinates to the point in image coordinates.
1033
     *     You will get coordinates of point on image with respect to rotation,
1034
     *     but will be set as if image was not rotated.
1035
     *     So, if image was rotated 90 degrees, it's (0,0) point will be on the
1036
     *     top right corner.
1037
     *
1038
     * @param {{x: number, y: number}} point Point in container coordinates.
1039
     * @return  {{x: number, y: number}}
1040
     */
1041
    this.toOriginalCoords = function(point) {
1042
        switch (this.angle()) {
1043
            case 0: return { x: point.x, y: point.y }
1044
            case 90: return { x: point.y, y: this.display_width() - point.x }
1045
            case 180: return { x: this.display_width() - point.x, y: this.display_height() - point.y }
1046
            case 270: return { x: this.display_height() - point.y, y: point.x }
1047
        }
1048
    };
1049
1050
    /**
1051
     * Map point in the image coordinates to the point in container coordinates.
1052
     *     You will get coordinates of point on container with respect to rotation.
1053
     *     Note, if image was rotated 90 degrees, it's (0,0) point will be on the
1054
     *     top right corner.
1055
     *
1056
     * @param {{x: number, y: number}} point Point in container coordinates.
1057
     * @return  {{x: number, y: number}}
1058
     */
1059
    this.toRealCoords = function(point) {
1060
        switch (this.angle()) {
1061
            case 0: return { x: this.x() + point.x, y: this.y() + point.y }
1062
            case 90: return { x: this.x() + this.display_width() - point.y, y: this.y() + point.x}
1063
            case 180: return { x: this.x() + this.display_width() - point.x, y: this.y() + this.display_height() - point.y}
1064
            case 270: return { x: this.x() + point.y, y: this.y() + this.display_height() - point.x}
1065
        }
1066
    };
1067
1068
    /**
1069
     * @return {jQuery} Return image node. this is needed to add event handlers.
1070
     */
1071
    this.object = setter(jQuery.noop,
1072
                           function() { return this._img; });
1073
1074
    /**
1075
     * Change image properties.
1076
     *
1077
     * @param {number} disp_w Display width;
1078
     * @param {number} disp_h Display height;
1079
     * @param {number} x
1080
     * @param {number} y
1081
     * @param {boolean} skip_animation If true, the animation will be skiped despite the
1082
     *     value set in constructor.
1083
     * @param {Function=} complete Call back will be fired when zoom will be complete.
1084
     */
1085
    this.setImageProps = function(disp_w, disp_h, x, y, skip_animation, complete) {
1086
        complete = complete || jQuery.noop;
1087
1088
        this.display_width(disp_w);
1089
        this.display_height(disp_h);
1090
        this.x(x, true);
1091
        this.y(y, true);
1092
1093
        var w = this._swapDimensions ? disp_h : disp_w;
1094
        var h = this._swapDimensions ? disp_w : disp_h;
1095
1096
        var params = {
1097
            width: w,
1098
            height: h,
1099
            top: y - (this._swapDimensions ? this.display_diff() / 2 : 0) + "px",
1100
            left: x + (this._swapDimensions ? this.display_diff() / 2 : 0) + "px"
1101
        };
1102
1103
        if (useIeTransforms) {
1104
            jQuery.extend(params, {
1105
                marginLeft: ieTransforms[this.angle()].marginLeft * this.display_diff() / 2,
1106
                marginTop: ieTransforms[this.angle()].marginTop * this.display_diff() / 2
1107
            });
1108
        }
1109
1110
        var swapDims = this._swapDimensions,
1111
            img = this._img;
1112
1113
        //here we come: another IE oddness. If image is rotated 90 degrees with a filter, than
1114
        //width and height getters return real width and height of rotated image. The bad news
1115
        //is that to set height you need to set a width and vice versa. Fuck IE.
1116
        //So, in this case we have to animate width and height manually.
1117
        if(useIeTransforms && swapDims) {
1118
            var ieh = this._img.width(),
1119
                iew = this._img.height(),
1120
                iedh = params.height - ieh;
1121
                iedw = params.width - iew;
1122
1123
            delete params.width;
1124
            delete params.height;
1125
        }
1126
1127
        if (this._do_anim && !skip_animation) {
1128
            this._img.stop(true)
1129
                .animate(params, {
1130
                    duration: 200,
1131
                    complete: complete,
1132
                    step: function(now, fx) {
1133
                        if(useIeTransforms && swapDims && (fx.prop === 'top')) {
1134
                            var percent = (now - fx.start) / (fx.end - fx.start);
1135
1136
                            img.height(ieh + iedh * percent);
1137
                            img.width(iew + iedw * percent);
1138
                            img.css('top', now);
1139
                        }
1140
                    }
1141
                });
1142
        } else {
1143
            this._img.css(params);
1144
            setTimeout(complete, 0); //both if branches should behave equally.
1145
        }
1146
    };
1147
1148
    //if we set image coordinates we need to be sure that no animation is active atm
1149
    this._finishAnimation = function() {
1150
      this._img.stop(true, true);
1151
    }
1152
1153
}).apply($.ui.iviewer.ImageObject.prototype);
1154
1155
1156
1157
var util = {
1158
    scaleValue: function(value, toZoom)
1159
    {
1160
        return value * toZoom / 100;
1161
    },
1162
1163
    descaleValue: function(value, fromZoom)
1164
    {
1165
        return value * 100 / fromZoom;
1166
    }
1167
};
1168
1169
 } )( jQuery, undefined );
1170

docs/js/jquery.iviewer.min.js 1 location

@@ 13-47 (lines=35) @@
10
 * Author: Dmitry Petrov
11
 * Version: 0.7.7
12
 */
13
(function($,undefined){var mouseEvents={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup"},gesturesSupport="ongesturestart"in document.createElement("div");function makeMouseEvent(event){var touch=event.originalEvent.changedTouches[0];return $.extend(event,{type:mouseEvents[event.type],which:1,pageX:touch.pageX,pageY:touch.pageY,screenX:touch.screenX,screenY:touch.screenY,clientX:touch.clientX,clientY:touch.clientY,isTouchEvent:true})}var mouseProto=$.ui.mouse.prototype,_mouseInit=$.ui.mouse.prototype._mouseInit;
14
mouseProto._mouseInit=function(){var self=this;self._touchActive=false;this.element.bind("touchstart."+this.widgetName,function(event){if(gesturesSupport&&event.originalEvent.touches.length>1)return;self._touchActive=true;return self._mouseDown(makeMouseEvent(event))});var self=this;this._mouseMoveDelegate=function(event){if(gesturesSupport&&event.originalEvent.touches&&event.originalEvent.touches.length>1)return;if(self._touchActive)return self._mouseMove(makeMouseEvent(event))};this._mouseUpDelegate=
15
function(event){if(self._touchActive){self._touchActive=false;return self._mouseUp(makeMouseEvent(event))}};$(document).bind("touchmove."+this.widgetName,this._mouseMoveDelegate).bind("touchend."+this.widgetName,this._mouseUpDelegate);_mouseInit.apply(this)};var setter=function(setter,getter){return function(val){if(arguments.length===0)return getter.apply(this);else setter.apply(this,arguments)}};var ieTransforms={"0":{marginLeft:0,marginTop:0,filter:'progid:DXImageTransform.Microsoft.Matrix(M11=1, M12=0, M21=0, M22=1, SizingMethod="auto expand")'},
16
90:{marginLeft:-1,marginTop:1,filter:'progid:DXImageTransform.Microsoft.Matrix(M11=0, M12=-1, M21=1, M22=0, SizingMethod="auto expand")'},180:{marginLeft:0,marginTop:0,filter:'progid:DXImageTransform.Microsoft.Matrix(M11=-1, M12=0, M21=0, M22=-1, SizingMethod="auto expand")'},270:{marginLeft:-1,marginTop:1,filter:'progid:DXImageTransform.Microsoft.Matrix(M11=0, M12=1, M21=-1, M22=0, SizingMethod="auto expand")'}},useIeTransforms=function(){var el=document.createElement("div");el.style.cssText=["-ms-",
17
"",""].join("filter:blur(2px); ");return!!el.style.cssText&&document.documentMode<9}();$.widget("ui.iviewer",$.ui.mouse,{widgetEventPrefix:"iviewer",options:{zoom:"fit",zoom_base:100,zoom_max:800,zoom_min:25,zoom_delta:1.4,zoom_animation:true,ui_disabled:false,mousewheel:true,update_on_resize:true,onZoom:jQuery.noop,onAfterZoom:jQuery.noop,onStartDrag:jQuery.noop,onDrag:jQuery.noop,onStopDrag:jQuery.noop,onMouseMove:jQuery.noop,onClick:jQuery.noop,onStartLoad:null,onFinishLoad:null,onErrorLoad:null},
18
_create:function(){var me=this;this.dx=0;this.dy=0;this.img_object={};this.zoom_object={};this._angle=0;this.current_zoom=this.options.zoom;if(this.options.src===null)return;this.container=this.element;this._updateContainerInfo();this.container.css("overflow","hidden");if(this.options.update_on_resize==true)$(window).resize(function(){me.update()});this.img_object=new $.ui.iviewer.ImageObject(this.options.zoom_animation);if(this.options.mousewheel){this.container.bind("mousewheel.iviewer",function(ev,
19
delta){var zoom=delta>0?1:-1,container_offset=me.container.offset(),mouse_pos={x:ev.pageX-container_offset.left,y:ev.pageY-container_offset.top};me.zoom_by(zoom,mouse_pos);return false});if(gesturesSupport){var gestureThrottle=+new Date;var originalScale,originalCenter;this.img_object.object().bind("touchstart",function(ev){originalScale=me.current_zoom;var touches=ev.originalEvent.touches,container_offset;if(touches.length==2){container_offset=me.container.offset();originalCenter={x:(touches[0].pageX+
20
touches[1].pageX)/2-container_offset.left,y:(touches[0].pageY+touches[1].pageY)/2-container_offset.top}}else originalCenter=null}).bind("gesturechange",function(ev){var d=+new Date;if(d-gestureThrottle<50)return;gestureThrottle=d;var zoom=originalScale*ev.originalEvent.scale;me.set_zoom(zoom,originalCenter);ev.preventDefault()}).bind("gestureend",function(ev){originalCenter=null})}}this.img_object.object().click(function(e){return me._click(e)}).prependTo(this.container);this.container.bind("mousemove",
21
function(ev){me._handleMouseMove(ev)});this.loadImage(this.options.src);if(!this.options.ui_disabled)this.createui();this._mouseInit()},destroy:function(){$.Widget.prototype.destroy.call(this);this._mouseDestroy();this.img_object.object().remove();this.container.off(".iviewer");this.container.css("overflow","")},_updateContainerInfo:function(){this.options.height=this.container.height();this.options.width=this.container.width()},update:function(){this._updateContainerInfo();this.setCoords(this.img_object.x(),
22
this.img_object.y())},loadImage:function(src){this.current_zoom=this.options.zoom;var me=this;this._trigger("onStartLoad",0,src);this.container.addClass("iviewer_loading");this.img_object.load(src,function(){me._imageLoaded(src)},function(){me._trigger("onErrorLoad",0,src)})},_imageLoaded:function(src){this.container.removeClass("iviewer_loading");this.container.addClass("iviewer_cursor");if(this.options.zoom=="fit")this.fit(true);else this.set_zoom(this.options.zoom,true);this._trigger("onFinishLoad",
23
0,src)},fit:function(skip_animation){var aspect_ratio=this.img_object.orig_width()/this.img_object.orig_height();var window_ratio=this.options.width/this.options.height;var choose_left=aspect_ratio>window_ratio;var new_zoom=0;if(choose_left)new_zoom=this.options.width/this.img_object.orig_width()*100;else new_zoom=this.options.height/this.img_object.orig_height()*100;this.set_zoom(new_zoom,skip_animation)},center:function(){this.setCoords(-Math.round((this.img_object.display_width()-this.options.width)/
24
2),-Math.round((this.img_object.display_height()-this.options.height)/2))},moveTo:function(x,y){var dx=x-Math.round(this.options.width/2);var dy=y-Math.round(this.options.height/2);var new_x=this.img_object.x()-dx;var new_y=this.img_object.y()-dy;this.setCoords(new_x,new_y)},getContainerOffset:function(){return jQuery.extend({},this.container.offset())},setCoords:function(x,y){if(!this.img_object.loaded())return;var coords=this._correctCoords(x,y);this.img_object.x(coords.x);this.img_object.y(coords.y)},
25
_correctCoords:function(x,y){x=parseInt(x,10);y=parseInt(y,10);if(y>0)y=0;if(x>0)x=0;if(y+this.img_object.display_height()<this.options.height)y=this.options.height-this.img_object.display_height();if(x+this.img_object.display_width()<this.options.width)x=this.options.width-this.img_object.display_width();if(this.img_object.display_width()<=this.options.width)x=-(this.img_object.display_width()-this.options.width)/2;if(this.img_object.display_height()<=this.options.height)y=-(this.img_object.display_height()-
26
this.options.height)/2;return{x:x,y:y}},containerToImage:function(x,y){var coords={x:x-this.img_object.x(),y:y-this.img_object.y()};coords=this.img_object.toOriginalCoords(coords);return{x:util.descaleValue(coords.x,this.current_zoom),y:util.descaleValue(coords.y,this.current_zoom)}},imageToContainer:function(x,y){var coords={x:util.scaleValue(x,this.current_zoom),y:util.scaleValue(y,this.current_zoom)};return this.img_object.toRealCoords(coords)},_getMouseCoords:function(e){var containerOffset=this.container.offset();
27
coords=this.containerToImage(e.pageX-containerOffset.left,e.pageY-containerOffset.top);return coords},set_zoom:function(new_zoom,skip_animation,zoom_center){if(this._trigger("onZoom",0,new_zoom)==false)return;if(!this.img_object.loaded())return;zoom_center=zoom_center||{x:Math.round(this.options.width/2),y:Math.round(this.options.height/2)};if(new_zoom<this.options.zoom_min)new_zoom=this.options.zoom_min;else if(new_zoom>this.options.zoom_max)new_zoom=this.options.zoom_max;if(this.current_zoom=="fit"){var old_x=
28
zoom_center.x+Math.round(this.img_object.orig_width()/2);var old_y=zoom_center.y+Math.round(this.img_object.orig_height()/2);this.current_zoom=100}else{var old_x=-this.img_object.x()+zoom_center.x;var old_y=-this.img_object.y()+zoom_center.y}var new_width=util.scaleValue(this.img_object.orig_width(),new_zoom);var new_height=util.scaleValue(this.img_object.orig_height(),new_zoom);var new_x=util.scaleValue(util.descaleValue(old_x,this.current_zoom),new_zoom);var new_y=util.scaleValue(util.descaleValue(old_y,
29
this.current_zoom),new_zoom);new_x=zoom_center.x-new_x;new_y=zoom_center.y-new_y;new_width=Math.floor(new_width);new_height=Math.floor(new_height);new_x=Math.floor(new_x);new_y=Math.floor(new_y);this.img_object.display_width(new_width);this.img_object.display_height(new_height);var coords=this._correctCoords(new_x,new_y),self=this;this.img_object.setImageProps(new_width,new_height,coords.x,coords.y,skip_animation,function(){self._trigger("onAfterZoom",0,new_zoom)});this.current_zoom=new_zoom;this.update_status()},
30
zoom_by:function(delta,zoom_center){var closest_rate=this.find_closest_zoom_rate(this.current_zoom);var next_rate=closest_rate+delta;var next_zoom=this.options.zoom_base*Math.pow(this.options.zoom_delta,next_rate);if(delta>0&&next_zoom<this.current_zoom)next_zoom*=this.options.zoom_delta;if(delta<0&&next_zoom>this.current_zoom)next_zoom/=this.options.zoom_delta;this.set_zoom(next_zoom,undefined,zoom_center)},angle:function(deg,abs){if(arguments.length===0)return this.img_object.angle();if(deg<-270||
31
deg>270||deg%90!==0)return;if(!abs)deg+=this.img_object.angle();if(deg<0)deg+=360;if(deg>=360)deg-=360;if(deg===this.img_object.angle())return;this.img_object.angle(deg);this.center();this._trigger("angle",0,{angle:this.img_object.angle()})},find_closest_zoom_rate:function(value){if(value==this.options.zoom_base)return 0;function div(val1,val2){return val1/val2}function mul(val1,val2){return val1*val2}var func=value>this.options.zoom_base?mul:div;var sgn=value>this.options.zoom_base?1:-1;var mltplr=
32
this.options.zoom_delta;var rate=1;while(Math.abs(func(this.options.zoom_base,Math.pow(mltplr,rate))-value)>Math.abs(func(this.options.zoom_base,Math.pow(mltplr,rate+1))-value))rate++;return sgn*rate},update_status:function(){if(!this.options.ui_disabled){var percent=Math.round(100*this.img_object.display_height()/this.img_object.orig_height());if(percent)this.zoom_object.html(percent+"%")}},info:function(param,withoutRotation){if(!param)return;switch(param){case "orig_width":case "orig_height":if(withoutRotation)return this.img_object.angle()%
33
180===0?this.img_object[param]():param==="orig_width"?this.img_object.orig_height():this.img_object.orig_width();else return this.img_object[param]();case "display_width":case "display_height":case "angle":return this.img_object[param]();case "zoom":return this.current_zoom;case "src":return this.img_object.object().attr("src");case "coords":return{x:this.img_object.x(),y:this.img_object.y()}}},_mouseStart:function(e){$.ui.mouse.prototype._mouseStart.call(this,e);if(this._trigger("onStartDrag",0,
34
this._getMouseCoords(e))===false)return false;this.container.addClass("iviewer_drag_cursor");this._dragInitialized=!(e.originalEvent.changedTouches&&e.originalEvent.changedTouches.length==1);this.dx=e.pageX-this.img_object.x();this.dy=e.pageY-this.img_object.y();return true},_mouseCapture:function(e){return true},_handleMouseMove:function(e){this._trigger("onMouseMove",e,this._getMouseCoords(e))},_mouseDrag:function(e){$.ui.mouse.prototype._mouseDrag.call(this,e);if(!this._dragInitialized){this.dx=
35
e.pageX-this.img_object.x();this.dy=e.pageY-this.img_object.y();this._dragInitialized=true}var ltop=e.pageY-this.dy;var lleft=e.pageX-this.dx;this.setCoords(lleft,ltop);this._trigger("onDrag",e,this._getMouseCoords(e));return false},_mouseStop:function(e){$.ui.mouse.prototype._mouseStop.call(this,e);this.container.removeClass("iviewer_drag_cursor");this._trigger("onStopDrag",0,this._getMouseCoords(e))},_click:function(e){this._trigger("onClick",0,this._getMouseCoords(e))},createui:function(){var me=
36
this;$("<div>",{"class":"iviewer_zoom_in iviewer_common iviewer_button"}).bind("mousedown touchstart",function(){me.zoom_by(1);return false}).appendTo(this.container);$("<div>",{"class":"iviewer_zoom_out iviewer_common iviewer_button"}).bind("mousedown touchstart",function(){me.zoom_by(-1);return false}).appendTo(this.container);$("<div>",{"class":"iviewer_zoom_zero iviewer_common iviewer_button"}).bind("mousedown touchstart",function(){me.set_zoom(100);return false}).appendTo(this.container);$("<div>",
37
{"class":"iviewer_zoom_fit iviewer_common iviewer_button"}).bind("mousedown touchstart",function(){me.fit(this);return false}).appendTo(this.container);this.zoom_object=$("<div>").addClass("iviewer_zoom_status iviewer_common").appendTo(this.container);$("<div>",{"class":"iviewer_rotate_left iviewer_common iviewer_button"}).bind("mousedown touchstart",function(){me.angle(-90);return false}).appendTo(this.container);$("<div>",{"class":"iviewer_rotate_right iviewer_common iviewer_button"}).bind("mousedown touchstart",
38
function(){me.angle(90);return false}).appendTo(this.container);this.update_status()}});$.ui.iviewer.ImageObject=function(do_anim){this._img=$("<img>").css({position:"absolute",top:"0px",left:"0px"});this._loaded=false;this._swapDimensions=false;this._do_anim=do_anim||false;this.x(0,true);this.y(0,true);this.angle(0)};(function(){this._reset=function(w,h){this._angle=0;this._swapDimensions=false;this.x(0);this.y(0);this.orig_width(w);this.orig_height(h);this.display_width(w);this.display_height(h)};
39
this.loaded=function(){return this._loaded};this.load=function(src,loaded,error){var self=this;loaded=loaded||jQuery.noop;this._loaded=false;var img=new Image;img.onload=function(){self._loaded=true;self._reset(this.width,this.height);self._img.removeAttr("width").removeAttr("height").removeAttr("style").css({position:"absolute",top:"0px",left:"0px",maxWidth:"none"});self._img[0].src=src;loaded()};img.onerror=error;setTimeout(function(){img.src=src},0);this.angle(0)};this._dimension=function(prefix,
40
name){var horiz="_"+prefix+"_"+name,vert="_"+prefix+"_"+(name==="height"?"width":"height");return setter(function(val){this[this._swapDimensions?horiz:vert]=val},function(){return this[this._swapDimensions?horiz:vert]})};this.display_width=this._dimension("display","width"),this.display_height=this._dimension("display","height"),this.display_diff=function(){return Math.floor(this.display_width()-this.display_height())};this.orig_width=this._dimension("orig","width"),this.orig_height=this._dimension("orig",
41
"height"),this.x=setter(function(val,skipCss){this._x=val;if(!skipCss){this._finishAnimation();this._img.css("left",this._x+(this._swapDimensions?this.display_diff()/2:0)+"px")}},function(){return this._x});this.y=setter(function(val,skipCss){this._y=val;if(!skipCss){this._finishAnimation();this._img.css("top",this._y-(this._swapDimensions?this.display_diff()/2:0)+"px")}},function(){return this._y});this.angle=setter(function(deg){var prevSwap=this._swapDimensions;this._angle=deg;this._swapDimensions=
42
deg%180!==0;if(prevSwap!==this._swapDimensions){var verticalMod=this._swapDimensions?-1:1;this.x(this.x()-verticalMod*this.display_diff()/2,true);this.y(this.y()+verticalMod*this.display_diff()/2,true)}var cssVal="rotate("+deg+"deg)",img=this._img;jQuery.each(["","-webkit-","-moz-","-o-","-ms-"],function(i,prefix){img.css(prefix+"transform",cssVal)});if(useIeTransforms){jQuery.each(["-ms-",""],function(i,prefix){img.css(prefix+"filter",ieTransforms[deg].filter)});img.css({marginLeft:ieTransforms[deg].marginLeft*
43
this.display_diff()/2,marginTop:ieTransforms[deg].marginTop*this.display_diff()/2})}},function(){return this._angle});this.toOriginalCoords=function(point){switch(this.angle()){case 0:return{x:point.x,y:point.y};case 90:return{x:point.y,y:this.display_width()-point.x};case 180:return{x:this.display_width()-point.x,y:this.display_height()-point.y};case 270:return{x:this.display_height()-point.y,y:point.x}}};this.toRealCoords=function(point){switch(this.angle()){case 0:return{x:this.x()+point.x,y:this.y()+
44
point.y};case 90:return{x:this.x()+this.display_width()-point.y,y:this.y()+point.x};case 180:return{x:this.x()+this.display_width()-point.x,y:this.y()+this.display_height()-point.y};case 270:return{x:this.x()+point.y,y:this.y()+this.display_height()-point.x}}};this.object=setter(jQuery.noop,function(){return this._img});this.setImageProps=function(disp_w,disp_h,x,y,skip_animation,complete){complete=complete||jQuery.noop;this.display_width(disp_w);this.display_height(disp_h);this.x(x,true);this.y(y,
45
true);var w=this._swapDimensions?disp_h:disp_w;var h=this._swapDimensions?disp_w:disp_h;var params={width:w,height:h,top:y-(this._swapDimensions?this.display_diff()/2:0)+"px",left:x+(this._swapDimensions?this.display_diff()/2:0)+"px"};if(useIeTransforms)jQuery.extend(params,{marginLeft:ieTransforms[this.angle()].marginLeft*this.display_diff()/2,marginTop:ieTransforms[this.angle()].marginTop*this.display_diff()/2});var swapDims=this._swapDimensions,img=this._img;if(useIeTransforms&&swapDims){var ieh=
46
this._img.width(),iew=this._img.height(),iedh=params.height-ieh;iedw=params.width-iew;delete params.width;delete params.height}if(this._do_anim&&!skip_animation)this._img.stop(true).animate(params,{duration:200,complete:complete,step:function(now,fx){if(useIeTransforms&&swapDims&&fx.prop==="top"){var percent=(now-fx.start)/(fx.end-fx.start);img.height(ieh+iedh*percent);img.width(iew+iedw*percent);img.css("top",now)}}});else{this._img.css(params);setTimeout(complete,0)}};this._finishAnimation=function(){this._img.stop(true,
47
true)}}).apply($.ui.iviewer.ImageObject.prototype);var util={scaleValue:function(value,toZoom){return value*toZoom/100},descaleValue:function(value,fromZoom){return value*100/fromZoom}}})(jQuery,undefined);
48