view webapp/src/main/webapp/jquery/jquery.digilib.regions.js @ 1107:365f95a14057

factor out defineArea, using overlay div
author hertzhaft
date Sat, 27 Oct 2012 00:06:29 +0200
parents 415da4e4b76b
children ae796bcac7f4
line wrap: on
line source

/** optional digilib regions plugin

Mark up a digilib image with rectangular regions.

If the parameter "processHtmlRegions" is set, the plugin reads region data from HTML.
Region data should be declared inside in the digilib element, like so:

<map class="dl-keep dl-regioncontent">
   <area href="http://www.mpiwg-berlin.mpg.de" coords="0.1,0.1,0.4,0.1" alt="MPI fuer Wissenschaftsgeschichte"/>
   <area href="http://www.biblhertz.it" coords="0.5,0.8,0.4,0.1" alt="Bibliotheca Hertziana"/>
   <area coords="0.3,0.5,0.15,0.1" />
</map>

According to the HTML specs, "area" and "a" elements are allowed inside of a "map".
Both can have a "coords" attribute, but "area" elements can't contain child nodes.
To have regions with content use "a" tags, like so:.

<map class="dl-keep dl-regioncontent">
   <a href="http://www.mpiwg-berlin.mpg.de" coords="0.4907,0.3521,0.1458,0.107">
       MPI fuer Wissenschaftsgeschichte
   </a>
   <a href="http://www.biblhertz.it" coords="0.3413,0.2912,0.4345,0.2945">
       Bibliotheca Hertziana
   </a>
   <area coords="0.3,0.5,0.15,0.1" />
</map>

Regions can also be defined in Javascript:
Set the parameter "regions" to an array with items.
Each item should be an object containing the following fields:

rect
    a retangle with relative coordinates (0..1);
index (optional)
    a number to display in the region
attributes (optional)
    HTML attributes for the region (id, class, title)
inner (optional)
    inner HTML (has to be a jQuery object)

*/

