changeset 1610:f425f00bf5e3 geom_prototype

changed digilib.geometry to use new and prototypes instead of adding methods to objects.
author robcast
date Sun, 12 Mar 2017 15:53:01 +0100
parents 3f3a4f4eecb1
children 0368a59dd0cf
files webapp/src/main/webapp/jquery/jquery.digilib.annotator.js webapp/src/main/webapp/jquery/jquery.digilib.arrows.js webapp/src/main/webapp/jquery/jquery.digilib.birdseye.js webapp/src/main/webapp/jquery/jquery.digilib.dialogs.js webapp/src/main/webapp/jquery/jquery.digilib.geometry.js webapp/src/main/webapp/jquery/jquery.digilib.js webapp/src/main/webapp/jquery/jquery.digilib.marks.js webapp/src/main/webapp/jquery/jquery.digilib.measure.js webapp/src/main/webapp/jquery/jquery.digilib.pluginstub.js webapp/src/main/webapp/jquery/jquery.digilib.regions.js webapp/src/main/webapp/jquery/jquery.digilib.sequence.js webapp/src/main/webapp/jquery/jquery.digilib.transparent.js webapp/src/main/webapp/jquery/jquery.digilib.vector.js
diffstat 13 files changed, 784 insertions(+), 581 deletions(-) [+]
line wrap: on
line diff
--- a/webapp/src/main/webapp/jquery/jquery.digilib.annotator.js	Wed Mar 08 13:12:01 2017 +0100
+++ b/webapp/src/main/webapp/jquery/jquery.digilib.annotator.js	Sun Mar 12 15:53:01 2017 +0100
@@ -304,16 +304,16 @@
 			if (shape.geometry.type === 'Point') {
 				// point needs "x", "y" coordinate members
 				var c = shape.geometry.coordinates;
-				annoshape.geometry = geom.position(c[0]);
+				annoshape.geometry = new geom.Position(c[0]);
 			}
 			if (shape.geometry.type === 'Rectangle') {
 				// rectangle needs "x", "y", "width", "height" coordinate members
 				var c = shape.geometry.coordinates;
-				annoshape.geometry = geom.rectangle(geom.position(c[0]), geom.position(c[1]));
+				annoshape.geometry = new geom.Rectangle(new geom.Position(c[0]), new geom.Position(c[1]));
 			}
 			annoshape.geometry.units = 'fraction';
 			// screen position for annotation editor
-			var pos = geom.position(shape.geometry.coordinates[0]);
+			var pos = new geom.Position(shape.geometry.coordinates[0]);
 			var mpos = data.imgTrafo.transform(pos);
 			console.debug("creating annotation shape:", annoshape);
 			// show annotatorjs edit box