(function($) {
    // the digilib object
    var digilib = null;
    // the normal zoom area
    var FULL_AREA = null;
    // the functions made available by digilib
    var fn = {
        // dummy function to avoid errors, gets overwritten by buttons plugin
        highlightButtons : function () {
            console.debug('regions: dummy function - highlightButtons');
            }
        };
    // affine geometry plugin
    var geom = null;
    // convenience variable, set in init()
    var CSS = '';

    var buttons = {
        defineregion : {
            onclick : "defineUserRegion",
            tooltip : "define a region",
            icon : "addregion.png"
            },
        removeregion : {
            onclick : "removeUserRegion",
            tooltip : "delete the last user defined region",
            icon : "delregion.png"
            },
        removeallregions : {
            onclick : "removeAllUserRegions",
            tooltip : "delete all user defined regions",
            icon : "delallregions.png"
            },
        regions : {
            onclick : "toggleRegions",
            tooltip : "show or hide regions",
            icon : "regions.png"
            },
        findcoords : {
            onclick : "findCoords",
            tooltip : "find a region by entering its relative coordinates",
            icon : "regions.png"
            },
        finddata : {
            onclick : "findData",
            tooltip : "find a region by entering text",
            icon : "regions.png"
            },
        regioninfo : {
            onclick : "showRegionInfo",
            tooltip : "show information about user defined regions",
            icon : "regioninfo.png"
            }
        };

    var defaults = {
        // are regions shown?
        'isRegionVisible' : true,
        // are region numbers shown?
        'showRegionNumbers' : true,
        // default width for region when only point is given
        'regionWidth' : 0.005,
        // is there region content in the page?
        'processHtmlRegions' : false,
        // region defined by users and in the URL
        'processUserRegions' : true,
        // callback for click on region
        'onClickRegion' : null,
        // callback when new user region is defined
        'onNewRegion' : null,
        // turn any region into a clickable link to its detail view (DEPRECATED)
        'autoZoomOnClick' : false,
        // css selector for area/a elements (must also be marked with class "dl-keep")
        'areaSelector' : 'map.dl-regioncontent area, map.dl-regioncontent a',
        // general buttonset of this plugin
        'regionSet' : ['regions', 'findcoords', 'finddata', 'lessoptions'],
        // buttonset for region editing by user
        'userRegionSet' : ['defineregion', 'removeregion', 'removeallregions', 'regioninfo'],
        // url param for regions
        'rg' : null,
        // array with region data
        'regions' : null,
        // region attributes to copy from HTML
        'regionAttributes' : {
            'id'    :1,
            'href'  :1,
            'name'  :1,
            'alt'   :1,
            'target':1,
            'title' :1,
            'style' :1
            }
        };

    var actions = { 

        // define a region interactively with two clicked points
        defineUserRegion : function(data) {
            if (!data.settings.isRegionVisible) {
                alert("Please turn on regions visibility!");
                return;
            }
            var onComplete = function(data, rect) {
                if (rect == null) return;
                var count = getRegions(data, 'regionURL').length;
                var attr = {'class' : CSS+'regionURL '+CSS+'overlay'};
                var item = {'rect' : rect, 'index' : count, 'attributes' : attr};
                var $regionDiv = addRegionDiv(data, item);
                fn.highlightButtons(data, 'defineregion', 0);
                redisplay(data);
                $(data).trigger('newRegion', [$regionDiv]);
                };
            fn.highlightButtons(data, 'defineregion', 1);
            fn.defineArea(data, onComplete, CSS+'regionArea');
        },

        // remove the last added URL region
        removeUserRegion : function (data) {
            if (!data.settings.isRegionVisible) {
                alert("Please turn on regions visibility!");
                return;
            }
            var selector = 'div.'+CSS+'regionURL';
            var $regionDiv = data.$elem.find(selector).last();
            if ($regionDiv.length == 0) return;
            $regionDiv.remove();
            redisplay(data);
        },

        // remove all manually added regions (defined through URL "rg" parameter)
        removeAllUserRegions : function (data) {
            if (!data.settings.isRegionVisible) {
                alert("Please turn on regions visibility!");
                return;
            }
            var selector = 'div.'+CSS+'regionURL';
            var $regionDivs = data.$elem.find(selector);
            if ($regionDivs.length == 0) return;
            $regionDivs.remove();
            redisplay(data);
        },

        // show/hide regions 
        toggleRegions : function (data) {
            var show = !data.settings.isRegionVisible;
            data.settings.isRegionVisible = show;
            fn.highlightButtons(data, 'regions', show);
            renderRegions(data, 1);
        },

        // show region info in a window
        showRegionInfo : function (data) {
            var $elem = data.$elem;
            var infoSelector = '#'+CSS+'regionInfo';
            if (fn.isOnScreen(data, infoSelector)) return; // already onscreen
            var html = '\
                <div id="'+CSS+'regionInfo" class="'+CSS+'keep '+CSS+'regionInfo">\
                    <table class="'+CSS+'infoheader">\
                        <tr>\
                            <td class="'+CSS+'infobutton html">HTML</td>\
                            <td class="'+CSS+'infobutton svgattr">SVG</td>\
                            <td class="'+CSS+'infobutton csv">CSV</td>\
                            <td class="'+CSS+'infobutton digilib">Digilib</td>\
                            <td class="'+CSS+'infobutton x">X</td>\
                        </tr>\
                    </table>\
                </div>';
            $info = $(html);
            $info.appendTo($elem);
            var $regions = getRegions(data, 'regionURL');
            $info.append(regionInfoHTML(data, $regions));
            $info.append(regionInfoSVG(data, $regions));
            $info.append(regionInfoCSV(data, $regions));
            $info.append(regionInfoDigilib(data, $regions));
            var bind = function(name) {
                $info.find('.'+name).on('click.regioninfo', function () {
                    $info.find('div.'+CSS+'info').hide();
                    $info.find('div.'+CSS+name).show();
                    fn.centerOnScreen(data, $info);
                    return false;
                    });
                };
            bind('html');
            bind('svgattr');
            bind('csv');
            bind('digilib');
            $info.on('click.regioninfo', function () {
                fn.withdraw($info);
                });
            $info.find('.x').on('click.regioninfo', function () {
                fn.withdraw($info);
                });
            $info.fadeIn();
            fn.centerOnScreen(data, $info);
        },

        // display region coordinates in an edit line
        showRegionCoords : function (data, $regionDiv) {
            var $elem = data.$elem;
            var rect = $regionDiv.data('rect');
            var text = $regionDiv.data('text');
            var coordString = packCoords(rect, ',');
            var html = '\
                <div id="'+CSS+'regionInfo" class="'+CSS+'keep '+CSS+'regionInfo">\
                    <div>'+text+'</div>\
                    <input name="coords" type="text" size="30" maxlength="40" value="'+coordString+'"/>\
                </div>';
            var $info = $(html);
            $info.appendTo($elem);
            $div = $info.find('div');
            $div.text(text);
            $input = $info.find('input');
            $input.on('focus.regioninfo', function (event) {
                this.select();
                });
            $input.on('blur.regioninfo', function (event) {
                fn.withdraw($info);
                return false;
                });
            $input.on('keypress.regioninfo', function (event) {
                fn.withdraw($info);  // OBS: "return false" disables copy!
                });
            $input.prop("readonly",true);
            $info.fadeIn();
            fn.centerOnScreen(data, $info);
            $input.focus();
            console.debug('showRegionCoords', coordString);
        },

        // draw a find region from coords and move into view
        regionFromCoords : function (data, coords) {
            var rect = parseCoords(data, coords);
            if (rect == null) {
                alert('invalid coordinates: ' + coords);
                return;
                }
            var attr = { 'class' : CSS+'findregion' };
            var item = { 'rect' : rect, 'attributes' : attr };
            var $regionDiv = addRegionDiv(data, item);
            var za = data.zoomArea;
            if (!fn.isFullArea(za)) {
                za.setCenter(rect.getCenter()).stayInside(FULL_AREA);
                if (!za.containsRect(rect)) {
                    za = FULL_AREA.copy();
                    }
                fn.setZoomArea(data, za);
                }
            console.debug('regionFromCoords', coords, rect, za);
            redisplay(data);
            },

        // find coordinates and display as new region
        findCoords : function (data) {
            var $elem = data.$elem;
            var findSelector = '#'+CSS+'regionFindCoords';
            if (fn.isOnScreen(data, findSelector)) return; // already onscreen
            var html = '\
                <div id="'+CSS+'regionFindCoords" class="'+CSS+'keep '+CSS+'regionInfo">\
                    <div>coordinates to find:</div>\
                    <form class="'+CSS+'form">\
                        <div>\
                            <input class="'+CSS+'input" name="coords" type="text" size="30" maxlength="40"/> \
                        </div>\
                        <input class="'+CSS+'submit" type="submit" name="sub" value="Ok"/>\
                        <input class="'+CSS+'cancel" type="button" value="Cancel"/>\
                    </form>\
                </div>';
            var $info = $(html);
            $info.appendTo($elem);
            var $form = $info.find('form');
            var $input = $info.find('input.'+CSS+'input');
            // handle submit
            $form.on('submit', function () {
                var coords = $input.val();
                actions.regionFromCoords(data, coords);
                fn.withdraw($info);
                return false;
                });
            // handle blur
            $input.on('blur', function () {
                fn.withdraw($info);
                });
            // handle cancel
            $form.find('.'+CSS+'cancel').on('click', function () {
                fn.withdraw($info);
                });
            $info.fadeIn();
            fn.centerOnScreen(data, $info);
            $input.focus();
        },

        // find text data and display as new region
        findData : function (data) {
            var $elem = data.$elem;
            var findSelector = '#'+CSS+'regionFindData';
            if (fn.isOnScreen(data, findSelector)) return; // already onscreen
            var textOptions = getTextOptions(data, 'regionHTML');
            var html = '\
                <div id="'+CSS+'regionFindData" class="'+CSS+'keep '+CSS+'regionInfo">\
                    <div>text to find:</div>\
                    <form class="'+CSS+'form">\
                        <div>\
                            <select class="'+CSS+'findData">\
                            '+textOptions+'\
                            </select>\
                        </div>\
                        <input class="'+CSS+'input" name="data" type="text" size="30" maxlength="40"/> \
                        <input class="'+CSS+'submit" type="submit" name="sub" value="Ok"/>\
                        <input class="'+CSS+'cancel" type="button" value="Cancel"/>\
                    </form>\
                </div>';
            var $info = $(html);
            $info.appendTo($elem);
            var $form = $info.find('form');
            var $input = $info.find('input.'+CSS+'input');
            var $select = $info.find('select');
            var findRegion = function () {
                var coords = $select.val();
                actions.regionFromCoords(data, coords);
                fn.withdraw($info);
                return false;
                };
            // handle submit
            $form.on('submit', findRegion);
            $select.on('change', findRegion);
            // handle cancel
            $form.find('.'+CSS+'cancel').on('click', function () {
                fn.withdraw($info);
                });
            $info.fadeIn();
            fn.centerOnScreen(data, $info);
            $input.focus();
        }
    };

    // make a coords string
    var packCoords = function (rect, sep) {
        if (sep == null) sep = ','; // comma as default separator
        return [
        fn.cropFloatStr(rect.x),
        fn.cropFloatStr(rect.y),
        fn.cropFloatStr(rect.width),
        fn.cropFloatStr(rect.height)
        ].join(sep);
    };

    // create a rectangle from a coords string
    var parseCoords = function (data, coords) {
        var pos = coords.match(/[0-9.]+/g); // TODO: check validity?
        if (pos == null) {
            return null;
            }
        var rect = geom.rectangle(pos[0], pos[1], pos[2], pos[3]);
        if (!fn.isNumber(rect.x) || !fn.isNumber(rect.y)) {
            return null;
            }
        if (!rect.getArea()) {
            var pt = rect.getPosition();
            rect.width = data.settings.regionWidth;
            rect.height = rect.width;
            rect.setCenter(pt);
            }
        return rect;
    };

    // create a new regionDiv and add it to data.$elem
    var newRegionDiv = function (data, attr) {
        var cls = CSS+'region';
        var $regionDiv = $('<div class="'+cls+'"/>');
        addRegionAttributes(data, $regionDiv, attr);
        data.$elem.append($regionDiv);
        return $regionDiv;
    };

    // copy attributes to a region div
    var addRegionAttributes = function (data, $regionDiv, attributes) {
        if (attributes == null) return;
        if (attributes['class']) {
            $regionDiv.addClass(attributes['class']);
            delete attributes['class'];
        }
        if (attributes['href']) {
            $regionDiv.data('href', attributes['href']);
            delete attributes['href'];
        }
        if (attributes['title']) {
            $regionDiv.data('text', attributes['title']);
        }
        $regionDiv.attr(attributes);
    };

    // set region number
    var addRegionNumber = function (data, $regionDiv, index) {
        if (!data.settings.showRegionNumbers) return;
        if (!fn.isNumber(index)) return;
        var $number = $('<a class="'+CSS+'regionnumber">'+index+'</a>');
        $regionDiv.append($number);
        return $regionDiv;
    };

    // construct a region from a rectangle
    var addRegionDiv = function (data, item) {
        var $regionDiv = newRegionDiv(data, item.attributes);
        var settings = data.settings;
        // add region number
        addRegionNumber(data, $regionDiv, item.index);
        // add inner HTML
        if (item.inner) {
            $regionDiv.append(item.inner);
        }
        // store the coordinates in data
        $regionDiv.data('rect', item.rect);
        // trigger a region event on click
        $regionDiv.on('click.dlRegion', function(evt) {
            $(data).trigger('regionClick', [$regionDiv]);
            });
        return $regionDiv;
    };

    // create regions from a Javascript array of items
    var createRegionsFromJS = function (data, items) {
        $.each(items, function(index, item) {
            addRegionDiv(data, item);
            });
    };

    // create regions from URL parameters
    var createRegionsFromURL = function (data) {
        var userRegions = unpackRegions(data);
        if (!userRegions) return;
        createRegionsFromJS(data, userRegions);
    };

    // create regions from HTML
    var createRegionsFromHTML = function (data) {
        // regions are defined in "area" tags
        var $areas = data.$elem.find(data.settings.areaSelector);
        console.debug("createRegionsFromHTML - elems found: ", $areas.length);
        $areas.each(function(index, area) {
            var $area = $(area);
            // the "title" attribute contains the text for the tooltip
            var title = $area.attr('title');
            // the "coords" attribute contains the region coords (0..1)
            var coords = $area.attr('coords');
            // create the rectangle
            var rect = parseCoords(data, coords);
            if (rect == null) {
                return console.error('bad coords in HTML:', title, coords);
            }
            // mark div class as regionHTML
            var cls = $area.attr('class') || '';
            cls += ' '+CSS+'regionHTML '+CSS+'overlay';
            var attr = {'class' : cls};
            // copy attributes
            for (var n in data.settings.regionAttributes) {
                attr[n] = $area.attr(n);
            }
            // copy inner HTML
            var $inner = $area.contents().clone();
            if (attr.href != null) {
                // wrap contents in a-tag
                var $a = $('<a href="'+attr.href+'"/>');
                $inner = $a.append($inner);
            }
            var item = {'rect' : rect, 'attributes' : attr, 'inner' : $inner};
            var $regionDiv = addRegionDiv(data, item);
        });
    };

    // select region divs (HTML or URL)
    var getRegions = function (data, selector) {
        var $regions = data.$elem.find('div.'+CSS+selector);
        return $regions;
    };

    // make text data options html
    var getTextOptions = function (data, selector) {
        var createOption = function(item, index) {
            var $item = $(item);
            var rect = $item.data('rect');
            if (rect == null)
                return null;
            var coords = packCoords(rect, ',');
            var text = $item.data('text');
            return '<option value="'+coords+'">'+text+'</option>';
            };
        var $regions = getRegions(data, selector);
        var options = $.map($regions, createOption);
        return options.join('');
    };

    // list of HTML regions matching text in its title attribute
    var matchRegionText = function (data, text) {
        var $regions = getRegions(data, 'regionHTML');
        var re = new RegExp(text);
        return $.grep($regions, function($item, index) {
            return re.match($item.data('text'));
            });
    };

    // html for later insertion
    var regionInfoHTML = function (data, $regions) {
        var $infoDiv = $('<div class="'+CSS+'info '+CSS+'html"/>');
        $infoDiv.append($('<div/>').text('<map class="'+CSS+'keep '+CSS+'regioncontent">'));
        $regions.each(function(index, region) {
            var rect = $(region).data('rect');
            var coords = packCoords(rect, ',');
            $infoDiv.append($('<div/>').text('<area coords="' + coords + '"/>'));
            });
        $infoDiv.append($('<div/>').text('</map>'));
        return $infoDiv;
    };

    // SVG-style
    var regionInfoSVG = function (data, $regions) {
        var $infoDiv = $('<div class="'+CSS+'info '+CSS+'svgattr"/>');
        $regions.each(function(index, region) {
            var rect = $(region).data('rect');
            var coords = packCoords(rect, ',');
            $infoDiv.append($('<div/>').text('"' + coords + '"'));
            });
        return $infoDiv;
    };

    // CSV-style
    var regionInfoCSV = function (data, $regions) {
        var $infoDiv = $('<div class="'+CSS+'info '+CSS+'csv"/>');
        $regions.each(function(index, region) {
            var rect = $(region).data('rect');
            var coords = packCoords(rect, ',');
            $infoDiv.append($('<div/>').text(index+1 + ": " + coords));
            });
        return $infoDiv;
    };

    // digilib-style (h,w@x,y)
    var regionInfoDigilib = function (data, $regions) {
        var $infoDiv = $('<div class="'+CSS+'info '+CSS+'digilib"/>');
        $regions.each(function(index, region) {
            var rect = $(region).data('rect');
            var coords = packCoords(rect, ',');
            $infoDiv.append($('<div/>').text(coords));
            });
        return $infoDiv;
    };

    // show a region on top of the scaler image 
    var renderRegion = function (data, $regionDiv, anim) {
        if (!data.imgTrafo) return;
        var zoomArea = data.zoomArea;
        var rect = $regionDiv.data('rect').copy();
        var show = data.settings.isRegionVisible;
        if (show && zoomArea.overlapsRect(rect) && !rect.containsRect(zoomArea)) {
            rect.clipTo(zoomArea);
            var screenRect = data.imgTrafo.transform(rect);
            // console.debug("renderRegion: pos=",geom.position(screenRect));
            if (anim) {
                $regionDiv.fadeIn();
            } else{
                $regionDiv.show();
            }
            // adjustDiv sets wrong coords when called BEFORE show()
            screenRect.adjustDiv($regionDiv);
        } else {
            if (anim) {
                $regionDiv.fadeOut();
            } else{
                $regionDiv.hide();
            }
        }
    };

    // show regions 
    var renderRegions = function (data, anim) {
        var render = function(index, region) {
            renderRegion(data, $(region), anim);
            };
        var $regions = getRegions(data, 'region')
        $regions.each(render);
    };

    // read region data from URL parameters
    var unpackRegions = function (data) { 
        var rg = data.settings.rg;
        if (rg == null) return [];
        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 attr = {'class' : CSS+"regionURL "+CSS+"overlay"};
            var item = {'rect' : rect, 'index' : index+1, 'attributes' : attr};
            return item;
            });
        return regions;
    };

    // pack user regions array into a URL parameter string
    var packRegions = function (data) {
        var $regions = getRegions(data, 'regionURL');
        if ($regions.length == 0 || !data.settings.processUserRegions) {
            data.settings.rg = null;
            return;
        }
        var pack = function(region, index) {
            var $region = $(region);
            var rect = $region.data('rect');
            var packed = packCoords(rect, '/');
            return packed;
            };
        var coords = $.map($regions, pack);
        var rg = coords.join(',');
        data.settings.rg = rg;
        console.debug('pack regions:', rg);
    };

    // zoom to the region coordinates
    var zoomToRegion = function (data, region) {
        digilib.actions.zoomArea(data, region);
    };

    // reload display after a region has been added or removed
    var redisplay = function (data) {
        packRegions(data);
        fn.redisplay(data);
    };

    // event handler, gets called when a newRegion event is triggered
    var handleNewRegion = function (evt, $regionDiv) {
        var data = this;
        var settings = data.settings;
        console.debug("regions: handleNewRegion", $regionDiv);
        if (typeof settings.onNewRegion === 'function') {
            // execute callback
            return settings.onNewRegion(data, $regionDiv);
            }
        if (typeof settings.onNewRegion === 'string') {
            // execute action
            return actions[settings.onNewRegion](data, $regionDiv);
        }
    };

    // event handler, gets called when a regionClick event is triggered
    var handleRegionClick = function (evt, $regionDiv) {
        var data = this;
        var settings = data.settings;
        console.debug("regions: handleRegionClick", $regionDiv);
        if ($regionDiv.data('href')) {
            // follow the href attribute of the region area
            window.location = $regionDiv.data('href'); //TODO: how about target?
        }
        if (typeof settings.onClickRegion === 'function') {
            // execute callback
            return settings.onClickRegion(data, $regionDiv);
        }
        if (typeof settings.onClickRegion === 'string') {
            // execute action
            return actions[settings.onClickRegion](data, $regionDiv);
        }
    };

    // event handler, reads region parameter and creates region divs
    var handleSetup = function (evt) {
        var data = this;
        var settings = data.settings;
        console.debug("regions: handleSetup", settings.rg);
        // regions with content are given in a Javascript array
        if (settings.regions) {
            createRegionsFromJS(data, settings.regions);
        }
        // regions with content are given in HTML divs
        if (settings.processHtmlRegions) {
            createRegionsFromHTML(data);
        }
        // regions are defined in the URL
        if (settings.processUserRegions) {
            createRegionsFromURL(data);
        }
    };

    // event handler, sets buttons and shows regions when scaler img is reloaded
    var handleUpdate = function (evt) {
        var data = this;
        console.debug("regions: handleUpdate");
        var settings = data.settings;
        fn.highlightButtons(data, 'regions' , settings.isRegionVisible);
        renderRegions(data);
    };

    // additional buttons
    var installButtons = function (data) {
        var settings = data.settings;
        var mode = settings.interactionMode;
        var buttonSettings = settings.buttonSettings[mode];
        var buttonSet = settings.regionSet;
        if (settings.processUserRegions) {
            var first = buttonSet.slice(0,1);
            var rest = buttonSet.slice(1);
            buttonSet = first.concat(settings.userRegionSet, rest);
            }
        // set regionSet to [] or '' for no buttons (when showing regions only)
        if (buttonSet.length && buttonSet.length > 0) {
            buttonSettings.regionSet = buttonSet;
            buttonSettings.buttonSets.push('regionSet');
            }
    };

    // plugin installation called by digilib on plugin object.
    var install = function(plugin) {
        digilib = plugin;
        console.debug('installing regions plugin. digilib:', digilib);
        // import digilib functions
        $.extend(fn, digilib.fn);
        // import geometry classes
        geom = fn.geometry;
        // add defaults, actions, buttons
        $.extend(digilib.defaults, defaults);
        $.extend(digilib.actions, actions);
        $.extend(digilib.buttons, buttons);
        // add "rg" to digilibParamNames
        digilib.defaults.digilibParamNames.push('rg');
    };

    // plugin initialization
    var init = function (data) {
        console.debug('initialising regions plugin. data:', data);
        var $elem = data.$elem;
        var settings = data.settings;
        CSS = settings.cssPrefix;
        FULL_AREA  = geom.rectangle(0, 0, 1, 1);
        // install event handlers
        var $data = $(data);
        $data.on('setup', handleSetup);
        $data.on('update', handleUpdate);
        $data.on('newRegion', handleNewRegion);
        $data.on('regionClick', handleRegionClick);
        // default: autoZoom to region, when clicked - DEPRECATED
        if (settings.autoZoomOnClick && settings.onClickRegion == null) {
            settings.onClickRegion = zoomToRegion;
        }
        // install region buttons if user defined regions are allowed
        if (digilib.plugins.buttons != null) {
            installButtons(data);
        }
    };

    // plugin object with name and install/init methods
    // shared objects filled by digilib on registration
    var pluginProperties = {
            name : 'regions',
            install : install,
            init : init,
            buttons : {},
            actions : {},
            fn : {},
            plugins : {}
    };

    if ($.fn.digilib == null) {
        $.error("jquery.digilib.regions must be loaded after jquery.digilib!");
    } else {
        $.fn.digilib('plugin', pluginProperties);
    }
})(jQuery);