@@ -432,7 +432,7 @@
         if (annotation.areas != null && annotation.shapes == null) {
             console.warn("Annotation uses legacy 'areas' format! Converting...");
             // convert legacy annotation areas into shapes
-            area = geom.rectangle(annotation.areas[0]);
+            area = new geom.Rectangle(annotation.areas[0]);
             annoShape = {
                 'geometry' : area,
                 'type': area.isRectangle() ? 'rectangle' : 'point'
@@ -451,10 +451,10 @@
             type = annoShape.type;
             var coordinates;
             if (type === "point") {
-                area = geom.position(annoShape.geometry);
+                area = new geom.Position(annoShape.geometry);
                 coordinates = [[area.x, area.y]];
             } else if (type === "rectangle") {
-                area = geom.rectangle(annoShape.geometry);
+                area = new geom.Rectangle(annoShape.geometry);
                 var pt1 = area.getPt1();
                 var pt2 = area.getPt2();
                 coordinates = [[pt1.x, pt1.y], [pt2.x, pt2.y]];
@@ -615,7 +615,7 @@
     var zoomToAnnotation = function (data, $div) {
         var settings = data.settings;
         var rect = $div.data('rect');
-        var za = geom.rectangle(rect);
+        var za = new geom.Rectangle(rect);
         var w = settings.annotationAutoWidth;
         if (za.width == null || za.width == 0) za.width = w; 
         if (za.height == null || za.height == 0) za.height = w; 
--- a/webapp/src/main/webapp/jquery/jquery.digilib.arrows.js	Wed Mar 08 13:12:01 2017 +0100
+++ b/webapp/src/main/webapp/jquery/jquery.digilib.arrows.js	Sun Mar 12 15:53:01 2017 +0100
@@ -98,7 +98,7 @@
             }
             var deltaX = tdx * factor * za.width;
             var deltaY = tdy * factor * za.height;
-            var delta = geom.position(deltaX, deltaY);
+            var delta = new geom.Position(deltaX, deltaY);
             za.addPosition(delta);
             za = FULL_AREA.fit(za);
             digilib.fn.setZoomArea(data, za);
@@ -113,7 +113,7 @@
         console.debug('installing arrows plugin. digilib:', digilib);
         // import geometry classes
         geom = digilib.fn.geometry;
-        FULL_AREA = geom.rectangle(0, 0, 1, 1);
+        FULL_AREA = new geom.Rectangle(0, 0, 1, 1);
         // add defaults, actions
         $.extend(true, digilib.defaults, defaults); // make deep copy
         $.extend(digilib.buttons, buttons);
--- a/webapp/src/main/webapp/jquery/jquery.digilib.birdseye.js	Wed Mar 08 13:12:01 2017 +0100
+++ b/webapp/src/main/webapp/jquery/jquery.digilib.birdseye.js	Sun Mar 12 15:53:01 2017 +0100
@@ -77,7 +77,7 @@
         console.debug('installing birdseye plugin. digilib:', digilib);
         // import geometry classes
         geom = digilib.fn.geometry;
-        FULL_AREA = geom.rectangle(0,0,1,1);
+        FULL_AREA = new geom.Rectangle(0,0,1,1);
         // add defaults
         $.extend(digilib.defaults, defaults);
         // add actions
@@ -200,7 +200,7 @@
     var birdImgLoadedHandler = function (data) {
         return function () {
             var $birdImg = $(this);
-            var birdRect = geom.rectangle($birdImg);
+            var birdRect = new geom.Rectangle($birdImg);
             console.debug("birdImg loaded!", $birdImg, "rect=", birdRect, "data=", data);
             // create Transform from current area and picsize
             data.birdTrafo = digilib.fn.getImgTrafo(data.$birdImg, FULL_AREA);
@@ -269,11 +269,11 @@
 
         // mousedown handler: start dragging bird zoom to a new position
         var birdZoomStartDrag = function(evt) {
-            startPos = geom.position(evt);
+            startPos = new geom.Position(evt);
             // position may have changed
             data.birdTrafo = digilib.fn.getImgTrafo($birdImg, FULL_AREA);
-            birdImgRect = geom.rectangle($birdImg);
-            birdZoomRect = geom.rectangle($birdZoom);
+            birdImgRect = new geom.Rectangle($birdImg);
+            birdZoomRect = new geom.Rectangle($birdZoom);
             // grow rectangle by border width
             newRect = null;
             data.$elem.find('.'+cssPrefix+'overlay').hide(); // hide all overlays (marks/regions)
@@ -286,7 +286,7 @@
 
         // mousemove handler: drag
         var birdZoomMove = function(evt) {
-            var pos = geom.position(evt);
+            var pos = new geom.Position(evt);
             var delta = startPos.delta(pos);
             // move birdZoom div, keeping size
             newRect = birdZoomRect.copy();
--- a/webapp/src/main/webapp/jquery/jquery.digilib.dialogs.js	Wed Mar 08 13:12:01 2017 +0100
+++ b/webapp/src/main/webapp/jquery/jquery.digilib.dialogs.js	Sun Mar 12 15:53:01 2017 +0100
@@ -165,8 +165,8 @@
             fn.centerOnScreen($scaleDiv);
         } else {
             var $button = fn.findButtonByName(data, 'scale');
-            var buttonRect = geom.rectangle($button);
-            var divRect = geom.rectangle($scaleDiv);
+            var buttonRect = new geom.Rectangle($button);
+            var divRect = new geom.Rectangle($scaleDiv);
             $scaleDiv.offset({
                 left : Math.abs(buttonRect.x - divRect.width - 4),
                 top : buttonRect.y + 4
--- a/webapp/src/main/webapp/jquery/jquery.digilib.geometry.js	Wed Mar 08 13:12:01 2017 +0100
+++ b/webapp/src/main/webapp/jquery/jquery.digilib.geometry.js	Sun Mar 12 15:53:01 2017 +0100
@@ -2,7 +2,7 @@
  * #%L
  * required digilib geometry plugin
  * %%
- * Copyright (C) 2011 - 2013 MPIWG Berlin, Bibliotheca Hertziana
+ * Copyright (C) 2011 - 2017 MPIWG Berlin, Bibliotheca Hertziana
  * %%
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Lesser General Public License as 
@@ -25,499 +25,655 @@
 
 (function($) {
     var RAD2DEG = 180.0 / Math.PI;
-    /*
+    /**
      * Size class
      */
-    var size = function(w, h) {
-        var that;
+    function Size(w, h) {
         if (typeof w === "object") {
             // assume an object having width and height
-            that = {
-                width : w.width,
-                height : w.height
-            };
+            this.width = w.width, this.height = w.height;
         } else {
-            that = {
-                width : parseFloat(w),
-                height : parseFloat(h)
-            };
+            this.width = parseFloat(w), this.height = parseFloat(h);
         }
-        // returns true if both sizes are equal
-        that.equals = function(other) {
-            return (this.width === other.width && this.height === other.height);
-        };
-        // returns the aspect ratio of this size
-        that.getAspect = function() {
-            return (this.width / this.height);
-        };
-        // returns a size of a given aspect ratio that fits into this one 
-        that.fitAspect = function(aspect) {
-            var s = size(this);
-            if (aspect > this.getAspect()) {
-                // size is more horizontally stretched than this
-                s.height = s.width / aspect;
-            } else {
-                s.width = s.height * aspect;
-            }
-            return s;
-        };
-        // adjusts size of jQuery element "$elem" to this size
-        that.adjustDiv = function($elem) {
-            $elem.width(this.width).height(this.height);
-        };
-        that.toString = function() {
-            return (this.width + "x" + this.height);
-        };
-        return that;
+    }
+
+    /**
+     * returns true if both sizes are equal
+     */
+    Size.prototype.equals = function(other) {
+        return (this.width === other.width && this.height === other.height);
+    };
+
+    /**
+     * returns the aspect ratio of this size
+     */
+    Size.prototype.getAspect = function() {
+        return (this.width / this.height);
     };
 
-    /*
+    /**
+     * returns a size of a given aspect ratio that fits into this one
+     */
+    Size.prototype.fitAspect = function(aspect) {
+        var s = new Size(this);
+        if (aspect > this.getAspect()) {
+            // size is more horizontally stretched than this
+            s.height = s.width / aspect;
+        } else {
+            s.width = s.height * aspect;
+        }
+        return s;
+    };
+
+    /**
+     * adjusts size of jQuery element "$elem" to this size
+     */
+    Size.prototype.adjustDiv = function($elem) {
+        $elem.width(this.width).height(this.height);
+    };
+
+    Size.prototype.toString = function() {
+        return (this.width + "x" + this.height);
+    };
+
+    /**
      * Position class
      */
-    var position = function(x, y) {
-        var that;
+    function Position(x, y) {
         if (typeof x === "object") {
             if (x instanceof jQuery) {
                 // jQuery object
                 var pos = x.offset();
-                that = {
-                    x : pos.left,
-                    y : pos.top
-                };
+                this.x = pos.left;
+                this.y = pos.top;
             } else if ($.isArray(x)) {
-                that = {
-                    x : x[0],
-                    y : x[1]
-                };
+                this.x = x[0];
+                this.y = x[1];
             } else {
                 if (x.x != null) {
                     // position object
-                    that = {
-                        x : parseFloat(x.x),
-                        y : parseFloat(x.y)
-                    };
+                    this.x = parseFloat(x.x);
+                    this.y = parseFloat(x.y);
                 }
                 if (x.pageX != null) {
                     // event object
-                    that = {
-                        x : x.pageX,
-                        y : x.pageY
-                    };
+                    this.x = x.pageX;
+                    this.y = x.pageY;
                 }
             }
         } else {
-            that = {
-                x : parseFloat(x),
-                y : parseFloat(y)
-            };
-        };
-        // return a copy of this position
-        that.copy = function() {
-            return position(this);
-        };
-        // compare function
-        that.equals = function(other) {
-            return (this.x === other.x && this.y === other.y);
-        };
-        // add vector or position to this
-        that.add = function(other) {
-            if ($.isArray(other)) {
-                this.x += other[0];
-                this.y += other[1];
-            } else {
-                this.x += other.x;
-                this.y += other.y;
-            }
-            return this;
-        };
-        // returns negative position
-        that.neg = function() {
-            return position({
-                x : -this.x,
-                y : -this.y
-            });
-        };
-        // returns new position that is the difference between this and other
-        that.delta = function(other) {
-            return position({
-                x : other.x - this.x,
-                y : other.y - this.y
-            });
-        };
-        // returns other position scaled by ratio with regard to this point
-        that.scale = function(other, ratio) {
-            var d = this.delta(other);
-            return position({
-                x : this.x + d.x * ratio,
-                y : this.y + d.y * ratio
-            });
-        };
-        // adjusts CSS position of $elem to this position
-        that.adjustDiv = function($elem) {
-            $elem.offset({
-                left : this.x,
-                top : this.y
-            });
-        };
-        // move this position to another
-        that.moveTo = function(other) {
-            this.x = other.x;
-            this.y = other.y;
-            return this;
-        };
-        // adjust this position so that is is inside rect
-        that.clipTo = function (rect) {
-            var p1 = rect.getPt1();
-            var p2 = rect.getPt2();
-            this.x = Math.max(this.x, p1.x);
-            this.y = Math.max(this.y, p1.y);
-            this.x = Math.min(this.x, p2.x);
-            this.y = Math.min(this.y, p2.y);
-            return this;
-        };
-        // returns distance of this position to pos (length if pos == null)
-        that.distance = function(pos) {
-            if (pos == null) {
-                pos = {
-                    x : 0,
-                    y : 0
-                };
-            }
-            var dx = pos.x - this.x;
-            var dy = pos.y - this.y;
-            return Math.sqrt(dx * dx + dy * dy);
-        };
-        // nearest of several points
-        that.nearest = function (points) {
-            var nearest = points[0];
-            var dist = this.distance(nearest);
-            $.each(points, function(index, item) {
-                var len = this.distance(item);
-                if (len < dist) {
-                    dist = len;
-                    nearest = item;
-                    }
-                });
-            return nearest;
-        };
-        // midpoint of this and other pos
-        that.mid = function (pos) {
-            return position({
-                x : (this.x + pos.x)/2,
-                y : (this.y + pos.y)/2
-            });
-        };
-        // radians of angle between line and the positive X axis
-        that.rad = function (pos) {
-            return Math.atan2(pos.y - this.y, pos.x - this.x);
-        };
+            this.x = parseFloat(x);
+            this.y = parseFloat(y);
+        }
+    }
+
+    /**
+     * return a copy of this position
+     */
+    Position.prototype.copy = function() {
+        return new Position(this);
+    };
+
+    /**
+     * compare function
+     */
+    Position.prototype.equals = function(other) {
+        return (this.x === other.x && this.y === other.y);
+    };
+
+    /**
+     * add vector or position to this
+     */
+    Position.prototype.add = function(other) {
+        if ($.isArray(other)) {
+            this.x += other[0];
+            this.y += other[1];
+        } else {
+            this.x += other.x;
+            this.y += other.y;
+        }
+        return this;
+    };
 
-        // degree of angle between line and the positive X axis
-        that.deg = function (pos) {
-            return this.rad(pos) * RAD2DEG;
-        };
+    /**
+     * returns negative position
+     */
+    Position.prototype.neg = function() {
+        return new Position({
+            x : -this.x,
+            y : -this.y
+        });
+    };
+
+    /**
+     * returns new position that is the difference between this and other
+     */
+    Position.prototype.delta = function(other) {
+        return new Position({
+            x : other.x - this.x,
+            y : other.y - this.y
+        });
+    };
 
-        // returns position in css-compatible format
-        that.getAsCss = function() {
-            return {
-                left : this.x,
-                top : this.y
-            };
-        };
-        // return as string
-        that.toString = function() {
-            return (this.x + "," + this.y);
-        };
-        // return as array
-        that.toArray = function() {
-            return [this.x, this.y];
-        };
-        return that;
+    /**
+     * returns other position scaled by ratio with regard to this point
+     */
+    Position.prototype.scale = function(other, ratio) {
+        var d = this.delta(other);
+        return new Position({
+            x : this.x + d.x * ratio,
+            y : this.y + d.y * ratio
+        });
+    };
+
+    /**
+     * adjusts CSS position of $elem to this position
+     */
+    Position.prototype.adjustDiv = function($elem) {
+        $elem.offset({
+            left : this.x,
+            top : this.y
+        });
+    };
+
+    /**
+     * move this position to another
+     */
+    Position.prototype.moveTo = function(other) {
+        this.x = other.x;
+        this.y = other.y;
+        return this;
     };
 
-    /*
+    /**
+     * adjust this position so that is is inside rect
+     */
+    Position.prototype.clipTo = function(rect) {
+        var p1 = rect.getPt1();
+        var p2 = rect.getPt2();
+        this.x = Math.max(this.x, p1.x);
+        this.y = Math.max(this.y, p1.y);
+        this.x = Math.min(this.x, p2.x);
+        this.y = Math.min(this.y, p2.y);
+        return this;
+    };
+
+    /**
+     * returns distance of this position to pos (length if pos == null)
+     */
+    Position.prototype.distance = function(pos) {
+        if (pos == null) {
+            pos = {
+                x : 0,
+                y : 0
+            };
+        }
+        var dx = pos.x - this.x;
+        var dy = pos.y - this.y;
+        return Math.sqrt(dx * dx + dy * dy);
+    };
+
+    /**
+     * nearest of several points
+     */
+    Position.prototype.nearest = function(points) {
+        var nearest = points[0];
+        var dist = this.distance(nearest);
+        $.each(points, function(index, item) {
+            var len = this.distance(item);
+            if (len < dist) {
+                dist = len;
+                nearest = item;
+            }
+        });
+        return nearest;
+    };
+
+    /**
+     * midpoint of this and other pos
+     */
+    Position.prototype.mid = function(pos) {
+        return new Position({
+            x : (this.x + pos.x) / 2,
+            y : (this.y + pos.y) / 2
+        });
+    };
+
+    /**
+     * radians of angle between line and the positive X axis
+     */
+    Position.prototype.rad = function(pos) {
+        return Math.atan2(pos.y - this.y, pos.x - this.x);
+    };
+
+    /**
+     * degree of angle between line and the positive X axis
+     */
+    Position.prototype.deg = function(pos) {
+        return this.rad(pos) * RAD2DEG;
+    };
+
+    /**
+     * returns position in css-compatible format
+     */
+    Position.prototype.getAsCss = function() {
+        return {
+            left : this.x,
+            top : this.y
+        };
+    };
+
+    /**
+     * return as string
+     */
+    Position.prototype.toString = function() {
+        return (this.x + "," + this.y);
+    };
+
+    /**
+     * return as array
+     */
+    Position.prototype.toArray = function() {
+        return [ this.x, this.y ];
+    };
+
+    /**
      * Line class (for on-screen geometry)
      */
-    var line = function(p, q) {
-        var that = { // definition point
-            x : p.x,
-            y : p.y
-            };
+    function Line(p, q) {
+        this.x = p.x;
+        this.y = p.y;
         if (q.x != null) { // second point
-            that.dx = q.x - that.x;
-            that.dy = q.y - that.y;
+            this.dx = q.x - that.x;
+            this.dy = q.y - that.y;
         } else if ($.isArray(q)) { // vector
-            that.dx = q[0];
-            that.dy = q[1];
+            this.dx = q[0];
+            this.dy = q[1];
         } else if (q === 0) { // slope
-            that.dx = 0;
-            that.dy = 1;
+            this.dx = 0;
+            this.dy = 1;
         } else if (q === Infinity) {
-            that.dx = 1;
-            that.dy = 0;
+            this.dx = 1;
+            this.dy = 0;
         } else if (q === -Infinity) {
-            that.dx = -1;
-            that.dy = 0;
+            this.dx = -1;
+            this.dy = 0;
         } else if (typeof q === 'number' && isFinite(q)) {
-            that.dx = 1;
-            that.dy = 1/q;
+            this.dx = 1;
+            this.dy = 1 / q;
         } else {
-            that.dx = 1;
-            that.dy = 1;
-            }
-        // get/set origin of line
-        that.origin = function(p) {
-            if (p == null) {
-                return position(this.x, this.y);
-                }
-            this.x = p.x;
-            this.y = p.y;
-            return this;
-            };
-        // get/set vector
-        that.vector = function(vector) {
-            if (vector == null) {
-                return [this.dx, this.dy];
-                }
-            this.dx = vector[0];
-            this.dy = vector[1];
-            return this;
-            };
-        // return a vector with the contrary direction
-        that.invertedVector = function() {
-            return [-this.dx, -this.dy];
-            };
-        // return a vector that is perpendicular to this line
-        that.perpendicularVector = function(clockwise) {
-            return clockwise ? [-this.dy, this.dx] : [this.dy, -this.dx];
-            };
-        // return vector distance
-        that.dist = function() {
-            return Math.sqrt(this.dx * this.dx + this.dy * this.dy);
-            };
-        // multiply vector with a ratio
-        that.scale = function(ratio) {
-            this.dx *= ratio;
-            this.dy *= ratio
-            return this;
-            };
-        // get/set vector length
-        that.length = function(length) {
-            var dist = this.dist();
-            if (length == null) {
-                return dist;
-                }
-            return this.scale(length/dist);
-            };
-        // return the slope
-        that.slope = function() {
-            return this.dx/this.dy;
-            };
-        // return a copy of this line
-        that.copy = function() {
-            return line(position(this.x, this.y), this.vector());
-            };
-        // invert direction
-        that.invert = function() {
-            this.vector(this.invertedVector);
-            return this;
-            };
-        // return a parallel line through a point (with the same vector)
-        that.parallel = function(p) {
-            return line(position(p.x, p.y), this.vector());
-            };
-        // return a perpendicular line from the origin (optionally from another point) with direction
-        that.perpendicular = function(p, clockwise) {
-            var point = (p == null || p.x == null)
-                ? position(this.x, this.y) : p;
-            return line(point, this.perpendicularVector(clockwise));
-            };
-        // return the intersection with a perpendicular line through a point
-        that.perpendicularPoint = function(p) {
-            return this.intersection(this.perpendicular(p));
-            };
-        // return perpendicular line from point
-        that.perpendicularLine = function(p) {
-            return line(p, this.perpendicularPoint(p));
-            };
-        // return point in mirrored position (with regard to this line)
-        that.mirror = function(p) {
-            var line = this.perpendicularLine(p);
-            return line.addEnd(line.vector());
-            };
-        // return a position by adding a vector/position/distance to origin
-        that.add = function(item) {
-            if (item == null) {
-                return this.origin();
-            } else if ($.isArray(item)) { // add a vector
-                return position(this.x + item[0], this.y + item[1])
-            } else if (item.x != null) { // add a position
-                return position(this.x + item.x, this.y + item.y);
-            } else if (typeof item === 'number' && isFinite(item)) { // add a distance
-                ratio = item/this.dist();
-                return position(this.x + this.dx*ratio, this.y + this.dy*ratio);
-            } else {
-                return this.origin();
-                }
-            };
-        // return a position by adding a vector/position/distance to end point
-        that.addEnd = function(item) {
-            return this.add(item).add(this.vector());
-            };
-        // end point on the line (pointed to by vector)
-        that.point = function(factor) {
-            if (factor == null) { factor = 1; }
-            var vector = [factor*this.dx, factor*this.dy];
-            return this.add(vector);
-            };
-        // midpoint on the line (half of vector distance, multiplied by factor)
-        that.mid = function(factor) {
-            return this.origin().mid(this.point(factor));
-            };
-        // radians of angle between line and the positive X axis
-        that.rad = function() {
-            return this.origin().rad(this.point());
-            };
-        // degree of angle between line and the positive X axis
-        that.deg = function() {
-            return this.origin().deg(this.point());
-            };
-        // factor of point (assuming it is on the line)
-        that.factor = function(p) {
-            return (dx === 0)
-                ? (p.y - this.y)/this.dy
-                : (p.x - this.x)/this.dx;
-            };
-        // intersection point with other line
-        that.intersection = function(line) {
-            var denominator = this.dy*line.dx - this.dx*line.dy
-            if (denominator === 0) { // parallel
-                return null; }
-            var num = this.dx*(line.y - this.y) + this.dy*(this.x - line.x);
-            return line.point(num/denominator);
-            };
-        return that;
-        };
+            this.dx = 1;
+            this.dy = 1;
+        }
+    }
+    ;
+
+    /**
+     * get/set origin of line
+     */
+    Line.prototype.origin = function(p) {
+        if (p == null) {
+            return new Position(this.x, this.y);
+        }
+        this.x = p.x;
+        this.y = p.y;
+        return this;
+    };
+
+    /**
+     * get/set vector
+     */
+    Line.prototype.vector = function(vector) {
+        if (vector == null) {
+            return [ this.dx, this.dy ];
+        }
+        this.dx = vector[0];
+        this.dy = vector[1];
+        return this;
+    };
+
+    /**
+     * return a vector with the contrary direction
+     */
+    Line.prototype.invertedVector = function() {
+        return [ -this.dx, -this.dy ];
+    };
+
+    /**
+     * return a vector that is perpendicular to this line
+     */
+    Line.prototype.perpendicularVector = function(clockwise) {
+        return clockwise ? [ -this.dy, this.dx ] : [ this.dy, -this.dx ];
+    };
+
+    /**
+     * return vector distance
+     */
+    Line.prototype.dist = function() {
+        return Math.sqrt(this.dx * this.dx + this.dy * this.dy);
+    };
+
+    /**
+     * multiply vector with a ratio
+     */
+    Line.prototype.scale = function(ratio) {
+        this.dx *= ratio;
+        this.dy *= ratio
+        return this;
+    };
+
+    /**
+     * get/set vector length
+     */
+    Line.prototype.length = function(length) {
+        var dist = this.dist();
+        if (length == null) {
+            return dist;
+        }
+        return this.scale(length / dist);
+    };
+
+    /**
+     * return the slope
+     */
+    Line.prototype.slope = function() {
+        return this.dx / this.dy;
+    };
+
+    /**
+     * return a copy of this line
+     */
+    Line.prototype.copy = function() {
+        return new Line(new Position(this.x, this.y), this.vector());
+    };
+
+    /**
+     * invert direction
+     */
+    Line.prototype.invert = function() {
+        this.vector(this.invertedVector);
+        return this;
+    };
+
+    /**
+     * return a parallel line through a point (with the same vector)
+     */
+    Line.prototype.parallel = function(p) {
+        return new Line(new Position(p.x, p.y), this.vector());
+    };
 
-    /*
+    /**
+     * return a perpendicular line from the origin (optionally from another
+     * point) with direction
+     */
+    Line.prototype.perpendicular = function(p, clockwise) {
+        var point = (p == null || p.x == null) ? new Position(this.x, this.y)
+                : p;
+        return new Line(point, this.perpendicularVector(clockwise));
+    };
+
+    /**
+     * return the intersection with a perpendicular line through a point
+     */
+    Line.prototype.perpendicularPoint = function(p) {
+        return this.intersection(this.perpendicular(p));
+    };
+
+    /**
+     * return perpendicular line from point
+     */
+    Line.prototype.perpendicularLine = function(p) {
+        return new Line(p, this.perpendicularPoint(p));
+    };
+
+    /**
+     * return point in mirrored position (with regard to this line)
+     */
+    Line.prototype.mirror = function(p) {
+        var line = this.perpendicularLine(p);
+        return line.addEnd(line.vector());
+    };
+
+    /**
+     * return a position by adding a vector/position/distance to origin
+     */
+    Line.prototype.add = function(item) {
+        if (item == null) {
+            return this.origin();
+        } else if ($.isArray(item)) { // add a vector
+            return new Position(this.x + item[0], this.y + item[1])
+        } else if (item.x != null) { // add a position
+            return new Position(this.x + item.x, this.y + item.y);
+        } else if (typeof item === 'number' && isFinite(item)) { // add a
+                                                                    // distance
+            ratio = item / this.dist();
+            return new Position(this.x + this.dx * ratio, this.y + this.dy
+                    * ratio);
+        } else {
+            return this.origin();
+        }
+    };
+
+    /**
+     * return a position by adding a vector/position/distance to end point
+     */
+    Line.prototype.addEnd = function(item) {
+        return this.add(item).add(this.vector());
+    };
+
+    /**
+     * end point on the line (pointed to by vector)
+     */
+    Line.prototype.point = function(factor) {
+        if (factor == null) {
+            factor = 1;
+        }
+        var vector = [ factor * this.dx, factor * this.dy ];
+        return this.add(vector);
+    };
+
+    /**
+     * midpoint on the line (half of vector distance, multiplied by factor)
+     */
+    Line.prototype.mid = function(factor) {
+        return this.origin().mid(this.point(factor));
+    };
+
+    /**
+     * radians of angle between line and the positive X axis
+     */
+    Line.prototype.rad = function() {
+        return this.origin().rad(this.point());
+    };
+
+    /**
+     * degree of angle between line and the positive X axis
+     */
+    Line.prototype.deg = function() {
+        return this.origin().deg(this.point());
+    };
+
+    /**
+     * factor of point (assuming it is on the line)
+     */
+    Line.prototype.factor = function(p) {
+        return (dx === 0) ? (p.y - this.y) / this.dy : (p.x - this.x) / this.dx;
+    };
+
+    /**
+     * intersection point with other line
+     */
+    Line.prototype.intersection = function(line) {
+        var denominator = this.dy * line.dx - this.dx * line.dy
+        if (denominator === 0) { // parallel
+            return null;
+        }
+        var num = this.dx * (line.y - this.y) + this.dy * (this.x - line.x);
+        return line.point(num / denominator);
+    };
+
+            
+    /**
      * Rectangle class
      */
-    var rectangle = function(x, y, w, h) {
-        var that = {};
+    function Rectangle(x, y, w, h) {
         if (typeof x === "object") {
             if (x instanceof jQuery) {
                 // jQuery object
                 var pos = x.offset();
-                that = {
-                    x : pos.left,
-                    y : pos.top,
-                    width : x.width(),
-                    height : x.height()
-                };
+                this.x = pos.left;
+                this.y = pos.top;
+                this.width = x.width();
+                this.height = x.height();
             } else if (y == null) {
                 // assume x is rectangle
-                that = {
-                    x : parseFloat(x.x) || 0,
-                    y : parseFloat(x.y) || 0,
-                    width : parseFloat(x.width) || 0,
-                    height : parseFloat(x.height) || 0
-                };
+                this.x = parseFloat(x.x) || 0;
+                this.y = parseFloat(x.y) || 0;
+                this.width = parseFloat(x.width) || 0;
+                this.height = parseFloat(x.height) || 0;
             } else {
                 // assume x and y are Position
-                that = {
-                    x : Math.min(x.x, y.x),
-                    y : Math.min(x.y, y.y),
-                    width : Math.abs(y.x - x.x),
-                    height : Math.abs(y.y - x.y)
-                };
+                this.x = Math.min(x.x, y.x);
+                this.y = Math.min(x.y, y.y);
+                this.width = Math.abs(y.x - x.x);
+                this.height = Math.abs(y.y - x.y);
             }
         } else {
-            that = {
-                x : parseFloat(x),
-                y : parseFloat(y),
-                width : parseFloat(w),
-                height : parseFloat(h)
-            };
+            this.x = parseFloat(x);
+            this.y = parseFloat(y);
+            this.width = parseFloat(w);
+            this.height = parseFloat(h);
         }
-        // returns a copy of this Rectangle
-        that.copy = function() {
-            return rectangle(this);
+    };
+
+    /**
+     * returns a copy of this Rectangle
+     */
+        Rectangle.prototype.copy = function() {
+            return new Rectangle(this);
         };
-        // returns the position of this Rectangle
-        that.getPosition = function() {
-            return position(this);
+        
+        /**
+         * returns the position of this Rectangle
+         */
+        Rectangle.prototype.getPosition = function() {
+            return new Position(this);
         };
-        // returns the size of this Rectangle
-        that.getSize = function() {
-            return size(this);
+        
+        /**
+         * returns the size of this Rectangle
+         */
+        Rectangle.prototype.getSize = function() {
+            return new Size(this);
         };
-        // returns the upper left corner position
-        that.getPt1 = that.getPosition;
-        // returns the lower right corner position of this Rectangle
-        that.getPt2 = function() {
-            return position({
+        
+        /**
+         * returns the upper left corner position
+         */
+        Rectangle.prototype.getPt1 = Rectangle.prototype.getPosition;
+        
+        /**
+         * returns the lower right corner position of this Rectangle
+         */
+        Rectangle.prototype.getPt2 = function() {
+            return new Position({
                 x : this.x + this.width,
                 y : this.y + this.height
             });
         };
-        // sets the upper left corner position to pos
-        that.setPosition = function(pos) {
+        
+        /**
+         * sets the upper left corner position to pos
+         */
+        Rectangle.prototype.setPosition = function(pos) {
             this.x = pos.x;
             this.y = pos.y;
             return this;
         };
-        // adds pos to the position
-        that.setPt1 = that.setPosition; // TODO: not really the same
-        that.addPosition = function(pos) {
+        
+        Rectangle.prototype.setPt1 = Rectangle.prototype.setPosition; 
+        // TODO: not really the same
+        
+        /**
+         * adds pos to the position
+         */
+        Rectangle.prototype.addPosition = function(pos) {
             this.x += pos.x;
             this.y += pos.y;
             return this;
         };
-        // adds pos to the dimensions
-        that.enlarge = function(pos) {
+        
+        /**
+         * adds pos to the dimensions
+         */
+        Rectangle.prototype.enlarge = function(pos) {
             this.width += pos.x;
             this.height += pos.y;
             return this;
         };
-        // sets the lower right corner to position pos
-        that.setPt2 = function(pos) {
+        
+        /**
+         * sets the lower right corner to position pos
+         */
+        Rectangle.prototype.setPt2 = function(pos) {
             this.width = pos.x - this.x;
             this.height = pos.y - this.y;
             return this;
         };
-        // returns the center position of this Rectangle
-        that.getCenter = function() {
-            return position({
+        
+        /**
+         * returns the center position of this Rectangle
+         */
+        Rectangle.prototype.getCenter = function() {
+            return new Position({
                 x : this.x + this.width / 2,
                 y : this.y + this.height / 2
             });
         };
-        // moves this rectangle's center to position pos
-        that.setCenter = function(pos) {
+        
+        /**
+         * moves this rectangle's center to position pos
+         */
+        Rectangle.prototype.setCenter = function(pos) {
             this.x = pos.x - this.width / 2;
             this.y = pos.y - this.height / 2;
             return this;
         };
-        // returns true if both rectangles have equal position and size
-        that.equals = function(other) {
+        
+        /**
+         * returns true if both rectangles have equal position and size
+         */
+        Rectangle.prototype.equals = function(other) {
             var eq = (this.x === other.x && this.y === other.y && this.width === other.width);
             return eq;
         };
-        // returns a rectangle with the difference width, height and position
-        that.delta = function(other) {
-            return rectangle(other.x - this.x, other.y - this.y, 
+        
+        /**
+         * returns a rectangle with the difference width, height and position
+         */
+        Rectangle.prototype.delta = function(other) {
+            return new Rectangle(other.x - this.x, other.y - this.y, 
             		other.width - this.width, other.height - this.height);
         };
-        // returns the area of this Rectangle
-        that.getArea = function() {
+        
+        /**
+         * returns the area of this Rectangle
+         */
+        Rectangle.prototype.getArea = function() {
             return (this.width * this.height);
         };
-        // returns the aspect ratio of this Rectangle
-        that.getAspect = function() {
+        
+        /**
+         * returns the aspect ratio of this Rectangle
+         */
+        Rectangle.prototype.getAspect = function() {
             return (this.width / this.height);
         };
-        // eliminates negative width and height
-        that.normalize = function() {
+        
+        /**
+         * eliminates negative width and height
+         */
+        Rectangle.prototype.normalize = function() {
             var p = this.getPt2();
             this.x = Math.min(this.x, p.x);
             this.y = Math.min(this.y, p.y);
@@ -525,28 +681,43 @@
             this.height = Math.abs(this.height);
             return this;
         };
-        // returns if Position "pos" lies inside of this rectangle
-        that.containsPosition = function(pos) {
+        
+        /**
+         * returns if Position "pos" lies inside of this rectangle
+         */
+        Rectangle.prototype.containsPosition = function(pos) {
             var ct = ((pos.x >= this.x) && (pos.y >= this.y)
                     && (pos.x <= this.x + this.width) && (pos.y <= this.y
                     + this.height));
             return ct;
         };
-        // returns true if rectangle "rect" is contained in this rectangle
-        that.containsRect = function(rect) {
+        
+        /**
+         * returns true if rectangle "rect" is contained in this rectangle
+         */
+        Rectangle.prototype.containsRect = function(rect) {
             return (this.containsPosition(rect.getPt1()) && this
                     .containsPosition(rect.getPt2()));
         };
-        // returns true if rectangle "rect" and this rectangle overlap
-        that.overlapsRect = function(rect) {
+        
+        /**
+         * returns true if rectangle "rect" and this rectangle overlap
+         */
+        Rectangle.prototype.overlapsRect = function(rect) {
             return this.intersect(rect) != null;
         };
-        // returns the ratio of height to width
-        that.getProportion = function() {
+        
+        /**
+         * returns the ratio of height to width
+         */
+        Rectangle.prototype.getProportion = function() {
             return this.height/this.width;
         };
-        // shrink/grow rectangle until it has the given proportion
-        that.setProportion = function(ratio, canGrow) {
+        
+        /**
+         * shrink/grow rectangle until it has the given proportion
+         */
+        Rectangle.prototype.setProportion = function(ratio, canGrow) {
             var prop = this.getProportion();
             if (ratio < prop == canGrow) {
                 this.width = this.height / ratio;
@@ -555,9 +726,12 @@
             }
             return this;
         };
-        // changes this rectangle's x/y values so it stays inside of rectangle
-        // "rect", keeping the proportions
-        that.stayInside = function(rect) {
+        
+        /**
+         * changes this rectangle's x/y values so it stays inside of rectangle
+         * "rect", keeping the proportions
+         */
+        Rectangle.prototype.stayInside = function(rect) {
             this.x = Math.max(this.x, rect.x);
             this.y = Math.max(this.y, rect.y);
             if (this.x + this.width > rect.x + rect.width) {
@@ -568,26 +742,34 @@
             }
             return this;
         };
-        // clips this rectangle so it stays inside of rectangle "rect"
-        that.clipTo = function(rect) {
+        
+        /**
+         * clips this rectangle so it stays inside of rectangle "rect"
+         */
+        Rectangle.prototype.clipTo = function(rect) {
             var p1 = rect.getPt1();
             var p2 = rect.getPt2();
             var this2 = this.getPt2();
-            this.setPosition(position(Math.max(this.x, p1.x), Math.max(this.y, p1.y)));
-            this.setPt2(position(Math.min(this2.x, p2.x), Math.min(this2.y, p2.y)));
+            this.setPosition(new Position(Math.max(this.x, p1.x), Math.max(this.y, p1.y)));
+            this.setPt2(new Position(Math.min(this2.x, p2.x), Math.min(this2.y, p2.y)));
             return this;
         };
-        // returns the intersection of rectangle "rect" and this one
-        that.intersect = function(rect) {
+
+        /**
+         * returns the intersection of rectangle "rect" and this one
+         */
+        Rectangle.prototype.intersect = function(rect) {
             var r = rect.copy();
             var result = r.clipTo(this);
             if (result.width < 0 || result.height < 0) result = null;
             return result;
         };
 
-        // returns a copy of rectangle "rect" that fits into this one
-        // (moving it first)
-        that.fit = function(rect) {
+        /**
+         * returns a copy of rectangle "rect" that fits into this one (moving it
+         * first)
+         */
+        Rectangle.prototype.fit = function(rect) {
             var r = rect.copy();
             r.x = Math.max(r.x, this.x);
             r.y = Math.max(r.y, this.x);
@@ -600,16 +782,21 @@
             return r.intersect(this);
         };
 
-        // adjusts position and size of jQuery element "$elem" to this rectangle
-        that.adjustDiv = function($elem) {
+        /**
+         * adjusts position and size of jQuery element "$elem" to this rectangle
+         */
+        Rectangle.prototype.adjustDiv = function($elem) {
             $elem.offset({
                 left : this.x,
                 top : this.y
             });
             $elem.width(this.width).height(this.height);
         };
-        // returns position and size of this rectangle in css-compatible format
-        that.getAsCss = function() {
+        
+        /**
+         * returns position and size of this rectangle in css-compatible format
+         */
+        Rectangle.prototype.getAsCss = function() {
             return {
                 left : this.x,
                 top : this.y,
@@ -617,49 +804,62 @@
                 height : this.height
             };
         };
-        // returns position and size of this rectangle formatted for SVG attributes
-        that.getAsSvg = function() {
+        
+        /**
+         * returns position and size of this rectangle formatted for SVG
+         * attributes
+         */
+        Rectangle.prototype.getAsSvg = function() {
             return [this.x, this.y, this.width, this.height].join(" ");
         };
-        // returns if this rectangle is a rectangle
-        that.isRectangle = function () {
+        
+        /**
+         * returns if this rectangle is a rectangle
+         */
+        Rectangle.prototype.isRectangle = function () {
         	return this.width > 0 && this.height > 0;
         };
-        // returns size and position of this rectangle formatted for ??? (w x h@x,y)
-        that.toString = function() {
+        
+        /**
+         * returns size and position of this rectangle formatted as string "(w x
+         * h@x,y)"
+         */
+        Rectangle.prototype.toString = function() {
             return this.width + "x" + this.height + "@" + this.x + "," + this.y;
         };
-        return that;
-    };
 
-    /*
+        
+    /**
      * Transform class
      * 
      * defines a class of affine transformations
      */
-    var transform = function(spec) {
-        var that = {
-            m00 : 1.0,
-            m01 : 0.0,
-            m02 : 0.0,
-            m10 : 0.0,
-            m11 : 1.0,
-            m12 : 0.0,
-            m20 : 0.0,
-            m21 : 0.0,
-            m22 : 1.0
-        };
+    function Transform(spec) {
+        // 3x3 transform matrix
+        this.m00 = 1.0;
+        this.m01 = 0.0;
+        this.m02 = 0.0;
+        this.m10 = 0.0;
+        this.m11 = 1.0;
+        this.m12 = 0.0;
+        this.m20 = 0.0;
+        this.m21 = 0.0;
+        this.m22 = 1.0;
+        
         if (spec) {
-            jQuery.extend(that, spec);
+            jQuery.extend(this, spec);
         }
-        ;
-        that.concat = function(trafA) {
-            // add Transform trafA to this Transform (i.e. this = trafC = trafA * this)
+    };
+    
+    /**
+     *  add Transform trafA to this Transform (i.e. this = trafC = trafA * this)
+     */
+        Transform.prototype.concat = function(trafA) {
             var trafC = {};
-            for ( var i = 0; i < 3; i++) {
-                for ( var j = 0; j < 3; j++) {
+            for (var i = 0; i < 3; i++) {
+                for (var j = 0; j < 3; j++) {
                     var c = 0.0;
-                    for ( var k = 0; k < 3; k++) {
+                    for (var k = 0; k < 3; k++) {
                         c += trafA["m" + i + k] * this["m" + k + j];
                     }
                     trafC["m" + i + j] = c;
@@ -668,22 +868,28 @@
             jQuery.extend(this, trafC);
             return this;
         };
-        that.transform = function(rect) {
-            // returns transformed Rectangle or Position with this Transform
-            // applied
+        
+        /**
+         * returns transformed Rectangle or Position with this Transform
+         * applied
+         */
+        Transform.prototype.transform = function(rect) {
+            // 
             var x = this.m00 * rect.x + this.m01 * rect.y + this.m02;
             var y = this.m10 * rect.x + this.m11 * rect.y + this.m12;
-            var pt = position(x, y);
+            var pt = new Position(x, y);
             if (rect.width != null) {
                 // transform the other corner point
                 var pt2 = this.transform(rect.getPt2());
-                return rectangle(pt, pt2);
+                return new Rectangle(pt, pt2);
             }
             return pt;
         };
-        that.invtransform = function(rect) {
-            // returns transformed Rectangle or Position with the inverse of
-            // this Transform applied
+        
+        /**
+         * returns transformed Rectangle or Position with the inverse of this Transform applied
+         */
+        Transform.prototype.invtransform = function(rect) {
             var det = this.m00 * this.m11 - this.m01 * this.m10;
             var x = (this.m11 * rect.x - this.m01 * rect.y - this.m11
                     * this.m02 + this.m01 * this.m12)
@@ -691,15 +897,16 @@
             var y = (-this.m10 * rect.x + this.m00 * rect.y + this.m10
                     * this.m02 - this.m00 * this.m12)
                     / det;
-            var pt = position(x, y);
+            var pt = new Position(x, y);
             if (rect.width != null) {
                 // transform the other corner point
                 var pt2 = this.invtransform(rect.getPt2());
-                return rectangle(pt, pt2);
+                return new Rectangle(pt, pt2);
             }
             return pt;
         };
-        that.toString = function(pretty) {
+        
+        Transform.prototype.toString = function(pretty) {
             var s = '[';
             if (pretty)
                 s += '\n';
@@ -719,18 +926,11 @@
                 s += '\n';
             return s;
         };
-        // add class methods to instance
-        that.getRotation = transform.getRotation;
-        that.getRotationAround = transform.getRotationAround;
-        that.getTranslation = transform.getTranslation;
-        that.getMirror = transform.getMirror;
-        that.getScale = transform.getScale;
 
-        return that;
-    };
-
-    transform.getRotation = function(angle) {
-        // returns a Transform that is a rotation by angle degrees around [0,0]
+        /**
+         * returns a Transform that is a rotation by angle degrees around [0,0]
+         */
+        Transform.prototype.getRotation = function(angle) {
         if (angle !== 0) {
             var t = parseFloat(angle) / RAD2DEG;
             var cost = Math.cos(t);
@@ -741,29 +941,31 @@
                 m10 : sint,
                 m11 : cost
             };
-            return transform(traf);
+            return new Transform(traf);
         }
-        return transform();
+        return new Transform();
     };
 
-    transform.getRotationAround = function(angle, pos) {
-        // returns a Transform that is a rotation by angle degrees around pos
-        var traf = transform.getTranslation(pos.neg());
-        traf.concat(transform.getRotation(angle));
-        traf.concat(transform.getTranslation(pos));
+    /**
+     * returns a Transform that is a rotation by angle degrees around pos
+     */
+    Transform.prototype.getRotationAround = function(angle, pos) {
+        var traf = this.getTranslation(pos.neg());
+        traf.concat(this.getRotation(angle));
+        traf.concat(this.getTranslation(pos));
         return traf;
     };
 
-    transform.getTranslation = function(pos) {
+    Transform.prototype.getTranslation = function(pos) {
         // returns a Transform that is a translation by [pos.x, pos,y]
         var traf = {
             m02 : pos.x,
             m12 : pos.y
         };
-        return transform(traf);
+        return new Transform(traf);
     };
 
-    transform.getMirror = function(type) {
+    Transform.prototype.getMirror = function(type) {
         // returns a Transform that is a mirror about the axis type
         if (type === 'x') {
             var traf = {
@@ -776,25 +978,26 @@
                 m11 : 1
             };
         }
-        return transform(traf);
+        return new Transform(traf);
     };
 
-    transform.getScale = function(size) {
+    Transform.prototype.getScale = function(size) {
         // returns a Transform that is a scale by [size.width, size.height]
         var traf = {
             m00 : size.width,
             m11 : size.height
         };
-        return transform(traf);
+        return new Transform(traf);
     };
 
+    
     // export constructor functions to digilib plugin
     var geometry = {
-            size : size,
-            position : position,
-            line : line,
-            rectangle : rectangle,
-            transform : transform
+            Size : Size,
+            Position : Position,
+            Line : Line,
+            Rectangle : Rectangle,
+            Transform : Transform
     };
 
     // install function called by digilib on plugin object
--- a/webapp/src/main/webapp/jquery/jquery.digilib.js	Wed Mar 08 13:12:01 2017 +0100
+++ b/webapp/src/main/webapp/jquery/jquery.digilib.js	Sun Mar 12 15:53:01 2017 +0100
@@ -139,7 +139,7 @@
                 // geometry plugin puts classes in the shared fn
                 geom = fn.geometry;
             }
-            FULL_AREA  = geom.rectangle(0, 0, 1, 1);
+            FULL_AREA  = new geom.Rectangle(0, 0, 1, 1);
             // settings for this digilib instance are merged from defaults and options
             // (no deep copy because lists would be joined)
             var settings = $.extend({}, defaults, options);
@@ -426,7 +426,7 @@
                     };
                 defineArea(data, onComplete);
             } else {
-                data.zoomArea = geom.rectangle(area);
+                data.zoomArea = new geom.Rectangle(area);
                 redisplay(data);
             }
         },
@@ -880,7 +880,7 @@
     var unpackParams = function (data) {
         var settings = data.settings;
         // zoom area
-        var zoomArea = geom.rectangle(settings.wx, settings.wy, settings.ww, settings.wh);
+        var zoomArea = new geom.Rectangle(settings.wx, settings.wy, settings.ww, settings.wh);
         data.zoomArea = zoomArea;
         // mo (Scaler flags)
         var flags = {};
@@ -1138,14 +1138,14 @@
         var imgW = winW - insets.x;
         var imgH = winH - insets.y;
         console.debug('getFullscreenImgSize - screen w/h:', winW, winH, 'window.width', $win.width(), 'img w/h:', imgW, imgH);
-        return geom.size(imgW, imgH);
+        return new geom.Size(imgW, imgH);
     };
 
     /** 
      * returns a rectangle.with the fullscreen dimensions 
      */
     var getFullscreenRect = function (data) {
-        return geom.rectangle(getFullscreenImgSize(data));
+        return new geom.Rectangle(getFullscreenImgSize(data));
     };
 
     /** 
@@ -1186,7 +1186,7 @@
             $img = $('<img/>');
         } else {
             // embedded mode -- try to keep img tag
-            data.maxImgSize = geom.rectangle($elem).getSize();
+            data.maxImgSize = new geom.Rectangle($elem).getSize();
             $elem.addClass(cssPrefix+'embedded');
             scalerUrl = getScalerUrl(data);
             $img = $elem.find('img');
@@ -1248,7 +1248,7 @@
      * Returns Transform between normalized coordinates and image pixel coordinates.
      */
     var getImgTrafo = function ($img, area, rot, hmir, vmir, mode, data) {
-        var picrect = geom.rectangle($img);
+        var picrect = new geom.Rectangle($img);
         // handle pixel-by-pixel and original-size modes 
         if (mode != null) {
             var imgInfo = data.imgInfo;
@@ -1272,15 +1272,15 @@
                 }
             }
         }
-        var trafo = geom.transform();
+        var trafo = new geom.Transform();
         // move zoom area offset to center
-        trafo.concat(trafo.getTranslation(geom.position(-area.x, -area.y)));
+        trafo.concat(trafo.getTranslation(new geom.Position(-area.x, -area.y)));
         // scale zoom area size to [1,1]
-        trafo.concat(trafo.getScale(geom.size(1/area.width, 1/area.height)));
+        trafo.concat(trafo.getScale(new geom.Size(1/area.width, 1/area.height)));
         // rotate and mirror (around transformed image center i.e. [0.5,0.5])
         if (rot || hmir || vmir) {
             // move [0.5,0.5] to center
-            trafo.concat(trafo.getTranslation(geom.position(-0.5, -0.5)));
+            trafo.concat(trafo.getTranslation(new geom.Position(-0.5, -0.5)));
             if (hmir) {
                 // mirror about center
                 trafo.concat(trafo.getMirror('y'));
@@ -1294,7 +1294,7 @@
                 trafo.concat(trafo.getRotation(parseFloat(rot)));
                 }
             // move back
-            trafo.concat(trafo.getTranslation(geom.position(0.5, 0.5)));
+            trafo.concat(trafo.getTranslation(new geom.Position(0.5, 0.5)));
             }
         // scale to screen position and size
         trafo.concat(trafo.getScale(picrect));
@@ -1318,7 +1318,7 @@
                     data.scaleMode, data);
             console.debug("updateImgTrafo: ", data.imgTrafo);
             // update imgRect
-            data.imgRect = geom.rectangle($img);
+            data.imgRect = new geom.Rectangle($img);
         }
     };
 
@@ -1330,7 +1330,7 @@
             var $img = $(this);
             console.debug("scaler img loaded=",$img);
             var $scaler = data.$scaler;
-            var imgRect = geom.rectangle($img);
+            var imgRect = new geom.Rectangle($img);
             data.imgRect = imgRect;
             // reset busy cursor
             $('body').css('cursor', 'auto');
@@ -1419,9 +1419,9 @@
         var CSS = data.settings.cssPrefix;
         var $elem = data.$elem;
         var $scaler = data.$scaler;
-        var picRect = geom.rectangle($scaler);
+        var picRect = new geom.Rectangle($scaler);
         var $body = $('body');
-        var bodyRect = geom.rectangle($body);
+        var bodyRect = new geom.Rectangle($body);
         var pt1, pt2;
         // overlay div prevents other elements from reacting to mouse events 
         var $overlayDiv = $('<div id="'+CSS+'areaoverlay" class="'+CSS+'areaoverlay"/>');
@@ -1436,7 +1436,7 @@
         $scaler.addClass(CSS+'definearea');
 
         var areaStart = function (evt) {
-            pt1 = geom.position(evt);
+            pt1 = new geom.Position(evt);
             // setup and show area div
             pt1.adjustDiv($areaDiv);
             $areaDiv.width(0).height(0);
@@ -1449,8 +1449,8 @@
 
         // mouse move handler
         var areaMove = function (evt) {
-            pt2 = geom.position(evt);
-            var rect = geom.rectangle(pt1, pt2);
+            pt2 = new geom.Position(evt);
+            var rect = new geom.Rectangle(pt1, pt2);
             rect.clipTo(picRect);
             // update area div
             rect.adjustDiv($areaDiv);
@@ -1459,9 +1459,9 @@
 
         // mouseup handler: end moving
         var areaEnd = function (evt) {
-            pt2 = geom.position(evt);
+            pt2 = new geom.Position(evt);
             // assume a click and continue if the area is too small
-            var clickRect = geom.rectangle(pt1, pt2);
+            var clickRect = new geom.Rectangle(pt1, pt2);
             if (clickRect.getArea() <= 5) {
                 onComplete(data, null);
                 return false;
@@ -1491,7 +1491,7 @@
     var setPreviewBg = function(data, newZoomArea) {
         var $scaler = data.$scaler;
         var imgTrafo = data.imgTrafo;
-        var scalerPos = geom.position($scaler);
+        var scalerPos = new geom.Position($scaler);
         var bgRect = null;
         // use current image as first background
         var scalerCss = {
@@ -1565,7 +1565,7 @@
             // don't start dragging if not zoomed
             if (isFullArea(data.zoomArea)) return false;
             $elem.find('.'+data.settings.cssPrefix+'overlay').hide(); // hide all overlays (marks/regions)
-            startPos = geom.position(evt);
+            startPos = new geom.Position(evt);
             delta = null;
             // hide image, show dimmed background
             fadeScalerImg(data, 'hide');
@@ -1578,10 +1578,10 @@
 
         // mousemove handler: drag zoomed image
         var dragMove = function (evt) {
-            var pos = geom.position(evt);
+            var pos = new geom.Position(evt);
             delta = startPos.delta(pos);
             // send message event with current zoom position
-            var za = geom.rectangle($img);
+            var za = new geom.Rectangle($img);
             za.addPosition(delta.neg());
             // transform back
             var area = data.imgTrafo.invtransform(za);
@@ -1603,7 +1603,7 @@
                 return false; 
             }
             // get old zoom area (screen coordinates)
-            var za = geom.rectangle($img);
+            var za = new geom.Rectangle($img);
             // move
             za.addPosition(delta.neg());
             // transform back
@@ -1800,8 +1800,8 @@
 	    		}
 	    	}
 	    	var ar = getImgAspectRatio(data);
-	    	var r1 = geom.position(p1.x * ar, p1.y);
-	    	var r2 = geom.position(p2.x * ar, p2.y);
+	    	var r1 = new geom.Position(p1.x * ar, p1.y);
+	    	var r2 = new geom.Position(p2.x * ar, p2.y);
 	    	dist['rectified'] = r1.distance(r2);
     	}
     	return dist;
@@ -1907,7 +1907,7 @@
     */
     var centerOnScreen = function (data, $div) {
         if ($div == null) return;
-        var r = geom.rectangle($div);
+        var r = new geom.Rectangle($div);
         var s = fn.getFullscreenRect(data);
         r.setCenter(s.getCenter());
         r.getPosition().adjustDiv($div);
--- a/webapp/src/main/webapp/jquery/jquery.digilib.marks.js	Wed Mar 08 13:12:01 2017 +0100
+++ b/webapp/src/main/webapp/jquery/jquery.digilib.marks.js	Sun Mar 12 15:53:01 2017 +0100
@@ -134,7 +134,7 @@
             for ( var i = 0; i < pa.length; i++) {
                 var pos = pa[i].split("/");
                 if (pos.length > 1) {
-                    marks.push(geom.position(pos[0], pos[1]));
+                    marks.push(new geom.Position(pos[0], pos[1]));
                 }
             }
         }
@@ -205,7 +205,7 @@
         $scaler.one('mousedown.dlSetMark', function(evt) {
             // event handler adding a new mark
             console.log("setmark at=", evt);
-            var mpos = geom.position(evt);
+            var mpos = new geom.Position(evt);
             var pos = data.imgTrafo.invtransform(mpos);
             data.marks.push(pos);
             digilib.fn.redisplay(data);
--- a/webapp/src/main/webapp/jquery/jquery.digilib.measure.js	Wed Mar 08 13:12:01 2017 +0100
+++ b/webapp/src/main/webapp/jquery/jquery.digilib.measure.js	Sun Mar 12 15:53:01 2017 +0100
@@ -921,7 +921,7 @@
         var coords = shape.geometry.coordinates;
         var last = getLastVertex(shape, vtx);
         // safely assume that screenpos and coords have equal length?
-        var dist = fn.getDistance(data, geom.position(coords[last]), geom.position(coords[vtx]));
+        var dist = fn.getDistance(data, new geom.Position(coords[last]), new geom.Position(coords[vtx]));
         return dist.rectified;
         };
 
@@ -943,7 +943,7 @@
     var rectifiedArea = function(data, shape) {
         var ar = fn.getImgAspectRatio(data);
         var rectifyPoint = function (c) {
-            return geom.position(ar * c[0], c[1]);
+            return new geom.Position(ar * c[0], c[1]);
             };
         var coords = $.map(shape.geometry.coordinates, rectifyPoint);
          // formula for ellipse area
@@ -1215,9 +1215,9 @@
     // initial position of measure bar (bottom left of browser window)
     var setScreenPosition = function(data, $bar) {
         if ($bar == null) return;
-        var barH = geom.rectangle($bar).height;
+        var barH = (new geom.Rectangle($bar)).height;
         var screenH = fn.getFullscreenRect(data).height;
-        geom.position(10, screenH - barH).adjustDiv($bar);
+        new geom.Position(10, screenH - barH).adjustDiv($bar);
     };
 
     // drag measureBar around
@@ -1369,7 +1369,7 @@
                         place.call($s); // place the linestring
                         if (p.length > 2) { // p[2] is the mouse pointer
                             var m1 = p[1].mid(p[2]);
-                            var line = geom.line(m1, p[1]);
+                            var line = new geom.Line(m1, p[1]);
                             var m2 = p[0].copy().add(line.vector());
                             var rad = line.length();
                             $c1.attr({cx: m1.x, cy: m1.y, r: rad});
@@ -1392,7 +1392,7 @@
                         var p = props.screenpos;
                         var vtx = props.vtx;
                         if (p.length > 2) { // p[2] is the mouse pointer
-                            var line1 = geom.line(p[0], p[1]); // base line
+                            var line1 = new geom.Line(p[0], p[1]); // base line
                             var line2 = line1.parallel(p[2]);
                             var p3 = line1.perpendicular().intersection(line2);
                             var p2 = p3.copy().add(line1.vector());
@@ -1432,10 +1432,10 @@
                         var p = props.screenpos;
                         place.call($s); // place the framing rectangle (polygon)
                         if (p.length > 3) { // p[3] is the mouse pointer
-                            var side0 = geom.line(p[0], p[1]) // the sides
-                            var side1 = geom.line(p[1], props.pos[0]);
-                            var side2 = geom.line(props.pos[0], props.pos[1]);
-                            var side3 = geom.line(props.pos[1], p[0]);
+                            var side0 = new geom.Line(p[0], p[1]) // the sides
+                            var side1 = new geom.Line(p[1], props.pos[0]);
+                            var side2 = new geom.Line(props.pos[0], props.pos[1]);
+                            var side3 = new geom.Line(props.pos[1], p[0]);
                             var mid0 = side0.mid(); // the midpoints of the sides
                             var mid1 = side1.mid();
                             var mid2 = side2.mid();
@@ -1447,7 +1447,7 @@
                             if (handle.distance(mid0) > axis2.length()) { // constrain handle
                                 handle.moveTo(mid2);
                             } else if (handle.distance(mid2) > maxDiam) {
-                                handle.moveTo(geom.line(mid2, handle).length(maxDiam).point());
+                                handle.moveTo(new geom.Line(mid2, handle).length(maxDiam).point());
                                 }
                             var m1 = handle.mid(mid2); // centers of the small circles
                             var m2 = axis1.mirror(m1);
@@ -1458,13 +1458,13 @@
                             var md2 = axis2.mirror(md1);
                             var md3 = axis1.mirror(md1);
                             var md4 = axis1.mirror(md2);
-                            var bi = geom.line(rd1, m1).perpendicular(md1); // construct the perpendicular bisector of the connection line
+                            var bi = new geom.Line(rd1, m1).perpendicular(md1); // construct the perpendicular bisector of the connection line
                             var m3 = axis1.intersection(bi); // find the centers of the big circles
                             var m4 = axis2.mirror(m3);
-                            var fp1 = geom.line(m3, m1).addEnd(rad1); // the four fitting points
-                            var fp2 = geom.line(m3, m2).addEnd(rad1);
-                            var fp3 = geom.line(m4, m1).addEnd(rad1);
-                            var fp4 = geom.line(m4, m2).addEnd(rad1);
+                            var fp1 = new geom.Line(m3, m1).addEnd(rad1); // the four fitting points
+                            var fp2 = new geom.Line(m3, m2).addEnd(rad1);
+                            var fp3 = new geom.Line(m4, m1).addEnd(rad1);
+                            var fp4 = new geom.Line(m4, m2).addEnd(rad1);
                             var rad2 = m3.distance(fp1); // radius of the big circles
                             // construct the SVG shapes
                             $c1.attr({cx: m1.x, cy: m1.y, r: rad1});
@@ -1683,7 +1683,7 @@
         console.debug('initialising measure plugin. data:', data);
         var settings = data.settings;
         CSS = settings.cssPrefix+'measure-';
-        FULL_AREA  = geom.rectangle(0, 0, 1, 1);
+        FULL_AREA  = new geom.Rectangle(0, 0, 1, 1);
         // install event handlers
         var $data = $(data);
         $data.on('setup', handleSetup);
--- a/webapp/src/main/webapp/jquery/jquery.digilib.pluginstub.js	Wed Mar 08 13:12:01 2017 +0100
+++ b/webapp/src/main/webapp/jquery/jquery.digilib.pluginstub.js	Sun Mar 12 15:53:01 2017 +0100
@@ -63,7 +63,7 @@
         console.debug('installing STUB plugin. digilib:', digilib);
         // import geometry classes
         geom = digilib.fn.geometry;
-        FULL_AREA = geom.rectangle(0,0,1,1);
+        FULL_AREA = new geom.Rectangle(0,0,1,1);
         // add defaults, actions, buttons to the main digilib object
         $.extend(digilib.defaults, defaults);
         $.extend(digilib.actions, actions);
--- a/webapp/src/main/webapp/jquery/jquery.digilib.regions.js	Wed Mar 08 13:12:01 2017 +0100
+++ b/webapp/src/main/webapp/jquery/jquery.digilib.regions.js	Sun Mar 12 15:53:01 2017 +0100
@@ -414,7 +414,7 @@
         if (pos == null) {
             return null;
             }
-        var rect = geom.rectangle(pos[0], pos[1], pos[2], pos[3]);
+        var rect = new geom.Rectangle(pos[0], pos[1], pos[2], pos[3]);
         if (!fn.isNumber(rect.x) || !fn.isNumber(rect.y)) {
             return null;
             }
@@ -498,7 +498,7 @@
       var ww = data.settings.regionWidth;
       $.each(items, function (index, item) {
         addRegionDiv(data, {
-          rect: geom.rectangle(item.x, item.y, item.w || ww, item.h || ww),
+          rect: new geom.Rectangle(item.x, item.y, item.w || ww, item.h || ww),
           attributes: {'class' : CSS+"regionJSON "+CSS+"overlay", title: item.title },
           index: item.index || index+1
           });
@@ -649,7 +649,7 @@
         var coords = rg.split(",");
         var regions = $.map(coords, function (coord, index) {
             var pos = coord.split("/", 4);
-            var rect = geom.rectangle(pos[0], pos[1], pos[2], pos[3]);
+            var rect = new geom.Rectangle(pos[0], pos[1], pos[2], pos[3]);
             var attr = {'class' : CSS+"regionURL "+CSS+"overlay"};
             var item = {'rect' : rect, 'index' : index+1, 'attributes' : attr};
             return item;
@@ -814,7 +814,7 @@
         var $elem = data.$elem;
         var settings = data.settings;
         CSS = settings.cssPrefix;
-        FULL_AREA  = geom.rectangle(0, 0, 1, 1);
+        FULL_AREA  = new geom.Rectangle(0, 0, 1, 1);
         // install event handlers
         var $data = $(data);
         $data.on('setup', handleSetup);
--- a/webapp/src/main/webapp/jquery/jquery.digilib.sequence.js	Wed Mar 08 13:12:01 2017 +0100
+++ b/webapp/src/main/webapp/jquery/jquery.digilib.sequence.js	Sun Mar 12 15:53:01 2017 +0100
@@ -140,7 +140,7 @@
         console.debug('installing sequence plugin. digilib:', digilib);
         // import geometry classes
         geom = digilib.fn.geometry;
-        FULL_AREA = geom.rectangle(0,0,1,1);
+        FULL_AREA = new geom.Rectangle(0,0,1,1);
         // add defaults, actions, buttons to the main digilib object
         $.extend(digilib.defaults, defaults);
         $.extend(digilib.actions, actions);
--- a/webapp/src/main/webapp/jquery/jquery.digilib.transparent.js	Wed Mar 08 13:12:01 2017 +0100
+++ b/webapp/src/main/webapp/jquery/jquery.digilib.transparent.js	Sun Mar 12 15:53:01 2017 +0100
@@ -188,7 +188,7 @@
             }
         var imgTrafo = data.imgTrafo;
         var $scaler = data.$scaler;
-        var scalerPos = geom.position($scaler);
+        var scalerPos = new geom.Position($scaler);
         var tRect = null;
         var newCss = {};
         $div.css(newCss);
@@ -227,7 +227,7 @@
         var tc = data.settings.tc;
         if (tc == null) return ;
         var pos = coord.split("/", 4);
-        var rect = geom.rectangle(pos[0], pos[1], pos[2], pos[3]);
+        var rect = new geom.Rectangle(pos[0], pos[1], pos[2], pos[3]);
         return rect;
     };
 
@@ -262,7 +262,7 @@
         if (fn.slider == null) {
             return console.error('jquery.digilib.sliders.js is needed for transparent plugin')
             }
-        FULL_AREA = geom.rectangle(0,0,1,1);
+        FULL_AREA = new geom.Rectangle(0,0,1,1);
         // add defaults, actions, buttons to the main digilib object
         $.extend(digilib.defaults, defaults);
         $.extend(digilib.actions, actions);
--- a/webapp/src/main/webapp/jquery/jquery.digilib.vector.js	Wed Mar 08 13:12:01 2017 +0100
+++ b/webapp/src/main/webapp/jquery/jquery.digilib.vector.js	Sun Mar 12 15:53:01 2017 +0100
@@ -413,7 +413,7 @@
                     var $s = $(svgElement('rect', svgAttr(data, shape)));
                     $s.place = function () {
                         var p = shape.properties.screenpos;
-                        var r = geom.rectangle(p[0], p[1]);
+                        var r = new geom.Rectangle(p[0], p[1]);
                         this.attr({'x': r.x, 'y': r.y, 'width': r.width, 'height': r.height});
                         };
                     return $s;
@@ -567,7 +567,7 @@
         var handles = [];
         var createHandle = data.handleFactory[type];
         var insertHandle = function (i, item) {
-            var p = trafo.transform(geom.position(item));
+            var p = trafo.transform(new geom.Position(item));
             var $handle = createHandle();
             $handle.data('vertex', i);
             $handle.moveTo(p);
@@ -716,7 +716,7 @@
             var shape = $w.data('shape');
             var coords = shape.geometry.coordinates;
             var v1 = parseInt(vertex) > 0 ? vertex-1 : coords.length-1;
-            var pt = geom.position(coords[vertex]).mid(geom.position(coords[v1]));
+            var pt = new geom.Position(coords[vertex]).mid(new geom.Position(coords[v1]));
             console.debug('+ point', coords[vertex], pt);
             coords.splice(vertex, 0, pt);
             redrawShape(data, shape);
@@ -756,7 +756,7 @@
         var coords = shape.geometry.coordinates;
         var trafo = data.imgTrafo;
         var screenpos = $.map(coords, function (coord) {
-            return trafo.transform(geom.position(coord));
+            return trafo.transform(new geom.Position(coord));
             });
         if (shape.properties == null) {
           shape.properties = {};
@@ -781,7 +781,7 @@
             ymin = (y < ymin) ? y : ymin;
             ymax = (y > ymax) ? y : ymax;
         }
-        return geom.rectangle(xmin, ymin, xmax-xmin, ymax-ymin);
+        return new geom.Rectangle(xmin, ymin, xmax-xmin, ymax-ymin);
     };
 
     /**
@@ -888,7 +888,7 @@
         var dragStart = function (evt) { // start dragging
             // cancel if not left-click
             if (evt.which != 1) return;
-            pStart = geom.position(evt);
+            pStart = new geom.Position(evt);
             props.startpos = pStart;
             props.vtx = vtx;
             $(data).trigger('positionShape', shape);
@@ -899,7 +899,7 @@
         };
 
         var dragMove = function (evt) { // dragging
-            var pt = geom.position(evt);
+            var pt = new geom.Position(evt);
             pt.clipTo(imgRect);
             pos[vtx].moveTo(pt);
             if (isSupported(data, shapeType)) {
@@ -917,7 +917,7 @@
         };
 
         var dragEnd = function (evt) { // end dragging
-            var pt = geom.position(evt);
+            var pt = new geom.Position(evt);
             if ((pt.distance(pStart) < 5) && evt.type === 'mouseup') {
             	// not drag but click to start
                 return false;
@@ -971,14 +971,14 @@
         data.shapeFactory[shapeType].setup(data, shape);
         var $elem = data.$elem;
         var $body = $('body');
-        var bodyRect = geom.rectangle($body);
+        var bodyRect = new geom.Rectangle($body);
         // overlay div prevents other elements from reacting to mouse events 
         var $overlayDiv = $('<div class="'+data.settings.cssPrefix+'shapeOverlay" style="position:absolute; z-index:100;"/>');
         $elem.append($overlayDiv);
         bodyRect.adjustDiv($overlayDiv);
 
         var shapeStart = function (evt) {
-            var pt = geom.position(evt);
+            var pt = new geom.Position(evt);
             // setup shape
             var p1 = data.imgTrafo.invtransform(pt).toArray();
             var p2 = p1.slice(0);