diff --git a/JavaScript-API.md b/JavaScript-API.md new file mode 100644 index 0000000..5caf91b --- /dev/null +++ b/JavaScript-API.md @@ -0,0 +1,202 @@ +# JavaScript API + +Annotorious provides a JavaScript API you can use to get, add or remove annotations, and hook into the Annotorious event lifecycle. All functionality is exposed via the global _anno_ object. The _anno_ object has the following methods + +* _anno.activateSelector(opt_item_url_or_callback, opt_callback)_ +* _anno.addAnnotation(annotation, opt_replace)_ +* _anno.addHandler(type, handler)_ +* _anno.addPlugin(pluginName, opt_config_options)_ +* _anno.destroy(opt_item_url)_ +* _anno.getAnnotations(opt_item_url)_ +* _anno.hideAnnotations(opt_item_url)_ +* _anno.hideSelectionWidget(opt_item_url)_ +* _anno.highlightAnnotation(annotation)_ +* _anno.makeAnnotatable(item)_ +* _anno.removeAll(opt_item_url)_ +* _anno.removeAnnotation(annotation)_ +* _anno.reset()_ +* _anno.showAnnotations(opt_item_url)_ +* _anno.showSelectionWidget(opt_item_url)_ + +## anno.activateSelector(opt_item_url_or_callback, opt_callback) + +__NOTE: this method is currently only relevant for the OpenLayers module. Feel free to ignore in +case you are only using the standard image annotation features of Annotorious.__ + +Manually actives the selector. The selector can be activated on a specific item or globally, on all items (which serves mainly as a shortcut for pages where there is only one annotatable item). The function can take a callback function as parameter, which will be called when the selector is deactivated again. + +## anno.addAnnotation(annotation, opt_replace) + +Adds a new annotation, or replaces an existing annotation with a new annotation. (In the latter case, the parameter _opt\_replace_ must be the existing annotation.) + +Create the new annotation as an object literal, according to the following example: + + var myAnnotation = { + /** The URL of the image where the annotation should go **/ + src : 'http://www.example.com/myimage.jpg', + + /** The annotation text **/ + text : 'My annotation', + + /** The annotation shape **/ + shapes : [{ + /** The shape type **/ + type : 'rect', + + /** The shape geometry (relative coordinates) **/ + geometry : { x : 0.1, y: 0.1, width : 0.4, height: 0.3 } + }] + } + +__Some notes on annotation shapes:__ + +* Although the ``shapes`` field requires an array of shapes, Annotorious currently uses + the first shape in the array __only__. All other shapes are disregarded. (The array is there for future use, + and for reasons of compatibility with other annotation systems.) +* Currently, ``rect`` (rectangle) is the only supported shape type. +* Per default, Annotorious uses a normalized coordinate system. The example above represents + a rectangle that starts at a horizontal (vertical) distance of 10% of the image's width (height); + has a width of 40% of the image's width; and a height of 30% of the image's height. + +__Using pixel coordinates:__ optionally, you can also express geometry coordinates in pixel units. See below for an example: + + var myAnnotation = { + /** The URL of the image where the annotation should go **/ + src : 'http://www.example.com/myimage.jpg', + + /** The annotation text **/ + text : 'My annotation', + + /** The annotation shape **/ + shapes : [{ + /** The shape type **/ + type : 'rect', + + /** 'units' is required unless you want to use relative coordinates! **/ + units: 'pixel', + + /** The shape geometry (pixel coordinates) **/ + geometry : { x : 10, y: 10, width : 40, height: 60 } + }] + } + +__Making annotations 'read-only'__: in most cases, you probably don't want users to be able to delete or edit the +annotations you have added via the API. You can easily make them 'read-only' by adding an additional field to the object literal: + + editable : false + +If this field is set to false, there will be no _delete_ icon in the annotation popup. + +## anno.addHandler(type, handler) + +Adds an event handler function. Code example: + + // Logs newly-created annotations to the console + anno.addHandler('onAnnotationCreated', function(annotation) { + console.log(annotation.text); + }); + +Annotorious issues the following events: + +* _onMouseOverItem(event)_ - fired when the mouse enters an annotatable item +* _onMouseOutOfItem(event)_ - fired when the mouse leaves an annotatable item +* _onMouseOverAnnotation(event)_ - fired when the mouse enters an annotation +* _onMouseOutOfAnnotation(event)_ - fired when the mouse leaves an annotation +* _onSelectionStarted(event)_ - fired when the user starts a selection +* _onSelectionCanceled(event)_ - fired when the user cancels a selection (not available on all selection tools) +* _onSelectionCompleted(event)_ - fired when the user completes a selection +* _onSelectionChanged(event)_ - fired when the user changed a selection +* _beforePopupHide(popup)_ - fired just before the annotation info popup window hides +* _beforeAnnotationRemoved(annotation)_ - fired before an annotation is removed (Note: it is possible + to prevent annotation removal by returning _false_ from the handler method!) +* _onAnnotationRemoved(annotation)_ - fired when an annotation is removed from an imgae +* _onAnnotationCreated(annotation)_ - fired when an annotation was created +* _onAnnotationUpdated(annotation)_ - fired when an existing annotation was edited/updated + + +## anno.addPlugin(pluginName, opt_config_options) + +Registers a plugin. For more information, see the [Plugins Wiki page](/annotorious/annotorious/wiki/Plugins). + +## anno.destroy(opt_item_url) + +Destroys annotation functionality on a specific item, or on all items on the page. Note that this +method differs from ``anno.reset()`` (see below) insofar as ``destroy`` does not re-evaluate the +``annotatable`` CSS attributes. What is destroyed, stays destroyed. (Until re-enabled through +``anno.makeAnnotatable()``). + +## anno.getAnnotations(opt_item_url) + +Returns the current annotations. ``opt_item_url`` is optional. If omitted, the method call will return all annotations, on all annotatable items on the page. If set to a specific item URL, only the annotations on that item will be returned. + +## anno.hideAnnotations(opt_item_url) + +Hides existing annotations on all, or a specific item. + +## anno.hideSelectionWidget(opt_item_url) + +Disables the selection widget (the small tooltip in the upper left corner which says "Click and Drag to +Annotate"), thus preventing users from creating new annotations alltogether. The typical use case for this +is 'read-only' annotated images. I.e. if you want to add some pre-defined annotations using +anno.addAnnotation without the user being able to add or change anything. + +The selection widget can be hidden on a specific item or globally, on all annotatable items on the page. + +## anno.highlightAnnotation(annotation) + +Highlights the specified annotation, just as if the mouse pointer was hovering over it. The annotation +will remain highlighted until one of these conditions is met: + +* The user moves the mouse into, and out of the annotation +* The user moves the mouse over another annotation +* The highlight is removed by calling this method with an empty parameter, e.g. + _anno.highlightAnnotation()_ or _anno.highlightAnnotation(undefined)_ +* Another annotation is highlighted via _anno.highlightAnnotation_ + +## anno.makeAnnotatable(item) + +Makes an item on the screen annotatable (if there is a module available supporting the item format). You can +use this method as an alternative to CSS-based activation. It works just the same way, and is simply there for convenience, and to prepare for (future) item formats that technically don't support CSS-based activation (such as Web maps). + +Example: +``` + + + + + + + + + + +``` + +## anno.removeAll(opt_item_url) + +Removes all annotations. If the optional parameter ``opt_item_url`` is set, only the annotations on the +specified item will be removed. Otherwise all annotations on all items on the page will be removed. + +## anno.removeAnnotation(annotation) + +Removes an annotation from the page. + +## anno.reset() + +Performs a 'hard reset' on Annotorious. This means all annotation features will be removed, and the page +will be re-scanned for items with the 'annotatable' CSS class. (Note: this method could be handy in case you +are working with JavaScript image carousels. Just make sure the images have 'annotatable' set, then +reset Annotorious after each page flip.) + +## anno.showAnnotations(opt_item_url) + +Shows existing annotations on all, or a specific item (if they were hidden using _anno.hideAnnotations_). + +## anno.showSelectionWidget(opt_item_url) + +Enables the selection widget (the small tooltip in the upper left corner which says "Click and Drag to Annotate"), thus enabling users to creating new annotations. (Per default, the selection widget is +enabled.) \ No newline at end of file diff --git a/annotorious.min.js b/annotorious.min.js new file mode 100644 index 0000000..721b60d --- /dev/null +++ b/annotorious.min.js @@ -0,0 +1,306 @@ +function f(){return function(){}}function m(a){return function(){return this[a]}}function aa(a){return function(){return a}}var n,p=this;function ba(a,b){var c=a.split("."),d=p;c[0]in d||!d.execScript||d.execScript("var "+c[0]);for(var e;c.length&&(e=c.shift());)c.length||void 0===b?d=d[e]?d[e]:d[e]={}:d[e]=b}function ca(){}function da(a){a.ec=function(){return a.le?a.le:a.le=new a}} +function fa(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null"; +else if("function"==b&&"undefined"==typeof a.call)return"object";return b}function ga(a){return"array"==fa(a)}function ha(a){var b=fa(a);return"array"==b||"object"==b&&"number"==typeof a.length}function q(a){return"string"==typeof a}function ia(a){return"number"==typeof a}function r(a){return"function"==fa(a)}function ja(a){var b=typeof a;return"object"==b&&null!=a||"function"==b}function ka(a){return a[ma]||(a[ma]=++na)}var ma="closure_uid_"+(1E9*Math.random()>>>0),na=0; +function oa(a,b,c){return a.call.apply(a.bind,arguments)}function pa(a,b,c){if(!a)throw Error();if(2/g,za=/\"/g,va=/[&<>\"]/;function Aa(a){return String(a).replace(/\-([a-z])/g,function(a,c){return c.toUpperCase()})} +function Ba(a){var b=q(void 0)?"undefined".replace(/([-()\[\]{}+?*.$\^|,:#c?Math.max(0,a.length+c):c;if(q(a))return q(b)&&1==b.length?a.indexOf(b,c):-1;for(;cc?null:q(a)?a.charAt(c):a[c]}function Ha(a,b){return 0<=Ca(a,b)}function z(a,b){var c=Ca(a,b),d;(d=0<=c)&&x.splice.call(a,c,1);return d}function Ia(a){var b=a.length;if(0=arguments.length?x.slice.call(a,b):x.slice.call(a,b,c)}function La(a,b){x.sort.call(a,b||Ma)}function Ma(a,b){return a>b?1:aparseFloat(bb)){ab=String(fb);break a}}ab=bb}var gb={}; +function F(a){var b;if(!(b=gb[a])){b=0;for(var c=ta(String(ab)).split("."),d=ta(String(a)).split("."),e=Math.max(c.length,d.length),g=0;0==b&&g(0==u[1].length?0:parseInt(u[1],10))?1:0)||((0==w[2].length)< +(0==u[2].length)?-1:(0==w[2].length)>(0==u[2].length)?1:0)||(w[2]u[2]?1:0)}while(0==b)}b=gb[a]=0<=b}return b}var hb=p.document,ib=hb&&C?$a()||("CSS1Compat"==hb.compatMode?parseInt(ab,10):5):void 0;var jb,nb=!C||C&&9<=ib;!D&&!C||C&&C&&9<=ib||D&&F("1.9.1");C&&F("9");var ob=C||B||E;function pb(a){a=a.className;return q(a)&&a.match(/\S+/g)||[]}function qb(a,b){var c=pb(a),d=Ka(arguments,1),e=c.length+d.length;rb(c,d);a.className=c.join(" ");return c.length==e}function sb(a,b){var c=pb(a),d=Ka(arguments,1),e=tb(c,d);a.className=e.join(" ");return e.length==c.length-d.length}function rb(a,b){for(var c=0;ca):!1}function Ab(a){this.da=a||p.document||document}n=Ab.prototype;n.ae=zb;n.j=function(a){return q(a)?this.da.getElementById(a):a};n.G=Db; +n.createElement=function(a){return this.da.createElement(a)};n.createTextNode=function(a){return this.da.createTextNode(String(a))};function Qb(a){var b=a.da;a=E?b.body:b.documentElement;b=b.parentWindow||b.defaultView;return C&&F("10")&&b.pageYOffset!=a.scrollTop?new G(a.scrollLeft,a.scrollTop):new G(b.pageXOffset||a.scrollLeft,b.pageYOffset||a.scrollTop)}n.appendChild=function(a,b){a.appendChild(b)};n.append=function(a,b){Gb(H(a),a,arguments,1)};n.contains=Nb;function Rb(){return!0};/* + Portions of this code are from the Dojo Toolkit, received by + The Closure Library Authors under the BSD license. All other code is + Copyright 2005-2009 The Closure Library Authors. All Rights Reserved. + +The "New" BSD License: + +Copyright (c) 2005-2009, The Dojo Foundation +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + Neither the name of the Dojo Foundation nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +var I=function(){function a(a,c){if(!a)return[];if(a.constructor==Array)return a;if(!q(a))return[a];if(q(c)&&(c=q(c)?document.getElementById(c):c,!c))return[];c=c||document;var e=c.ownerDocument||c.documentElement;Qa=c.contentType&&"application/xml"==c.contentType||B&&(c.doctype||"[object XMLDocument]"==e.toString())||!!e&&(C?e.xml:c.xmlVersion||e.xmlVersion);return(e=d(a)(c))&&e.Ic?e:b(e)}function b(a){if(a&&a.Ic)return a;var b=[];if(!a||!a.length)return b;a[0]&&b.push(a[0]);if(2>a.length)return b; +ea++;if(C&&Qa){var c=ea+"";a[0].setAttribute("_zipIdx",c);for(var d=1,e;e=a[d];d++)a[d].getAttribute("_zipIdx")!=c&&b.push(e),e.setAttribute("_zipIdx",c)}else if(C&&a.Ge)try{for(d=1;e=a[d];d++)Sb(e)&&b.push(e)}catch(g){}else for(a[0]&&(a[0]._zipIdx=ea),d=1;e=a[d];d++)a[d]._zipIdx!=ea&&b.push(e),e._zipIdx=ea;return b}function c(a,b){if(!b)return 1;var c=lf(a);return b[c]?0:b[c]=1}function d(a,b){if(Bd){var c=Cd[a];if(c&&!b)return c}if(c=Dd[a])return c;var c=a.charAt(0),g=-1==a.indexOf(" ");0<=a.indexOf("#")&& +g&&(b=!0);if(!Bd||b||-1!="\x3e~+".indexOf(c)||C&&-1!=a.indexOf(":")||Ed&&0<=a.indexOf(".")||-1!=a.indexOf(":contains")||-1!=a.indexOf("|\x3d")){var h=a.split(/\s*,\s*/);return Dd[a]=2>h.length?e(a):function(a){for(var b=0,c=[],d;d=h[b++];)c=c.concat(e(d)(a));return c}}var k=0<="\x3e~+".indexOf(a.charAt(a.length-1))?a+" *":a;return Cd[a]=function(b){try{if(9!=b.nodeType&&!g)throw"";var c=b.querySelectorAll(k);C?c.Ge=!0:c.Ic=!0;return c}catch(e){return d(a,!0)(b)}}}function e(a){var b=Fd(ta(a));if(1== +b.length){var c=g(b[0]);return function(a){if(a=c(a,[]))a.Ic=!0;return a}}return function(a){a=kb(a);for(var c,d,e=b.length,h,k,uc=0;uc"\x3e~+".indexOf(a)?v.aa=a:v.Jc=a;u=-1}0<=l&&(v.Ba.push(c(l+1,A).replace(/\\/g,"")),l=-1)}function c(b,d){return ta(a.slice(b,d))}a=0<="\x3e~+".indexOf(a.slice(-1))?a+" * ":a+" ";for(var d= +[],e=-1,g=-1,h=-1,k=-1,l=-1,s=-1,u=-1,w="",P="",la,A=0,ea=a.length,v=null,Q=null;w=P,P=a.charAt(A),Ae?e=e%d&&d+e%d:0=d&&(g=e-e%d),e%=d):0>d&&(d*=-1,0=g&&(0>h||a<=h)&&a%d==e};b=e}var k=parseInt(b,10);return function(a){return Ub(a)==k}}},mf=C?function(a){var b=a.toLowerCase();"class"==b&&(a="className");return function(c){return Qa?c.getAttribute(a):c[a]||c[b]}}:function(a){return function(b){return b&&b.getAttribute&&b.hasAttribute(a)}},Gd={},Dd={},Cd={},Bd=!!document.querySelectorAll&& +(!E||F("526")),ea=0,lf=C?function(a){return Qa?a.getAttribute("_uid")||a.setAttribute("_uid",++ea)||ea:a.uniqueID}:function(a){return a._uid||(a._uid=++ea)};a.kb=vc;return a}();ba("goog.dom.query",I);ba("goog.dom.query.pseudos",I.kb);var Vb=!C||C&&9<=ib,Wb=!C||C&&9<=ib,Xb=C&&!F("9");!E||F("528");D&&F("1.9b")||C&&F("8")||B&&F("9.5")||E&&F("528");D&&!F("8")||C&&F("9");function Yb(){0!=Zb&&(this.kf=Error().stack,$b[ka(this)]=this)}var Zb=0,$b={};Yb.prototype.xc=!1;Yb.prototype.ub=function(){if(!this.xc&&(this.xc=!0,this.S(),0!=Zb)){var a=ka(this);delete $b[a]}};Yb.prototype.S=function(){if(this.jc)for(;this.jc.length;)this.jc.shift()()};function ac(a){a&&"function"==typeof a.ub&&a.ub()};function bc(a,b){this.type=a;this.currentTarget=this.target=b}n=bc.prototype;n.S=f();n.ub=f();n.Ra=!1;n.defaultPrevented=!1;n.re=!0;n.stopPropagation=function(){this.Ra=!0};n.preventDefault=function(){this.defaultPrevented=!0;this.re=!1};function cc(a){a.preventDefault()};function dc(a){dc[" "](a);return a}dc[" "]=ca;function ec(a,b){a&&this.init(a,b)}t(ec,bc);var fc=[1,4,2];n=ec.prototype;n.target=null;n.relatedTarget=null;n.offsetX=0;n.offsetY=0;n.clientX=0;n.clientY=0;n.screenX=0;n.screenY=0;n.button=0;n.keyCode=0;n.charCode=0;n.ctrlKey=!1;n.altKey=!1;n.shiftKey=!1;n.metaKey=!1;n.yd=!1;n.D=null; +n.init=function(a,b){var c=this.type=a.type;bc.call(this,c);this.target=a.target||a.srcElement;this.currentTarget=b;var d=a.relatedTarget;if(d){if(D){var e;a:{try{dc(d.nodeName);e=!0;break a}catch(g){}e=!1}e||(d=null)}}else"mouseover"==c?d=a.fromElement:"mouseout"==c&&(d=a.toElement);this.relatedTarget=d;this.offsetX=E||void 0!==a.offsetX?a.offsetX:a.layerX;this.offsetY=E||void 0!==a.offsetY?a.offsetY:a.layerY;this.clientX=void 0!==a.clientX?a.clientX:a.pageX;this.clientY=void 0!==a.clientY?a.clientY: +a.pageY;this.screenX=a.screenX||0;this.screenY=a.screenY||0;this.button=a.button;this.keyCode=a.keyCode||0;this.charCode=a.charCode||("keypress"==c?a.keyCode:0);this.ctrlKey=a.ctrlKey;this.altKey=a.altKey;this.shiftKey=a.shiftKey;this.metaKey=a.metaKey;this.yd=Ta?a.metaKey:a.ctrlKey;this.state=a.state;this.D=a;a.defaultPrevented&&this.preventDefault();delete this.Ra};function gc(a){return(Vb?0==a.D.button:"click"==a.type?!0:!!(a.D.button&fc[0]))&&!(E&&Ta&&a.ctrlKey)} +n.stopPropagation=function(){ec.H.stopPropagation.call(this);this.D.stopPropagation?this.D.stopPropagation():this.D.cancelBubble=!0};n.preventDefault=function(){ec.H.preventDefault.call(this);var a=this.D;if(a.preventDefault)a.preventDefault();else if(a.returnValue=!1,Xb)try{if(a.ctrlKey||112<=a.keyCode&&123>=a.keyCode)a.keyCode=-1}catch(b){}};n.He=m("D");n.S=f();var hc="closure_listenable_"+(1E6*Math.random()|0),ic=0;function jc(a,b,c,d,e,g){this.wa=a;this.pe=b;this.src=c;this.type=d;this.capture=!!e;this.ua=g;this.key=++ic;this.Fa=this.qb=!1}function kc(a){a.Fa=!0;a.wa=null;a.pe=null;a.src=null;a.ua=null};var lc={},mc={},nc={},oc={}; +function J(a,b,c,d,e){if(ga(b)){for(var g=0;ge.keyCode||void 0!=e.returnValue)return!0;a:{var k=!1;if(0==e.keyCode)try{e.keyCode=-1;break a}catch(l){k=!0}if(k||void 0==e.returnValue)e.returnValue=!0}}k=new ec;k.init(e,this);e=!0;try{if(c){for(var s=[],w=k.currentTarget;w;w=w.parentNode)s.push(w);g=d[!0];for(var u= +s.length-1;!k.Ra&&0<=u;u--)k.currentTarget=s[u],e&=xc(g,s[u],k);if(h)for(g=d[!1],u=0;!k.Ra&&u>>0);function pc(a){return r(a)?a:a[zc]||(a[zc]=function(b){return a.handleEvent(b)})};function Ac(a){Yb.call(this);this.he=a;this.w={}}t(Ac,Yb);var Bc=[];n=Ac.prototype;n.A=function(a,b,c,d,e){ga(b)||(Bc[0]=b,b=Bc);for(var g=0;g=this.left&&a.right<=this.right&&a.top>=this.top&&a.bottom<=this.bottom:a.x>=this.left&&a.x<=this.right&&a.y>=this.top&&a.y<=this.bottom:!1};Gc.prototype.round=function(){this.top=Math.round(this.top);this.right=Math.round(this.right);this.bottom=Math.round(this.bottom);this.left=Math.round(this.left);return this}; +Gc.prototype.translate=function(a,b){a instanceof G?(this.left+=a.x,this.right+=a.x,this.top+=a.y,this.bottom+=a.y):(this.left+=a,this.right+=a,ia(b)&&(this.top+=b,this.bottom+=b));return this};function Hc(a,b,c,d){this.left=a;this.top=b;this.width=c;this.height=d}Hc.prototype.contains=function(a){return a instanceof Hc?this.left<=a.left&&this.left+this.width>=a.left+a.width&&this.top<=a.top&&this.top+this.height>=a.top+a.height:a.x>=this.left&&a.x<=this.left+this.width&&a.y>=this.top&&a.y<=this.top+this.height};Hc.prototype.round=function(){this.left=Math.round(this.left);this.top=Math.round(this.top);this.width=Math.round(this.width);this.height=Math.round(this.height);return this}; +Hc.prototype.translate=function(a,b){a instanceof G?(this.left+=a.x,this.top+=a.y):(this.left+=a,ia(b)&&(this.top+=b));return this};function L(a,b,c){q(b)?Ic(a,c,b):wb(b,ra(Ic,a))}function Ic(a,b,c){(c=Jc(a,c))&&(a.style[c]=b)}function Jc(a,b){var c=Aa(b);if(void 0===a.style[c]){var d=(E?"Webkit":D?"Moz":C?"ms":B?"O":null)+Ba(b);if(void 0!==a.style[d])return d}return c}function Kc(a,b){var c=a.style[Aa(b)];return"undefined"!==typeof c?c:a.style[Jc(a,b)]||""}function M(a,b){var c=H(a);return c.defaultView&&c.defaultView.getComputedStyle&&(c=c.defaultView.getComputedStyle(a,null))?c[b]||c.getPropertyValue(b)||"":""} +function Lc(a,b){return M(a,b)||(a.currentStyle?a.currentStyle[b]:null)||a.style&&a.style[b]}function Mc(a,b,c){var d,e=D&&(Ta||Za)&&F("1.9");b instanceof G?(d=b.x,b=b.y):(d=b,b=c);a.style.left=Nc(d,e);a.style.top=Nc(b,e)}function Oc(a){var b;try{b=a.getBoundingClientRect()}catch(c){return{left:0,top:0,right:0,bottom:0}}C&&(a=a.ownerDocument,b.left-=a.documentElement.clientLeft+a.body.clientLeft,b.top-=a.documentElement.clientTop+a.body.clientTop);return b} +function Pc(a){if(C&&!(C&&8<=ib))return a.offsetParent;var b=H(a),c=Lc(a,"position"),d="fixed"==c||"absolute"==c;for(a=a.parentNode;a&&a!=b;a=a.parentNode)if(c=Lc(a,"position"),d=d&&"static"==c&&a!=b.documentElement&&a!=b.body,!d&&(a.scrollWidth>a.clientWidth||a.scrollHeight>a.clientHeight||"fixed"==c||"absolute"==c||"relative"==c))return a;return null} +function Qc(a){var b,c=H(a),d=Lc(a,"position"),e=D&&c.getBoxObjectFor&&!a.getBoundingClientRect&&"absolute"==d&&(b=c.getBoxObjectFor(a))&&(0>b.screenX||0>b.screenY),g=new G(0,0),h;b=c?H(c):document;(h=!C)||(h=C&&9<=ib)||(zb(b),h=!0);h=h?b.documentElement:b.body;if(a==h)return g;if(a.getBoundingClientRect)b=Oc(a),a=Qb(zb(c)),g.x=b.left+a.x,g.y=b.top+a.y;else if(c.getBoxObjectFor&&!e)b=c.getBoxObjectFor(a),a=c.getBoxObjectFor(h),g.x=b.screenX-a.screenX,g.y=b.screenY-a.screenY;else{e=a;do{g.x+=e.offsetLeft; +g.y+=e.offsetTop;e!=a&&(g.x+=e.clientLeft||0,g.y+=e.clientTop||0);if(E&&"fixed"==Lc(e,"position")){g.x+=c.body.scrollLeft;g.y+=c.body.scrollTop;break}e=e.offsetParent}while(e&&e!=a);if(B||E&&"absolute"==d)g.y-=c.body.offsetTop;for(e=a;(e=Pc(e))&&e!=c.body&&e!=h;)g.x-=e.scrollLeft,B&&"TR"==e.tagName||(g.y-=e.scrollTop)}return g}function Rc(a,b){var c=Sc(a),d=Sc(b);return new G(c.x-d.x,c.y-d.y)} +function Sc(a){if(1==a.nodeType){var b;if(a.getBoundingClientRect)b=Oc(a),b=new G(b.left,b.top);else{b=Qb(zb(a));var c=Qc(a);b=new G(c.x-b.x,c.y-b.y)}if(D&&!F(12)){var d;C?d="-ms-transform":E?d="-webkit-transform":B?d="-o-transform":D&&(d="-moz-transform");var e;d&&(e=Lc(a,d));e||(e=Lc(a,"transform"));a=e?(a=e.match(Tc))?new G(parseFloat(a[1]),parseFloat(a[2])):new G(0,0):new G(0,0);a=new G(b.x+a.x,b.y+a.y)}else a=b;return a}d=r(a.He);e=a;a.targetTouches?e=a.targetTouches[0]:d&&a.D.targetTouches&& +(e=a.D.targetTouches[0]);return new G(e.clientX,e.clientY)}function Uc(a,b,c){if(b instanceof vb)c=b.height,b=b.width;else if(void 0==c)throw Error("missing height argument");a.style.width=Nc(b,!0);a.style.height=Nc(c,!0)}function Nc(a,b){"number"==typeof a&&(a=(b?Math.round(a):a)+"px");return a} +function Vc(a){var b=Wc;if("none"!=Lc(a,"display"))return b(a);var c=a.style,d=c.display,e=c.visibility,g=c.position;c.visibility="hidden";c.position="absolute";c.display="inline";a=b(a);c.display=d;c.position=g;c.visibility=e;return a}function Wc(a){var b=a.offsetWidth,c=a.offsetHeight,d=E&&!b&&!c;return(void 0===b||d)&&a.getBoundingClientRect?(a=Oc(a),new vb(a.right-a.left,a.bottom-a.top)):new vb(b,c)}function Xc(a){var b=Qc(a);a=Vc(a);return new Hc(b.x,b.y,a.width,a.height)} +function N(a,b){var c=a.style;"opacity"in c?c.opacity=b:"MozOpacity"in c?c.MozOpacity=b:"filter"in c&&(c.filter=""===b?"":"alpha(opacity\x3d"+100*b+")")}function O(a,b){a.style.display=b?"":"none"}function Yc(a){return"rtl"==Lc(a,"direction")}var Zc=D?"MozUserSelect":E?"WebkitUserSelect":null; +function $c(a,b){if(/^\d+px?$/.test(b))return parseInt(b,10);var c=a.style.left,d=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;a.style.left=b;var e=a.style.pixelLeft;a.style.left=c;a.runtimeStyle.left=d;return e}function ad(a,b){var c=a.currentStyle?a.currentStyle[b]:null;return c?$c(a,c):0} +function bd(a,b){if(C){var c=ad(a,b+"Left"),d=ad(a,b+"Right"),e=ad(a,b+"Top"),g=ad(a,b+"Bottom");return new Gc(e,d,g,c)}c=M(a,b+"Left");d=M(a,b+"Right");e=M(a,b+"Top");g=M(a,b+"Bottom");return new Gc(parseFloat(e),parseFloat(d),parseFloat(g),parseFloat(c))}var cd={thin:2,medium:4,thick:6};function dd(a,b){if("none"==(a.currentStyle?a.currentStyle[b+"Style"]:null))return 0;var c=a.currentStyle?a.currentStyle[b+"Width"]:null;return c in cd?cd[c]:$c(a,c)} +function ed(a){if(C){var b=dd(a,"borderLeft"),c=dd(a,"borderRight"),d=dd(a,"borderTop");a=dd(a,"borderBottom");return new Gc(d,c,a,b)}b=M(a,"borderLeftWidth");c=M(a,"borderRightWidth");d=M(a,"borderTopWidth");a=M(a,"borderBottomWidth");return new Gc(parseFloat(d),parseFloat(c),parseFloat(a),parseFloat(b))}var Tc=/matrix\([0-9\.\-]+, [0-9\.\-]+, [0-9\.\-]+, [0-9\.\-]+, ([0-9\.\-]+)p?x?, ([0-9\.\-]+)p?x?\)/;function fd(a,b,c){Ec.call(this);this.target=a;this.handle=b||a;this.qd=c||new Hc(NaN,NaN,NaN,NaN);this.da=H(a);this.sa=new Ac(this);a=ra(ac,this.sa);this.jc||(this.jc=[]);this.jc.push(qa(a,void 0));J(this.handle,["touchstart","mousedown"],this.ue,!1,this)}t(fd,Ec);var gd=C||D&&F("1.9.3");n=fd.prototype;n.clientX=0;n.clientY=0;n.screenX=0;n.screenY=0;n.we=0;n.xe=0;n.sb=0;n.tb=0;n.Yd=!0;n.jb=!1;n.je=0;n.Se=0;n.Oe=!1;n.Hd=!1;n.zc=m("sa");function hd(a,b){a.qd=b||new Hc(NaN,NaN,NaN,NaN)} +n.S=function(){fd.H.S.call(this);sc(this.handle,["touchstart","mousedown"],this.ue,!1,this);this.sa.Sa();gd&&this.da.releaseCapture();this.handle=this.target=null};function id(a){void 0===a.Ta&&(a.Ta=Yc(a.target));return a.Ta} +n.ue=function(a){var b="mousedown"==a.type;if(!this.Yd||this.jb||b&&!gc(a))this.dispatchEvent("earlycancel");else{jd(a);if(0==this.je)if(this.dispatchEvent(new kd("start",this,a.clientX,a.clientY,a)))this.jb=!0,a.preventDefault();else return;else a.preventDefault();var b=this.da,c=b.documentElement,d=!gd;this.sa.A(b,["touchmove","mousemove"],this.Me,d);this.sa.A(b,["touchend","mouseup"],this.yc,d);gd?(c.setCapture(!1),this.sa.A(c,"losecapture",this.yc)):this.sa.A(b?b.parentWindow||b.defaultView:window, +"blur",this.yc);C&&this.Oe&&this.sa.A(b,"dragstart",cc);this.Ye&&this.sa.A(this.Ye,"scroll",this.Ve,d);this.clientX=this.we=a.clientX;this.clientY=this.xe=a.clientY;this.screenX=a.screenX;this.screenY=a.screenY;this.Hd?(a=this.target,b=a.offsetLeft,c=a.offsetParent,c||"fixed"!=Lc(a,"position")||(c=H(a).documentElement),c?(D?(d=ed(c),b+=d.left):C&&8<=ib&&(d=ed(c),b-=d.left),a=Yc(c)?c.clientWidth-(b+a.offsetWidth):b):a=b):a=this.target.offsetLeft;this.sb=a;this.tb=this.target.offsetTop;this.xd=Qb(zb(this.da)); +this.Se=sa()}};n.yc=function(a,b){this.sa.Sa();gd&&this.da.releaseCapture();if(this.jb){jd(a);this.jb=!1;var c=ld(this,this.sb),d=md(this,this.tb);this.dispatchEvent(new kd("end",this,a.clientX,a.clientY,a,c,d,b||"touchcancel"==a.type))}else this.dispatchEvent("earlycancel")};function jd(a){var b=a.type;"touchstart"==b||"touchmove"==b?a.init(a.D.targetTouches[0],a.currentTarget):"touchend"!=b&&"touchcancel"!=b||a.init(a.D.changedTouches[0],a.currentTarget)} +n.Me=function(a){if(this.Yd){jd(a);var b=(this.Hd&&id(this)?-1:1)*(a.clientX-this.clientX),c=a.clientY-this.clientY;this.clientX=a.clientX;this.clientY=a.clientY;this.screenX=a.screenX;this.screenY=a.screenY;if(!this.jb){var d=this.we-this.clientX,e=this.xe-this.clientY;if(d*d+e*e>this.je)if(this.dispatchEvent(new kd("start",this,a.clientX,a.clientY,a)))this.jb=!0;else{this.xc||this.yc(a);return}}c=nd(this,b,c);b=c.x;c=c.y;this.jb&&this.dispatchEvent(new kd("beforedrag",this,a.clientX,a.clientY,a, +b,c))&&(od(this,a,b,c),a.preventDefault())}};function nd(a,b,c){var d=Qb(zb(a.da));b+=d.x-a.xd.x;c+=d.y-a.xd.y;a.xd=d;a.sb+=b;a.tb+=c;b=ld(a,a.sb);a=md(a,a.tb);return new G(b,a)}n.Ve=function(a){var b=nd(this,0,0);a.clientX=this.clientX;a.clientY=this.clientY;od(this,a,b.x,b.y)};function od(a,b,c,d){a.Xd(c,d);a.dispatchEvent(new kd("drag",a,b.clientX,b.clientY,b,c,d))} +function ld(a,b){var c=a.qd,d=isNaN(c.left)?null:c.left,c=isNaN(c.width)?0:c.width;return Math.min(null!=d?d+c:Infinity,Math.max(null!=d?d:-Infinity,b))}function md(a,b){var c=a.qd,d=isNaN(c.top)?null:c.top,c=isNaN(c.height)?0:c.height;return Math.min(null!=d?d+c:Infinity,Math.max(null!=d?d:-Infinity,b))}n.Xd=function(a,b){this.Hd&&id(this)?this.target.style.right=a+"px":this.target.style.left=a+"px";this.target.style.top=b+"px"}; +function kd(a,b,c,d,e,g,h,k){bc.call(this,a);this.clientX=c;this.clientY=d;this.jf=e;this.left=void 0!==g?g:b.sb;this.top=void 0!==h?h:b.tb;this.mf=b;this.lf=!!k}t(kd,bc);function pd(a){for(var b=0,c=0;a&&!isNaN(a.offsetLeft)&&!isNaN(a.offsetTop);)b+=a.offsetLeft-a.scrollLeft,c+=a.offsetTop-a.scrollTop,a=a.offsetParent;return{top:c,left:b}}function qd(a){window.addEventListener?window.addEventListener("load",a,!1):window.attachEvent&&window.attachEvent("onload",a)} +function rd(a,b){var c=document.createElement("div");L(c,"position","absolute");L(c,"top","0px");L(c,"right","0px");L(c,"width","5px");L(c,"height","100%");L(c,"cursor","e-resize");a.appendChild(c);var d=ed(a),d=Xc(a).width-d.right-d.left,c=new fd(c);hd(c,new Hc(d,0,800,0));c.Xd=function(c){L(a,"width",c+"px");b&&b()}}function sd(a){if(02*this.X&&vd(this),!0):!1};function vd(a){if(a.X!=a.w.length){for(var b=0,c=0;byd(a)?-1:1)*b,k=g.x-c.x,l=g.y-c.y,s=0h?-1:0,h=Math.sqrt(Math.pow(h,2)/(1+Math.pow(k/l,2)));d.push({x:g.x+Math.abs(k/l*h)*(0k?-1:0)*s,y:g.y+Math.abs(h)*(0l?-1:0)*s})}return d};function Ld(a,b,c,d){0a.x+a.width||c>a.y+a.height?!1:!0;if("polygon"==a.type){a=a.geometry.points;for(var d=!1,e=a.length-1,g=0;gc!=a[e].y>c&&b<(a[e].x-a[g].x)*(c-a[g].y)/(a[e].y-a[g].y)+a[g].x&&(d=!d),e=g;return d}return!1} +function Pd(a){return a.type==T||"arrow"==a.type?(a="arrow"==a.type?Od(a.geometry):a.geometry,a.width*a.height):"polygon"==a.type?Math.abs(yd(a.geometry.points)):0}function Qd(a){if(a.type==T)return a;if("polygon"==a.type){for(var b=a.geometry.points,c=b[0].x,d=b[0].x,e=b[0].y,g=b[0].y,h=1;hd&&(d=b[h].x),b[h].xg&&(g=b[h].y),b[h].yyd(c)?-1:1;if(4>c.length)c=Ad(c,d*b);else{for(var e=c.length-1,g=1,h=[],k=0;kc.length-1&&(g=0);c=h}return new Md("polygon",new xd(c),!1,a.style)} +function U(a,b){if(a.type==T){var c=a.geometry,d=b(c);d.rotation=c.rotation;return new Md(T,d,!1,a.style,a.mask)}if("polygon"==a.type){var e=[];y(a.geometry.points,function(a){e.push(b(a))});return new Md("polygon",new xd(e),!1,a.style)}if("arrow"==a.type)return new Md("arrow",new Td(b(a.geometry.arrowTail),b(a.geometry.arrowHead)),!1,a.style)}function V(a){return JSON.stringify(a.geometry)};function Ud(a,b,c,d,e,g){this.id=g;this.src=a;this.text=b;this.textId=e;this.shapes=[c];this.created_at=d||Date.now();this.context=document.URL;this.setMask=this.setMask}Ud.prototype.setMask=function(a,b,c,d){if(!b||this.shapes.length>=b)b=0;this.shapes[b].type!=T?console.log("WARNING: impossible to set mask in shape "+this.shapes[b].type):this.shapes[b].mask=a;c&&(this.shapes[b].style.maskTransparency=c);void 0!=d&&(this.shapes[b].style.maskBorder=d)};function Vd(){}function Wd(a,b){a.l=new ud;a.Xc=[];a.Sb=[];a.pb=[];a.lb=[];a.Sc=[];a.pc={Ab:!1,zb:!1};a.mb=new ud;a.Jb={};a.Sd=b}function Xd(a,b){var c=a.mb.get(b);c||(c={Ab:!1,zb:!1},a.mb.set(b,c));return c} +function Yd(a,b){var c=a.Ac(b);if(!a.l.get(c)){var d=a.vd(b),e=[],g=[];y(a.Xc,function(a){d.addHandler(a.type,a.ua)});y(a.Sb,function(a){if(a.onInitAnnotator)a.onInitAnnotator(d)});y(a.lb,function(a){a.src==c&&(d.P(a),e.push(a))});y(a.Sc,function(a){a.src==c&&(d.T(a),g.push(a))});y(e,function(b){z(a.lb,b)});y(g,function(b){z(a.Sc,b)});var h=a.mb.get(c);h?(h.Ab&&d.Z(),h.zb&&d.Ea(),a.mb.remove(c)):(a.pc.Ab&&d.Z(),a.pc.zb&&d.Ea());a.Jb&&0!==Object.keys(a.Jb).length&&d.G(a.Jb);a.l.set(c,d);z(a.pb,b)}} +function Zd(a){var b,c;for(c=a.pb.length;0window.pageYOffset&&g+h>window.pageXOffset)&&Yd(a,b)}}function $d(a,b,c){if(b){var d=a.l.get(b);d?c?d.Va():d.Ea():Xd(a,b).zb=c}else y(R(a.l),function(a){c?a.Va():a.Ea()}),a.pc.zb=!c,y(R(a.mb),function(a){a.zb=!c})} +function ae(a,b,c){if(b){var d=a.l.get(b);d?c?d.fa():d.Z():Xd(a,b).Ab=c}else y(R(a.l),function(a){c?a.fa():a.Z()}),a.pc.Ab=!c,y(R(a.mb),function(a){a.Ab=!c})}n=Vd.prototype;n.Aa=function(a,b){var c=void 0,d=void 0;q(a)?(c=a,d=b):r(a)&&(d=a);c?(c=this.l.get(c))&&c.Aa(d):y(R(this.l),function(a){a.Aa(d)})};n.stopSelection=function(a){a?(a=this.l.get(a))&&a.stopSelection():y(R(this.l),function(a){a.stopSelection()})}; +n.P=function(a,b){if(be(this,a.src)){var c=this.l.get(a.src);c?c.P(a,b):(this.lb.push(a),b&&z(this.lb,b))}};n.addHandler=function(a,b){y(R(this.l),function(c){c.addHandler(a,b)});this.Xc.push({type:a,ua:b})};n.ya=function(a,b){y(R(this.l),function(c){c.ya(a,b)});y(this.Xc,function(c,d,e){c.type!==a||b&&c.ua!==b||x.splice.call(e,d,1)})};n.sc=function(a){this.Sb.push(a);y(R(this.l),function(b){if(a.onInitAnnotator)a.onInitAnnotator(b)})}; +function be(a,b){return wd(a.l.xa,b)?!0:null!=Ga(a.pb,function(c){return a.Ac(c)==b})}n.destroy=function(a){if(a){var b=this.l.get(a);b&&(b.destroy(),this.l.remove(a))}else y(R(this.l),function(a){a.destroy()}),this.l.clear()};n.Ma=function(a){if(be(this,a)&&(a=this.l.get(a)))return a.Ma().getName()};n.M=function(a){if(a){var b=this.l.get(a);return b?b.M():Da(this.lb,function(b){return b.src==a})}var c=[];y(R(this.l),function(a){Ja(c,a.M())});Ja(c,this.lb);return c}; +n.Da=function(a){if(be(this,a)&&(a=this.l.get(a)))return Ea(a.Da(),function(a){return a.getName()})};n.Ea=function(a){$d(this,a,!1)};n.Z=function(a){ae(this,a,!1)};n.J=function(a){if(a){if(be(this,a.src)){var b=this.l.get(a.src);b&&b.J(a)}}else y(R(this.l),function(a){a.J()})};n.init=function(){this.Sd&&Ja(this.pb,this.Sd());Zd(this);var a=this,b=J(window,"scroll",function(){0/g,re=/\"/g,se=/=/g,te=/\0/g,ue=/&(#\d+|#x[0-9A-Fa-f]+|\w+);/g,ve=/^#(\d+)$/,we=/^#x([0-9A-Fa-f]+)$/,xe=RegExp("^\\s*(?:(?:([a-z][a-z-]*)(\\s*\x3d\\s*(\"[^\"]*\"|'[^']*'|(?\x3d[a-z][a-z-]*\\s*\x3d)|[^\x3e\"'\\s]*))?)|(/?\x3e)|[^a-z\\s\x3e]+)", +"i"),ye=RegExp("^(?:\x26(\\#[0-9]+|\\#[x][0-9a-f]+|\\w+);|\x3c[!]--[\\s\\S]*?--\x3e|\x3c!\\w[^\x3e]*\x3e|\x3c\\?[^\x3e*]*\x3e|\x3c(/)?([a-z][a-z0-9]*)|([^\x3c\x26\x3e]+)|([\x3c\x26\x3e]))","i"); +ke.prototype.parse=function(a,b){var c=null,d=!1,e=[],g,h,k;a.ka=[];for(a.Oa=!1;b;){var l=b.match(d?xe:ye);b=b.substring(l[0].length);if(d)if(l[1]){var s=l[1].toLowerCase();if(l[2]){l=l[3];switch(l.charCodeAt(0)){case 34:case 39:l=l.substring(1,l.length-1)}l=l.replace(te,"").replace(ue,qa(this.Pe,this))}else l=s;e.push(s,l)}else l[4]&&(void 0!==h&&(k?a.ve&&a.ve(g,e):a.Zd&&a.Zd(g)),k&&h&12&&(c=null===c?b.toLowerCase():c.substring(c.length-b.length),d=c.indexOf("\x3c/"+g),0>d&&(d=b.length),h&4?a.Vd&& +a.Vd(b.substring(0,d)):a.qe&&a.qe(b.substring(0,d).replace(oe,"\x26amp;$1").replace(pe,"\x26lt;").replace(qe,"\x26gt;")),b=b.substring(d)),g=h=k=void 0,e.length=0,d=!1);else if(l[1])ze(a,l[0]);else if(l[3])k=!l[2],d=!0,g=l[3].toLowerCase(),h=me.hasOwnProperty(g)?me[g]:void 0;else if(l[4])ze(a,l[4]);else if(l[5])switch(l[5]){case "\x3c":ze(a,"\x26lt;");break;case "\x3e":ze(a,"\x26gt;");break;default:ze(a,"\x26amp;")}}for(c=a.ka.length;0<=--c;)a.Ga.append("\x3c/",a.ka[c],"\x3e");a.ka.length=0}; +ke.prototype.Pe=function(a){a=a.toLowerCase();if(le.hasOwnProperty(a))return le[a];var b=a.match(ve);return b?String.fromCharCode(parseInt(b[1],10)):(b=a.match(we))?String.fromCharCode(parseInt(b[1],16)):""};function Ae(){};/* + Portions of this code are from the google-caja project, received by + Google under the Apache license (http://code.google.com/p/google-caja/). + All other code is Copyright 2009 Google, Inc. All Rights Reserved. + +// Copyright (C) 2006 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +*/ +function Be(a,b){var c=new je;(new ke).parse(new Ce(c,b,void 0),a);return c.toString()}function Ce(a,b,c){this.Ga=a;this.ka=[];this.Oa=!1;this.ze=b;this.Hc=c}t(Ce,Ae); +var De={"*::class":9,"*::dir":0,"*::id":4,"*::lang":0,"*::onclick":2,"*::ondblclick":2,"*::onkeydown":2,"*::onkeypress":2,"*::onkeyup":2,"*::onload":2,"*::onmousedown":2,"*::onmousemove":2,"*::onmouseout":2,"*::onmouseover":2,"*::onmouseup":2,"*::style":3,"*::title":0,"*::accesskey":0,"*::tabindex":0,"*::onfocus":2,"*::onblur":2,"a::coords":0,"a::href":1,"a::hreflang":0,"a::name":7,"a::onblur":2,"a::rel":0,"a::rev":0,"a::shape":0,"a::target":10,"a::type":0,"area::accesskey":0,"area::alt":0,"area::coords":0, +"area::href":1,"area::nohref":0,"area::onfocus":2,"area::shape":0,"area::tabindex":0,"area::target":10,"bdo::dir":0,"blockquote::cite":1,"br::clear":0,"button::accesskey":0,"button::disabled":0,"button::name":8,"button::onblur":2,"button::onfocus":2,"button::tabindex":0,"button::type":0,"button::value":0,"caption::align":0,"col::align":0,"col::char":0,"col::charoff":0,"col::span":0,"col::valign":0,"col::width":0,"colgroup::align":0,"colgroup::char":0,"colgroup::charoff":0,"colgroup::span":0,"colgroup::valign":0, +"colgroup::width":0,"del::cite":1,"del::datetime":0,"dir::compact":0,"div::align":0,"dl::compact":0,"font::color":0,"font::face":0,"font::size":0,"form::accept":0,"form::action":1,"form::autocomplete":0,"form::enctype":0,"form::method":0,"form::name":7,"form::onreset":2,"form::onsubmit":2,"form::target":10,"h1::align":0,"h2::align":0,"h3::align":0,"h4::align":0,"h5::align":0,"h6::align":0,"hr::align":0,"hr::noshade":0,"hr::size":0,"hr::width":0,"img::align":0,"img::alt":0,"img::border":0,"img::height":0, +"img::hspace":0,"img::ismap":0,"img::longdesc":1,"img::name":7,"img::src":1,"img::usemap":11,"img::vspace":0,"img::width":0,"input::accept":0,"input::accesskey":0,"input::autocomplete":0,"input::align":0,"input::alt":0,"input::checked":0,"input::disabled":0,"input::ismap":0,"input::maxlength":0,"input::name":8,"input::onblur":2,"input::onchange":2,"input::onfocus":2,"input::onselect":2,"input::readonly":0,"input::size":0,"input::src":1,"input::tabindex":0,"input::type":0,"input::usemap":11,"input::value":0, +"ins::cite":1,"ins::datetime":0,"label::accesskey":0,"label::for":5,"label::onblur":2,"label::onfocus":2,"legend::accesskey":0,"legend::align":0,"li::type":0,"li::value":0,"map::name":7,"menu::compact":0,"ol::compact":0,"ol::start":0,"ol::type":0,"optgroup::disabled":0,"optgroup::label":0,"option::disabled":0,"option::label":0,"option::selected":0,"option::value":0,"p::align":0,"pre::width":0,"q::cite":1,"select::disabled":0,"select::multiple":0,"select::name":8,"select::onblur":2,"select::onchange":2, +"select::onfocus":2,"select::size":0,"select::tabindex":0,"table::align":0,"table::bgcolor":0,"table::border":0,"table::cellpadding":0,"table::cellspacing":0,"table::frame":0,"table::rules":0,"table::summary":0,"table::width":0,"tbody::align":0,"tbody::char":0,"tbody::charoff":0,"tbody::valign":0,"td::abbr":0,"td::align":0,"td::axis":0,"td::bgcolor":0,"td::char":0,"td::charoff":0,"td::colspan":0,"td::headers":6,"td::height":0,"td::nowrap":0,"td::rowspan":0,"td::scope":0,"td::valign":0,"td::width":0, +"textarea::accesskey":0,"textarea::cols":0,"textarea::disabled":0,"textarea::name":8,"textarea::onblur":2,"textarea::onchange":2,"textarea::onfocus":2,"textarea::onselect":2,"textarea::readonly":0,"textarea::rows":0,"textarea::tabindex":0,"tfoot::align":0,"tfoot::char":0,"tfoot::charoff":0,"tfoot::valign":0,"th::abbr":0,"th::align":0,"th::axis":0,"th::bgcolor":0,"th::char":0,"th::charoff":0,"th::colspan":0,"th::headers":6,"th::height":0,"th::nowrap":0,"th::rowspan":0,"th::scope":0,"th::valign":0, +"th::width":0,"thead::align":0,"thead::char":0,"thead::charoff":0,"thead::valign":0,"tr::align":0,"tr::bgcolor":0,"tr::char":0,"tr::charoff":0,"tr::valign":0,"ul::compact":0,"ul::type":0}; +Ce.prototype.ve=function(a,b){if(!this.Oa&&me.hasOwnProperty(a)){var c=me[a];if(!(c&32))if(c&16)this.Oa=!(c&2);else{for(var d=b,e=0;eb)){for(var d=this.ka.length;--d>b;)c=this.ka[d],me[c]&1||this.Ga.append("\x3c/",c,"\x3e");this.ka.length=b;this.Ga.append("\x3c/",a,"\x3e")}}}};function ze(a,b){a.Oa||a.Ga.append(b)}Ce.prototype.qe=function(a){this.Oa||this.Ga.append(a)}; +Ce.prototype.Vd=function(a){this.Oa||this.Ga.append(a)};function Ee(a,b,c,d,e){if(!(C||E&&F("525")))return!0;if(Ta&&e)return Fe(a);if(e&&!d||!c&&(17==b||18==b||Ta&&91==b))return!1;if(E&&d&&c)switch(a){case 220:case 219:case 221:case 192:case 186:case 189:case 187:case 188:case 190:case 191:case 192:case 222:return!1}if(C&&d&&b==a)return!1;switch(a){case 13:return!(C&&C&&9<=ib);case 27:return!E}return Fe(a)} +function Fe(a){if(48<=a&&57>=a||96<=a&&106>=a||65<=a&&90>=a||E&&0==a)return!0;switch(a){case 32:case 63:case 107:case 109:case 110:case 111:case 186:case 59:case 189:case 187:case 61:case 188:case 190:case 191:case 192:case 222:case 219:case 220:case 221:return!0;default:return!1}}function Ge(a){switch(a){case 61:return 187;case 59:return 186;case 224:return 91;case 0:return 224;default:return a}};function He(a,b){Ec.call(this);a&&Ie(this,a,b)}t(He,Ec);n=He.prototype;n.L=null;n.Dc=null;n.pd=null;n.Ec=null;n.ea=-1;n.Pa=-1;n.cd=!1; +var Je={3:13,12:144,63232:38,63233:40,63234:37,63235:39,63236:112,63237:113,63238:114,63239:115,63240:116,63241:117,63242:118,63243:119,63244:120,63245:121,63246:122,63247:123,63248:44,63272:46,63273:36,63275:35,63276:33,63277:34,63289:144,63302:45},Ke={Up:38,Down:40,Left:37,Right:39,Enter:13,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,"U+007F":46,Home:36,End:35,PageUp:33,PageDown:34,Insert:45},Le=C||E&&F("525"),Me=Ta&&D;n=He.prototype; +n.Je=function(a){E&&(17==this.ea&&!a.ctrlKey||18==this.ea&&!a.altKey||Ta&&91==this.ea&&!a.metaKey)&&(this.Pa=this.ea=-1);-1==this.ea&&(a.ctrlKey&&17!=a.keyCode?this.ea=17:a.altKey&&18!=a.keyCode?this.ea=18:a.metaKey&&91!=a.keyCode&&(this.ea=91));Le&&!Ee(a.keyCode,this.ea,a.shiftKey,a.ctrlKey,a.altKey)?this.handleEvent(a):(this.Pa=D?Ge(a.keyCode):a.keyCode,Me&&(this.cd=a.altKey))};n.Le=function(a){this.Pa=this.ea=-1;this.cd=a.altKey}; +n.handleEvent=function(a){var b=a.D,c,d,e=b.altKey;C&&"keypress"==a.type?(c=this.Pa,d=13!=c&&27!=c?b.keyCode:0):E&&"keypress"==a.type?(c=this.Pa,d=0<=b.charCode&&63232>b.charCode&&Fe(c)?b.charCode:0):B?(c=this.Pa,d=Fe(c)?b.keyCode:0):(c=b.keyCode||this.Pa,d=b.charCode||0,Me&&(e=this.cd),Ta&&(63==d&&224==c)&&(c=191));var g=c,h=b.keyIdentifier;c?63232<=c&&c in Je?g=Je[c]:25==c&&a.shiftKey&&(g=9):h&&h in Ke&&(g=Ke[h]);a=g==this.ea;this.ea=g;b=new Ne(g,d,a,b);b.altKey=e;this.dispatchEvent(b)};n.j=m("L"); +function Ie(a,b,c){a.Ec&&a.detach();a.L=b;a.Dc=J(a.L,"keypress",a,c);a.pd=J(a.L,"keydown",a.Je,c,a);a.Ec=J(a.L,"keyup",a.Le,c,a)}n.detach=function(){this.Dc&&(K(this.Dc),K(this.pd),K(this.Ec),this.Ec=this.pd=this.Dc=null);this.L=null;this.Pa=this.ea=-1};n.S=function(){He.H.S.call(this);this.detach()};function Ne(a,b,c,d){d&&this.init(d,void 0);this.type="key";this.keyCode=a;this.charCode=b;this.repeat=c}t(Ne,ec);function Oe(){}da(Oe);Oe.prototype.Ue=0;Oe.ec();function Pe(a){Ec.call(this);this.Zb=a||zb();this.Ta=Qe}t(Pe,Ec);Pe.prototype.Ne=Oe.ec();var Qe=null;function Re(a,b){switch(a){case 1:return b?"disable":"enable";case 2:return b?"highlight":"unhighlight";case 4:return b?"activate":"deactivate";case 8:return b?"select":"unselect";case 16:return b?"check":"uncheck";case 32:return b?"focus":"blur";case 64:return b?"open":"close"}throw Error("Invalid component state");}n=Pe.prototype;n.Cc=null;n.va=!1;n.L=null;n.Ta=null;n.Re=null;n.Qa=null;n.vc=null; +n.ib=null;n.Ae=!1;function Se(a,b){if(a.Qa&&a.Qa.ib){var c=a.Qa.ib,d=a.Cc;d in c&&delete c[d];c=a.Qa.ib;if(b in c)throw Error('The object already contains the key "'+b+'"');c[b]=a}a.Cc=b}n.j=m("L");n.zc=function(){return this.xb||(this.xb=new Ac(this))};n.Fd=function(a){if(this.Qa&&this.Qa!=a)throw Error("Method not supported");Pe.H.Fd.call(this,a)};n.ae=m("Zb"); +n.Yb=function(a){if(this.va)throw Error("Component already rendered");if(a&&this.Xb(a)){this.Ae=!0;var b=H(a);this.Zb&&this.Zb.da==b||(this.Zb=zb(a));this.Wd(a);this.vb()}else throw Error("Invalid element to decorate");};n.Xb=aa(!0);n.Wd=function(a){this.L=a};n.vb=function(){this.va=!0;Te(this,function(a){!a.va&&a.j()&&a.vb()})};n.bc=function(){Te(this,function(a){a.va&&a.bc()});this.xb&&this.xb.Sa();this.va=!1}; +n.S=function(){this.va&&this.bc();this.xb&&(this.xb.ub(),delete this.xb);Te(this,function(a){a.ub()});!this.Ae&&this.L&&Jb(this.L);this.Qa=this.Re=this.L=this.ib=this.vc=null;Pe.H.S.call(this)};n.cc=m("L");n.Eb=function(a){if(this.va)throw Error("Component already rendered");this.Ta=a};function Te(a,b){a.vc&&y(a.vc,b,void 0)} +n.removeChild=function(a,b){if(a){var c=q(a)?a:a.Cc||(a.Cc=":"+(a.Ne.Ue++).toString(36)),d;this.ib&&c?(d=this.ib,d=(c in d?d[c]:void 0)||null):d=null;a=d;if(c&&a){d=this.ib;c in d&&delete d[c];z(this.vc,a);b&&(a.bc(),a.L&&Jb(a.L));c=a;if(null==c)throw Error("Unable to set parent component");c.Qa=null;Pe.H.Fd.call(c,null)}}if(!a)throw Error("Child is not in parent component");return a};var Ue;function Ve(a,b,c){ha(c)&&(c=c.join(" "));var d="aria-"+b;""===c||void 0==c?(Ue||(Ue={atomic:!1,autocomplete:"none",dropeffect:"none",haspopup:!1,live:"off",multiline:!1,multiselectable:!1,orientation:"vertical",readonly:!1,relevant:"additions text",required:!1,sort:"none",busy:!1,disabled:!1,hidden:!1,invalid:"false"}),c=Ue,b in c?a.setAttribute(d,c[b]):a.removeAttribute(d)):a.setAttribute(d,c)};function We(){}var Xe;da(We);n=We.prototype;n.cc=function(a){return a};n.$b=function(a,b,c){if(a=a.j?a.j():a)if(C&&!F("7")){var d=Ye(pb(a),b);d.push(b);ra(c?qb:sb,a).apply(null,d)}else c?qb(a,b):sb(a,b)};n.Xb=aa(!0); +n.Yb=function(a,b){b.id&&Se(a,b.id);var c=this.cc(b);c&&c.firstChild?Ze(a,c.firstChild.nextSibling?Ia(c.childNodes):c.firstChild):a.rb=null;var d=0,e=this.dc(),g=this.dc(),h=!1,k=!1,c=!1,l=pb(b);y(l,function(a){if(h||a!=e)if(k||a!=g){var b=d;if(!this.ye){this.wc||$e(this);var c=this.wc,l={},s;for(s in c)l[c[s]]=s;this.ye=l}a=parseInt(this.ye[a],10);d=b|(isNaN(a)?0:a)}else k=!0;else h=!0,g==e&&(k=!0)},this);a.N=d;h||(l.push(e),g==e&&(k=!0));k||l.push(g);var s=a.ta;s&&l.push.apply(l,s);if(C&&!F("7")){var w= +Ye(l);0c;b.style.borderWidth="10px";a.Bd=b.scrollHeight>d;b.style.height="100px";100!=b.offsetHeight&&(a.Gc=!0);Jb(b);a.ie=!0}var b=a.j(),c=a.j().scrollHeight,e=a.j(),d=e.offsetHeight-e.clientHeight;if(!a.Cd)var g=a.kc,d=d-(g.top+g.bottom); +a.Bd||(e=ed(e),d-=e.top+e.bottom);c+=0l?(wf(this,l),b.style.overflowY="",e=!0):h!=g?wf(this,g):this.Na||(this.Na=g);d||(e||!rf)||(a=!0)}else xf(this);this.Bb=!1;a&&(a=this.j(),this.Bb||(this.Bb=!0,(d=a.scrollHeight)?(e=vf(this),b=tf(this),g=uf(this),b&&e<=b||g&&e>=g||(g=this.kc,a.style.paddingBottom=g.bottom+1+"px",vf(this)==e&&(a.style.paddingBottom= +g.bottom+d+"px",a.scrollTop=0,d=vf(this)-d,d>=b?wf(this,d):wf(this,b)),a.style.paddingBottom=g.bottom+"px")):xf(this),this.Bb=!1));c!=this.Na&&this.dispatchEvent("resize")}};n.Te=function(){var a=this.j(),b=a.offsetHeight;a.filters&&a.filters.length&&(a=a.filters.item("DXImageTransform.Microsoft.DropShadow"))&&(b-=a.offX);b!=this.Na&&(this.Na=this.oe=b)};C&&F(8);function yf(a){return a&&a.gd&&a.gd===ee?a.content:String(a).replace(zf,Af)}var Bf={"\x00":"\x26#0;",'"':"\x26quot;","\x26":"\x26amp;","'":"\x26#39;","\x3c":"\x26lt;","\x3e":"\x26gt;","\t":"\x26#9;","\n":"\x26#10;","\x0B":"\x26#11;","\f":"\x26#12;","\r":"\x26#13;"," ":"\x26#32;","-":"\x26#45;","/":"\x26#47;","\x3d":"\x26#61;","`":"\x26#96;","\u0085":"\x26#133;","\u00a0":"\x26#160;","\u2028":"\x26#8232;","\u2029":"\x26#8233;"};function Af(a){return Bf[a]}var zf=/[\x00\x22\x26\x27\x3c\x3e]/g;function Cf(){return'\x3cdiv class\x3d"annotorious-popup top-left" style\x3d"position:absolute;z-index:1"\x3e\x3cdiv class\x3d"annotorious-popup-buttons"\x3e\x3cdiv\x3e\x3ca role\x3d"button" class\x3d"annotorious-popup-button annotorious-popup-button-edit" title\x3d"Edit"\x3eEDIT\x3c/a\x3e\x3ca role\x3d"button" class\x3d"annotorious-popup-button annotorious-popup-button-delete" title\x3d"Delete"\x3eDELETE\x3c/a\x3e\x3c/div\x3e\x3cdiv\x3e\x3ca role\x3d"button" class\x3d"annotorious-popup-button annotorious-popup-button-move" title\x3d"Move"\x3eMOVE\x3c/a\x3e\x3ca role\x3d"button" class\x3d"annotorious-popup-button annotorious-popup-button-rotate" title\x3d"Rotate"\x3eROTATE\x3c/a\x3e\x3c/div\x3e\x3c/div\x3e\x3cspan class\x3d"annotorious-popup-text"\x3e\x3c/span\x3e\x3c/div\x3e'} +function Df(){return'\x3cdiv class\x3d"annotorious-editor" style\x3d"position:absolute;z-index:1"\x3e\x3cform\x3e\x3ctextarea class\x3d"annotorious-editor-text" placeholder\x3d"Add a Comment..." tabindex\x3d"1"\x3e\x3c/textarea\x3e\x3cdiv class\x3d"annotorious-editor-button-container"\x3e\x3ca role\x3d"button" class\x3d"annotorious-editor-button annotorious-editor-button-cancel" tabindex\x3d"3"\x3eCancel\x3c/a\x3e\x3ca role\x3d"button" class\x3d"annotorious-editor-button annotorious-editor-button-save" tabindex\x3d"2"\x3eSave\x3c/a\x3e\x3c/div\x3e\x3c/form\x3e\x3c/div\x3e'} +;function Ef(a){this.element=ge(Df);this.d=a;this.Be=a.getItem();this.ha=new qf("");this.Rc=I(".annotorious-editor-button-cancel",this.element)[0];this.oc=I(".annotorious-editor-button-save",this.element)[0];this.Jd=Mb(this.oc);this.Nb=[];this.gb=!1;this.c={ac:!0,Ad:!1,Wa:{placeholder:"Add a Comment...",className:"annotorious-editor-text"},buttons:{save:{text:"Save",className:"annotorious-editor-button annotorious-editor-button-save"},abort:{text:"Cancel",className:"annotorious-editor-button annotorious-editor-button-cancel"}}, +Ca:void 0};this.g=JSON.parse(JSON.stringify(this.c));var b=this;J(this.Rc,"click",function(c){c.preventDefault();a.stopSelection(b.Qb);b.close()});J(this.oc,"click",function(c){c.preventDefault();if(!b.gb||!b.r.options[0].disabled||0!=b.r.selectedIndex&&b.r.value){c=b.Qb?{text:b.Qb.text,textId:b.Qb.textId}:void 0;var d=b.$d();a.P(d);a.stopSelection();b.Qb?a.fireEvent("onAnnotationUpdated",d,c):a.fireEvent("onAnnotationCreated",d,a.getItem());b.close()}});O(this.element,!1);a.element.appendChild(this.element); +this.ha.Yb(I(".annotorious-editor-text",this.element)[0]);rd(this.element,function(){var a=b.ha;a.j()&&a.yb()})}n=Ef.prototype;n.Wb=function(a){var b=Fb("div","annotorious-editor-field");q(a)?b.innerHTML=a:r(a)?this.Nb.push({Y:b,kd:a}):Lb(a)&&b.appendChild(a);Ib(b,this.Jd)}; +n.open=function(a){this.d.fireEvent("beforeEditorShown",a);this.nb=this.Qb=a;O(this.element,!0);this.gb?this.c.ac?(this.r.classList.remove("d-none"),a&&-1Kc(d.ba,"opacity")&&N(d.ba,0.9);d.clearHideTimer()}),J(this.element,"mouseout",function(){N(d.ba,0);d.startHideTimer()}),a.addHandler("onMouseOutOfItem",function(){d.startHideTimer()}));N(this.ba,0);N(this.element,0);L(this.element,"pointer-events","none");a.element.appendChild(this.element)}n=Jf.prototype;n.Wb=function(a){var b=Fb("div","annotorious-popup-field");q(a)?b.innerHTML=a:r(a)?this.Nb.push({Y:b,kd:a}):Lb(a)&&b.appendChild(a);this.element.appendChild(b)}; +n.startHideTimer=function(){this.Uc=!1;if(!this.Tb){var a=this;this.Tb=window.setTimeout(function(){a.d.fireEvent("beforePopupHide",a);a.Uc||(N(a.element,0),L(a.element,"pointer-events","none"),N(a.ba,0.9),delete a.Tb)},150)}};n.clearHideTimer=function(){this.Uc=!0;this.Tb&&(window.clearTimeout(this.Tb),delete this.Tb)}; +n.show=function(a,b){this.clearHideTimer();b&&this.setPosition(b);a&&this.setAnnotation(a);this.Tc&&window.clearTimeout(this.Tc);N(this.ba,0.9);if(Kf){var c=this;this.Tc=window.setTimeout(function(){N(c.ba,0)},1E3)}N(this.element,0.9);L(this.element,"pointer-events","auto");this.d.fireEvent("onPopupShown",this.h)};n.setPosition=function(a){Mc(this.element,new G(a.x,a.y))}; +n.setAnnotation=function(a){this.h=a;this.De.innerHTML=a.text?a.text.replace(/\n/g,"\x3cbr/\x3e"):'\x3cspan class\x3d"annotorious-popup-empty"\x3eNo comment\x3c/span\x3e';"editable"in a&&!1==a.editable?O(this.ba,!1):O(this.ba,!0);!this.c.se||a.shapes[0].type!=T||"movable"in a&&!1==a.movable?O(this.Ya,!1):O(this.Ya,!0);!this.c.te||a.shapes[0].type!=T||"rotable"in a&&!1==a.rotable?O(this.Za,!1):O(this.Za,!0);y(this.Nb,function(b){var c=b.kd(a);q(c)?b.Y.innerHTML=c:Lb(c)&&(Hb(b.Y),b.Y.appendChild(c))})}; +n.G=function(a){a instanceof Object&&0!==Object.keys(a).length?(a.hasOwnProperty("extraFields")&&(this.c.Ca=a.extraFields),a.hasOwnProperty("showMoveButton")&&(this.c.se=a.showMoveButton),a.hasOwnProperty("showRotateButton")&&(this.c.te=a.showRotateButton)):this.c=JSON.parse(JSON.stringify(this.g));(a=I(".annotorious-popup-field",this.element)[0])&&a.remove();if(this.c.Ca&&Array.isArray(this.c.Ca)){var b=this;y(this.c.Ca,function(a){b.Wb(a)})}};Jf.prototype.addField=Jf.prototype.Wb;function Lf(){}n=Lf.prototype;n.P=function(a,b){this.k.P(a,b)};n.fc=function(a,b){var c=this,d="pixel"==a.shapes[0].units?U(a.shapes[0],function(a){return Mf(c,a)}):U(a.shapes[0],function(a){return c.La(a)}),d=Rd(d);return this.ia(d.x,d.y).filter(function(c){return V(c.shapes[0])!=V(a.shapes[0])&&(!b||c.shapes[0].type==b)})};n.addHandler=function(a,b){this.O.addHandler(a,b)};n.ya=function(a){this.O.ya(a)};n.fireEvent=function(a,b,c){return this.O.fireEvent(a,b,c)};n.Ma=m("Q");n.J=function(a){this.k.J(a)}; +n.T=function(a){this.k.T(a)};n.ya=function(a,b){this.O.ya(a,b)};n.stopSelection=function(a){Kf&&O(this.f,!1);this.Vb&&(this.Vb(),delete this.Vb);this.Q.stopSelection();a&&this.k.P(a)}; +function Nf(a,b){J(b,Of,function(c){c=Pf(c,b);a.k.J(!1);var d=void 0;if(a.Ub){if(a.Ld){d=a.k.ia(c.x,c.y);if(!d.length)return;d=d.filter(function(a){return a.shapes[0].type==T});if(!d.length)return}O(a.f,!0);a.Q.startSelection(c.x,c.y,d?a.k.C[V(d[0].shapes[0])]:void 0)}else d=a.k.ia(c.x,c.y),0this.V.x?(a=this.W.x,b=this.V.x):(a=this.V.x,b=this.W.x);var c,d;this.W.y>this.V.y?(c=this.V.y,d=this.W.y):(c=this.W.y,d=this.V.y);return{top:c,right:a,bottom:d,left:b}}; +n.drawShape=function(a,b,c){var d=b.geometry,e,g,h,k,l;b.style||(b.style={});if(b.type==T){c?(g=b.style.hiFill||this.c.hiFill,e=b.style.hiStroke||this.c.hiStroke,h=b.style.hiOutline||this.c.hiOutline,k=b.style.hiOutlineWidth||this.c.hiOutlineWidth,l=b.style.hiStrokeWidth||this.c.hiStrokeWidth):(g=b.style.fill||this.c.fill,e=b.style.stroke||this.c.stroke,h=b.style.outline||this.c.outline,k=b.style.outlineWidth||this.c.outlineWidth,l=b.style.strokeWidth||this.c.strokeWidth);var s=Object.assign({},d); +void 0!=d.rotation&&(!b.mask||b.mask&&b.hasOwnProperty("_loadedMask"))&&(a.save(),a.beginPath(),a.translate(d.x+d.width/2,d.y+d.height/2),a.rotate(d.rotation*Math.PI/180));if(b.mask){if(!b.hasOwnProperty("_loadedMask")||b._loadedMask.url!=b.mask){Object.defineProperty(b,"_loadedMask",{enumerable:!1,writable:!0});var w=this;b._loadedMask={gc:new Image,url:b.mask};b._loadedMask.gc.onload=function(){w.drawShape(a,b,c)};b._loadedMask.gc.src=b.mask}if(b._loadedMask.gc){a.globalAlpha=b.style.maskTransparency|| +this.c.maskTransparency;void 0!=d.rotation?a.drawImage(b._loadedMask.gc,-(d.width/2),-(d.height/2),d.width,d.height):a.drawImage(b._loadedMask.gc,d.x,d.y,d.width,d.height);a.globalAlpha=1;if(void 0!=b.style.maskBorder&&!b.style.maskBorder||void 0==b.style.maskBorder&&!this.c.maskBorder)return;d=void 0!=d.rotation?{x:d.width/2+l+k,y:d.height/2+l+k,width:d.width+l+k,height:d.height+l+k,rotation:d.rotation}:{x:d.x-l-k,y:d.y-l-k,width:d.width+l+k,height:d.height+l+k,rotation:d.rotation}}}void 0!=d.rotation&& +(!b.mask||b.mask&&b.hasOwnProperty("_loadedMask"))&&(s=Object.assign({},d),d.x*=-1,d.y*=-1);a.lineJoin="round";a.lineWidth=k;a.strokeStyle=h;a.strokeRect(d.x+k/2,d.y+k/2,d.width-k,d.height-k);a.lineJoin="miter";a.lineWidth=l;a.strokeStyle=e;a.strokeRect(d.x+k+l/2,d.y+k+l/2,d.width-2*k-l,d.height-2*k-l);g&&(a.lineJoin="miter",a.lineWidth=l,a.fillStyle=g,a.fillRect(d.x+k+l/2,d.y+k+l/2,d.width-2*k-l,d.height-2*k-l));void 0!=d.rotation&&(a.restore(),d.x=s.x,d.y=s.y)}else"point"==b.type&&(g=b.style.fill|| +this.c.fill,l=b.style.strokeWidth||this.c.strokeWidth,a.beginPath(),a.fillStyle=g,a.arc(d.x,d.y,l,0,l*Math.PI,!1),a.fill())};function Wf(a,b,c,d,e){if(c.type==T)return d=new S(d,e),e=c.geometry,e.x=d.x-e.width/2,e.y=d.y-e.height/2,a.drawShape(b,c),c}function Xf(a,b,c,d,e){if(c.type==T){d=new S(d,e);e=c.geometry;var g=new S(e.x+e.width/2,e.y+e.height/2);e.rotation=180*Math.atan2(d.y-g.y,d.x-g.x)/Math.PI;a.drawShape(b,c);return c}};function Td(a,b){this.arrowTail=a;this.arrowHead=b}function Od(a){var b,c;a.arrowHead.x>a.arrowTail.x?(b=a.arrowHead.x,c=a.arrowTail.x):(b=a.arrowTail.x,c=a.arrowHead.x);var d;a.arrowHead.y>a.arrowTail.y?(d=a.arrowTail.y,a=a.arrowHead.y):(d=a.arrowHead.y,a=a.arrowTail.y);return new Ld(c,d,b-c,a+5-d)};function dg(){}n=dg.prototype;n.init=function(a,b){this.n=b;this.d=a;this.o=b.getContext("2d");this.ab=!1;this.c={arrowStroke:"#ffffff",arrowStrokeWidth:2,hiArrowStroke:"#fff000",hiArrowStrokeWidth:2.2,highlightTail:void 0,hiTailRadius:5,highlightHead:void 0,hiHeadRadius:5};this.g=Object.assign({},this.c)}; +n.Hb=function(a,b){var c=this,d=this.n;this.cb=J(this.n,Rf,function(a){var g=Pf(a,d);!c.ab||b&&!Nd(b,g.x,g.y)||(c.ga=new S(g.x,g.y),c.o.clearRect(0,0,d.width,d.height),g=Sf(c.d,g),c.d.fireEvent("onMouseMoveOverItem",{cursor:g},a),c.drawShape(c.o,{type:"arrow",geometry:new Td(c.pa,c.ga),style:{}}))});this.eb=J(d,Yf,function(a){var b=Pf(a,d),h=c.getShape();a=a.D?a.D:a;c.ab=!1;h?(c.$a(),c.d.fireEvent("onSelectionCompleted",{mouseEvent:a,shape:h,viewportBounds:c.getViewportBounds()})):(c.d.fireEvent("onSelectionCanceled"), +a=c.d.ia(b.x,b.y),0this.pa.x?(a=this.ga.x,b=this.pa.x):(a=this.pa.x,b=this.ga.x);var c,d;this.ga.y>this.pa.y?(c=this.pa.y,d=this.ga.y):(c=this.ga.y,d=this.pa.y);return{top:c,right:a,bottom:d+5,left:b}}; +n.drawShape=function(a,b,c){b.style||(b.style={});if("arrow"==b.type){var d=b.geometry;c?(a.strokeStyle=a.fillStyle=b.style.hiArrowStroke||this.c.hiArrowStroke,a.lineWidth=b.style.hiArrowStrokeWidth||this.c.hiArrowStrokeWidth):(a.strokeStyle=a.fillStyle=b.style.arrowStroke||this.c.arrowStroke,a.lineWidth=b.style.arrowStrokeWidth||this.c.arrowStrokeWidth);c=Math.PI/5;var e=3*a.lineWidth,g=Math.sqrt((d.arrowHead.x-d.arrowTail.x)*(d.arrowHead.x-d.arrowTail.x)+(d.arrowHead.y-d.arrowTail.y)*(d.arrowHead.y- +d.arrowTail.y)),h=(g-e/3)/g,g=Math.round(d.arrowTail.x+(d.arrowHead.x-d.arrowTail.x)*h),h=Math.round(d.arrowTail.y+(d.arrowHead.y-d.arrowTail.y)*h);a.beginPath();a.moveTo(d.arrowTail.x,d.arrowTail.y);a.lineTo(g,h);a.stroke();var k=Math.atan2(d.arrowHead.y-d.arrowTail.y,d.arrowHead.x-d.arrowTail.x),g=Math.abs(e/Math.cos(c)),h=k+Math.PI+c,e=d.arrowHead.x+Math.cos(h)*g,h=d.arrowHead.y+Math.sin(h)*g,k=k+Math.PI-c;c=d.arrowHead.x+Math.cos(k)*g;g=d.arrowHead.y+Math.sin(k)*g;a.save();a.beginPath();a.moveTo(e, +h);a.lineTo(d.arrowHead.x,d.arrowHead.y);a.lineTo(c,g);a.beginPath();a.moveTo(e,h);a.lineTo(d.arrowHead.x,d.arrowHead.y);a.lineTo(c,g);a.lineTo(e,h);a.fill();if(b.style.highlightTail||this.c.highlightTail)a.beginPath(),a.arc(d.arrowTail.x,d.arrowTail.y,b.style.hiTailRadius||this.c.hiTailRadius,0,2*Math.PI,!1),a.fillStyle=b.style.highlightTail||this.c.highlightTail,a.fill();if(b.style.highlightHead||this.c.highlightHead)a.beginPath(),a.arc(d.arrowHead.x,d.arrowHead.y,b.style.hiHeadRadius||this.c.hiHeadRadius, +0,2*Math.PI,!1),a.fillStyle=b.style.highlightHead||this.c.highlightHead,a.fill();a.restore()}};function eg(a){return'\x3ccanvas class\x3d"annotorious-item annotorious-opacity-fade" style\x3d"position:absolute; top:0px; left:0px; width:'+yf(a.width)+"px; height:"+yf(a.height)+'px; z-index:0" width\x3d"'+yf(a.width)+'" height\x3d"'+yf(a.height)+'"\x3e\x3c/canvas\x3e'} +function Hf(a){return'\x3cdiv class\x3d"annotorious-hint" style\x3d"white-space:nowrap; position:absolute; top:0px; left:0px; pointer-events:none;"\x3e\x3cdiv class\x3d"annotorious-hint-msg annotorious-opacity-fade"\x3e'+yf(a.Fc)+'\x3c/div\x3e\x3cdiv class\x3d"annotorious-hint-icon" style\x3d"pointer-events:auto"\x3e\x3c/div\x3e\x3c/div\x3e'};function fg(a,b){this.F=a;this.Rd={padding:a.style.padding,margin:a.style.margin};this.Kd=void 0;this.Lc="fraction";this.O=new td;this.Ja=[];this.Ub=!0;this.Ld=!1;this.I={enabled:!1,hd:!1,color:"#ffffff",strokeWidth:2};this.Lb=Object.assign({},this.I);this.element=Fb("div","annotorious-annotationlayer");L(this.element,"position","relative");L(this.element,"display","inline-block");gg(a,this.element);Kb(this.element,a);this.element.appendChild(a);var c=Xc(a);this.Vc=ge(eg,{width:c.width,height:c.height}); +this.element.appendChild(this.Vc);this.za=ge(eg,{width:c.width,height:c.height});Kf&&qb(this.za,"annotorious-item-unfocus");this.element.appendChild(this.za);this.f=ge(eg,{width:c.width,height:c.height});Kf&&O(this.f,!1);this.element.appendChild(this.f);this.popup=b?b:new Jf(this);c=new bg;this.tc(c);this.tc(new dg);this.Q=c;this.editor=new Ef(this);this.k=new Qf(this.za,this);this.Pb=new Gf(this,this.element);var d=this;Kf&&(J(this.element,$f,function(a){a=a.relatedTarget;a&&Nb(d.element,a)||(d.O.fireEvent("onMouseOverItem"), +ub(d.za,"annotorious-item-unfocus","annotorious-item-focus"))}),J(this.element,ag,function(a){a=a.relatedTarget;a&&Nb(d.element,a)||(d.O.fireEvent("onMouseOutOfItem"),ub(d.za,"annotorious-item-focus","annotorious-item-unfocus"))}));Nf(this,Zf?this.f:this.za);this.O.addHandler("onSelectionCompleted",function(a){var b=a.viewportBounds;d.editor.setPosition(new S(b.left+d.F.offsetLeft,b.bottom+4+d.F.offsetTop));d.editor.open(!1,a)});this.O.addHandler("onSelectionCanceled",function(){Kf&&O(d.f,!1);d.Q.stopSelection()})} +t(fg,Lf);function gg(a,b){function c(c,d){L(b,"margin-"+c,d+"px");L(a,"margin-"+c,0);L(a,"padding-"+c,0)}var d=bd(a,"margin"),e=bd(a,"padding");0==d.top&&0==e.top||c("top",d.top+e.top);0==d.right&&0==e.right||c("right",d.right+e.right);0==d.bottom&&0==e.bottom||c("bottom",d.bottom+e.bottom);0==d.left&&0==e.left||c("left",d.left+e.left)}n=fg.prototype;n.Aa=f();n.tc=function(a){a.init(this,this.f);this.Ja.push(a)}; +n.destroy=function(){var a=this.F;a.style.margin=this.Rd.margin;a.style.padding=this.Rd.padding;Kb(a,this.element)}; +n.jd=function(a){this.k.T(a);var b=Ga(this.Ja,function(b){b=b.getSupportedShapeType();return Array.isArray(b)?-1!=Ca(b,a.shapes[0].type):b==a.shapes[0].type}),c=a.shapes[0],d=this,c="pixel"==c.units?U(c,function(a){return Mf(d,a)}):U(c,function(a){return d.La(a)});if(b){O(this.f,!0);this.k.J(!1);var e=this.f.getContext("2d");b.drawShape(e,c)}b=Qd(c).geometry;b=new S(b.x,b.y+b.height);this.editor.setPosition(new S(b.x+this.F.offsetLeft,b.y+4+this.F.offsetTop));this.editor.open(a)};n.ud=function(a){this.k.ud(a)}; +n.zd=function(a){this.k.zd(a)};n.Ma=m("Q");n.M=function(){return this.k.M()};n.ia=function(a,b){return Ia(this.k.ia(a,b))};n.Da=m("Ja");n.getItem=function(){return{src:hg(this.F),element:this.F}};function hg(a){var b=a.getAttribute("data-original");return b?b:a.src}n.Ea=function(){O(this.za,!1)};n.Z=function(){this.Ub=!1;this.Pb&&(this.Pb.destroy(),delete this.Pb)}; +n.Oc=function(a){this.Q=Ga(this.Ja,function(b){return b.getName()==a});return this.Q?!0:(console.log('WARNING: selector "'+a+'" not available'),!1)}; +n.G=function(a){a.hasOwnProperty("displayMessage")&&(this.Kd=a.displayMessage,this.Ub&&(this.Z(),this.fa()));a.hasOwnProperty("outputUnits")&&(this.Lc="pixel"==a.outputUnits?"pixel":"fraction");a.hasOwnProperty("drawInsideRectAnno")&&(this.Ld=a.drawInsideRectAnno);a.hasOwnProperty("colorMode")&&this.k.Dd(a.colorMode);a.hasOwnProperty("selectEditor")&&Ff(this.editor,a.selectEditor);a.hasOwnProperty("cursorAxes")&&ig(this,a.cursorAxes);a.hasOwnProperty("arrowMode")&&(a.arrowMode?this.Oc("arrow_drag"): +this.Oc("rect_drag"));a.hasOwnProperty("editor")&&this.editor.G(a.editor);a.hasOwnProperty("popup")&&this.popup.G(a.popup);a.hasOwnProperty("fancyBox")&&cg(Ga(this.Ja,function(a){return"rect_drag"==a.getName()}),a.fancyBox);a.hasOwnProperty("shapeStyle")&&(y(this.Ja,function(b){b.G(a.shapeStyle)}),Uf(this.k))};n.Va=function(){O(this.za,!0)};n.fa=function(){this.Ub=!0;this.Pb||(this.Pb=new Gf(this,this.element,this.Kd))};n.stopSelection=function(a){Kf&&O(this.f,!1);this.Q.stopSelection();a&&this.k.P(a)}; +n.La=function(a){var b=Vc(this.F),c={x:a.x*b.width,y:a.y*b.height};a.width&&(c.width=a.width*b.width,c.height=a.height*b.height);return c};function Mf(a,b){var c=Vc(a.F),d={x:parseInt(b.x/a.F.naturalWidth*c.width),y:parseInt(b.y/a.F.naturalHeight*c.height)};b.width&&(d.width=parseInt(b.width/a.F.naturalWidth*c.width),d.height=parseInt(b.height/a.F.naturalHeight*c.height));return d} +n.Ha=function(a){var b=Vc(this.F),c={x:a.x/b.width,y:a.y/b.height};a.width&&(c.width=a.width/b.width,c.height=a.height/b.height);return c};function Sf(a,b){var c=Vc(a.F),d={x:parseInt(b.x*a.F.naturalWidth/c.width),y:parseInt(b.y*a.F.naturalHeight/c.height)};b.width&&(d.width=parseInt(b.width*a.F.naturalWidth/c.width),d.height=parseInt(b.height*a.F.naturalHeight/c.height));return d} +function ig(a,b){b instanceof Object&&0!==Object.keys(b).length?(b.hasOwnProperty("enabled")&&(a.I.enabled=b.enabled||a.Lb.enabled),b.hasOwnProperty("dash")&&(a.I.hd=b.dash||a.Lb.hd),b.hasOwnProperty("color")&&(a.I.color=b.color||a.Lb.color),b.hasOwnProperty("strokeWidth")&&(a.I.strokeWidth=b.strokeWidth||a.Lb.strokeWidth)):a.I=Object.assign({R:a.I.R},a.Lb);if(a.I.enabled){if(!a.I.R){var c=a.Vc.getContext("2d"),d=function(b){b=Pf(b,a.ff);c.clearRect(0,0,c.canvas.width,c.canvas.height);a.I.hd?c.setLineDash([5, +3]):(c.setLineDash([]),c.lineCap="square");c.strokeStyle=a.I.color;c.lineWidth=a.I.strokeWidth;c.beginPath();c.moveTo(0,b.y);c.lineTo(c.canvas.width,b.y);c.moveTo(b.x,0);c.lineTo(b.x,c.canvas.height);c.stroke()};a.I.R=[];a.I.R[0]=J(a.za,Rf,d);a.I.R[1]=J(a.f,Rf,d)}}else a.I.R&&(y(a.I.R,function(a){K(a)}),c=a.Vc.getContext("2d"),c.clearRect(0,0,c.canvas.width,c.canvas.height),delete a.I.R)}fg.prototype.addSelector=fg.prototype.tc;fg.prototype.fireEvent=fg.prototype.fireEvent; +fg.prototype.setCurrentSelector=fg.prototype.Oc;fg.prototype.toItemCoordinates=fg.prototype.Ha;function jg(){Wd(this,function(){return I("img.annotatable",document)})}t(jg,Vd);jg.prototype.Ac=function(a){return hg(a)};jg.prototype.vd=function(a){return new fg(a)};jg.prototype.Pc=function(a){return Lb(a)?"IMG"==a.tagName:!1};function kg(a){return'\x3cdiv class\x3d"annotorious-opacity-fade" style\x3d"white-space:nowrap; position:absolute; pointer-events:none; top:80px; width:100%; text-align:center;"\x3e\x3cdiv class\x3d"annotorious-ol-hint" style\x3d"width: 400px; margin:0px auto;"\x3e'+yf(a.Fc)+"\x3c/div\x3e\x3c/div\x3e"};function lg(a,b){this.qa=a;this.d=b;this.ma=Xc(b.element);this.v=b.popup;L(this.v.element,"z-index",99E3);this.K=[];this.C=[];this.nc=new OpenLayers.Layer.Boxes("Annotorious");this.qa.addLayer(this.nc);var c=this;this.qa.events.register("move",this.qa,function(){c.B&&c.Rb()});b.addHandler("beforePopupHide",function(){c.bb==c.B?c.v.clearHideTimer():c.fb(c.bb,c.B)})}n=lg.prototype;n.destroy=function(){this.nc.destroy()}; +n.Rb=function(){var a=this.B.ic.div,b=Xc(a),c=Rc(a,this.qa.div),a=c.y,c=c.x,d=b.width,e=b.height,b=Xc(this.v.element),a={y:a+e+5};c+b.width>this.ma.width?(ub(this.v.element,"top-left","top-right"),a.x=c+d-b.width):(ub(this.v.element,"top-right","top-left"),a.x=c);0>a.x&&(a.x=0);a.x+b.width>this.ma.width&&(a.x=this.ma.width-b.width);a.y+b.height>this.ma.height&&(a.y=this.ma.height-b.height);this.v.setPosition(a)};n.ad=function(a){this.v.setAnnotation(a);this.Rb();this.v.show()}; +n.fb=function(a,b){a?(Rc(a.ic.div,this.qa.div),Kc(a.ic.div,"height"),L(a.hc,"border-color","#fff000"),this.B=a,this.ad(a.m)):delete this.B;b&&L(b.hc,"border-color","#fff")}; +n.P=function(a){var b=a.shapes[0].geometry,b=new OpenLayers.Marker.Box(new OpenLayers.Bounds(b.x,b.y,b.x+b.width,b.y+b.height));qb(b.div,"annotorious-ol-boxmarker-outer");L(b.div,"border",null);var c=Fb("div","annotorious-ol-boxmarker-inner");Uc(c,"100%","100%");b.div.appendChild(c);var d={m:a,ic:b,hc:c},e=this;J(c,"mouseover",function(){e.B||e.fb(d);e.bb=d});J(c,"mouseout",function(){delete e.bb;e.v.startHideTimer()});this.K.push(d);c=a.shapes[0];"pixel"!=c.units&&(c=U(c,function(a){return e.d.La(a)})); +this.C[V(a.shapes[0])]=c;La(this.K,function(a,b){return Pd(b.m.shapes[0])-Pd(a.m.shapes[0])});var g=1E4;y(this.K,function(a){L(a.ic.div,"z-index",g);g++});this.nc.addMarker(b)};n.T=function(a){var b=Ga(this.K,function(b){return b.m==a});b&&(z(this.K,b),this.nc.removeMarker(b.ic))};n.M=function(){return Ea(this.K,function(a){return a.m})};n.J=function(a){a||this.v.startHideTimer()};n.Gd=function(a,b){var c=this.ia(a,b);if(0this.ma.width&&(ub(this.v.element,"top-left","top-right"),a.x=b+d-c.width),0>a.x&&(a.x=0),a.x+c.width>this.ma.width&&(a.x=this.ma.width-c.width),a.y+c.height>this.ma.height&&(a.y=this.ma.height-c.height));this.v.setPosition(a)};n.ad=function(a){this.v.setAnnotation(a);this.Rb();this.v.show()}; +n.fb=function(a,b){a?(L(a.hc,"border-color","#fff000"),this.B=a,this.ad(a.m)):delete this.B;b&&L(b.hc,"border-color","#fff")}; +n.P=function(a){var b=a.shapes[0].geometry,c=Fb("div","annotorious-ol-boxmarker-outer"),d=Fb("div","annotorious-ol-boxmarker-inner");Uc(d,"100%","100%");c.appendChild(d);var b=new OpenSeadragon.Rect(b.x,b.y,b.width,b.height),e={m:a,Kc:c,hc:d},g=this;J(d,"mouseover",function(){g.B||g.fb(e);g.bb=e});J(d,"mouseout",function(){delete g.bb;g.v.startHideTimer()});this.K.push(e);La(this.K,function(a,b){return Pd(b.m.shapes[0])-Pd(a.m.shapes[0])});var h=1;y(this.K,function(a){L(a.Kc,"z-index",h);h++});this.na.addOverlay(c, +b)};n.T=function(a){var b=Ga(this.K,function(b){return b.m==a});b&&(z(this.K,b),this.na.removeOverlay(b.Kc))};n.M=function(){return Ea(this.K,function(a){console.log(a);return a.m})};n.J=function(a){var b=this;a?y(this.K,function(c){c.m==a&&(b.B&&b.B!=c?b.fb(c,b.B):b.fb(c))}):this.v.startHideTimer()};n.destroy=function(){var a=this;y(this.K,function(b){a.na.removeOverlay(b.Kc)});this.K=[]};function pg(a){this.element=a.element;L(Bb(),"z-index",0);this.na=a;this.O=new td;this.Ja=[];this.Ub=!0;this.oa=ge(kg,{Fc:"Click and Drag"});N(this.oa,0);this.element.appendChild(this.oa);this.popup=new Jf(this);this.k=new og(a,this);this.f=ge(eg,{width:"0",height:"0"});O(this.f,!1);this.element.appendChild(this.f);var b=this;(function(){var a=parseInt(M(b.element,"width"),10),d=parseInt(M(b.element,"height"),10);Uc(b.f,a,d);b.f.width=a;b.f.height=d})();a=new bg;a.init(this,this.f);this.Ja.push(a); +this.Q=a;this.editor=new Ef(this);Nf(this,this.f);this.O.addHandler("onSelectionCompleted",function(a){a=a.viewportBounds;b.editor.setPosition(new S(a.left,a.bottom+4));b.editor.open()});this.O.addHandler("onSelectionCanceled",function(){b.stopSelection()})}t(pg,Lf);n=pg.prototype;n.fa=f();n.Z=f();n.destroy=function(){this.k.destroy();delete this.k};n.Aa=function(a){L(this.f,"pointer-events","auto");var b=this;O(this.f,!0);N(this.oa,0.8);window.setTimeout(function(){N(b.oa,0)},2E3);a&&(this.Vb=a)}; +n.jd=function(a){this.k.T(a);var b=this.Q,c=this;if(b){O(this.f,!0);this.k.J(void 0);var d=this.f.getContext("2d"),e=U(a.shapes[0],function(a){return c.La(a)});b.drawShape(d,e);b=Qd(e).geometry;this.editor.setPosition(new S(b.x,b.y+b.height+4));this.editor.open(a)}}; +n.La=function(a){var b=pd(this.element);b.top+=window.pageYOffset;b.left+=window.pageXOffset;var c=new OpenSeadragon.Point(a.x,a.y);a=a.width?new OpenSeadragon.Point(a.x+a.width,a.y+a.height):!1;c=this.na.viewport.viewportToWindowCoordinates(c);return a?(a=this.na.viewport.viewportToWindowCoordinates(a),{x:c.x-b.left,y:c.y-b.top,width:a.x-c.x+2,height:a.y-c.y+2}):c};n.M=function(){return this.k.M()};n.Da=f();n.getItem=function(){return{src:"dzi://openseadragon/something"}};n.lc=f();n.Ma=m("Q"); +n.Ha=function(a){var b=pd(this.element);b.top+=window.pageYOffset;b.left+=window.pageXOffset;var c=new OpenSeadragon.Point(a.x+b.left,a.y+b.top);a=a.width?new OpenSeadragon.Point(a.x+b.left+a.width-2,a.y+b.top+a.height-2):!1;c=this.na.viewport.windowToViewportCoordinates(c);return a?(a=this.na.viewport.windowToViewportCoordinates(a),{x:c.x,y:c.y,width:a.x-c.x,height:a.y-c.y}):c};function qg(){Wd(this)}t(qg,Vd);qg.prototype.Ac=aa("dzi://openseadragon/something");qg.prototype.vd=function(a){return new pg(a)};qg.prototype.Pc=function(a){return a instanceof OpenSeadragon.ef};function Y(){this.Nd=!1;this.t=[new jg];window.OpenLayers&&this.t.push(new ng);window.OpenSeadragon&&this.t.push(new qg);this.Sb=[];var a=this;qd(function(){rg(a)})}function rg(a){a.Nd||(y(a.t,function(a){a.init()}),y(a.Sb,function(b){b.initPlugin&&b.initPlugin(a);y(a.t,function(a){a.sc(b)})}),J(window,"resize",function(){a.reload()}),a.Nd=!0)}function Z(a,b){return Ga(a.t,function(a){return be(a,b)})}n=Y.prototype; +n.Aa=function(a,b){var c=void 0,d=void 0;q(a)?(c=a,d=b):r(a)&&(d=a);if(c){var e=Z(this,c);e&&e.Aa(c,d)}else y(this.t,function(a){a.Aa(d)})}; +n.P=function(a,b){if(a.src)if(a.shapes){var c=new Ud(sd(a.src),a.text,a.shapes[0],a.created_at,a.textId,a.id);c.editable=a.editable;c.movable=a.movable;c.rotable=a.rotable;var d=Z(this,c.src);d&&d.P(c,b)}else console.error("Error: The shapes attribute is required and must necessarily include the type and geometry field. See the documentation for more details.",a);else console.error("Error: The src attribute is required for identification the image associated to annotation. See the documentation for more details.", +a)};n.Fe=function(a){if(Array.isArray(a)&&a.length){var b=this;a.forEach(function(a){b.P(a)})}};n.addHandler=function(a,b){y(this.t,function(c){c.addHandler(a,b)})};n.ya=function(a,b){y(this.t,function(c){c.ya(a,b)})};n.sc=function(a,b){try{var c=new window.annotorious.plugin[a](b);"complete"==document.readyState?(c.initPlugin&&c.initPlugin(this),y(this.t,function(a){a.sc(c)})):this.Sb.push(c)}catch(d){console.log("Could not load plugin: "+a)}}; +n.destroy=function(a){if(a){var b=Z(this,a);b&&b.destroy(a)}else y(this.t,function(a){a.destroy()})};n.Ma=function(a){var b=Z(this,a);if(b)return b.Ma(a)};n.M=function(a){if(a){var b=Z(this,a);return b?b.M(a):[]}var c=[];y(this.t,function(a){Ja(c,a.M())});return c};n.Da=function(a){var b=Z(this,a);return b?b.Da(a):[]};n.Ea=function(a){if(a){var b=Z(this,a);b&&b.Ea(a)}else y(this.t,function(a){a.Ea()})};n.Z=function(a){if(a){var b=Z(this,a);b&&b.Z(a)}else y(this.t,function(a){a.Z()})}; +n.stopSelection=function(a){if(a){var b=Z(this,a);b&&b.stopSelection(a)}else y(this.t,function(a){a.stopSelection()})};n.J=function(a){if(a){var b=Z(this,a.src);b&&b.J(a)}else y(this.t,function(a){a.J()})};n.rd=function(a){rg(this);var b=Ga(this.t,function(b){return b.Pc(a)});if(b)b.rd(a);else throw"Error: Annotorious does not support this media type in the current version or build configuration.";};n.Sa=function(a){var b=this;y(this.M(a),function(a){b.T(a)})}; +n.T=function(a){var b=Z(this,a.src);b&&b.T(a)};n.reload=function(a){y(this.t,function(b){var c=b.M();a&&de(b);b.destroy();b.init();y(c,function(a){b.P(a)})})};n.reset=function(){y(this.t,function(a){de(a);a.destroy();a.init()})};n.lc=function(a,b){var c=Z(this,a);c&&c.lc(a,b)};n.G=function(a){y(this.t,function(b){b.G(a)})};n.Dd=function(a,b,c,d,e){this.G({colorMode:{enabled:a,insideAnno:b,mode:c,color:d,strokeWidth:e}})}; +n.df=function(a,b,c,d){this.G({selectEditor:{enabled:a,options:b,emptyOption:c,customLabel:d}})};n.af=function(a,b,c,d){this.G({cursorAxes:{enabled:a,dash:b,color:c,strokeWidth:d}})};n.Ze=function(a){this.G({arrowMode:a})};n.cf=function(a){this.G({fancyBox:a})};n.fc=function(a,b){var c=Z(this,sd(a.src));return c?c.fc(a,b):[]};n.$e=function(a){a?this.fa(void 0):this.Z(void 0)};n.Va=function(a){if(a){var b=Z(this,a);b&&b.Va(a)}else y(this.t,function(a){a.Va()})}; +n.fa=function(a){if(a){var b=Z(this,a);b&&b.fa(a)}else y(this.t,function(a){a.fa()})};window.anno=new Y;Y.prototype.activateSelector=Y.prototype.Aa;Y.prototype.addAnnotation=Y.prototype.P;Y.prototype.addHandler=Y.prototype.addHandler;Y.prototype.addPlugin=Y.prototype.sc;Y.prototype.destroy=Y.prototype.destroy;Y.prototype.getActiveSelector=Y.prototype.Ma;Y.prototype.getAnnotations=Y.prototype.M;Y.prototype.getAvailableSelectors=Y.prototype.Da;Y.prototype.hideAnnotations=Y.prototype.Ea;Y.prototype.hideSelectionWidget=Y.prototype.Z;Y.prototype.highlightAnnotation=Y.prototype.J; +Y.prototype.makeAnnotatable=Y.prototype.rd;Y.prototype.removeAll=Y.prototype.Sa;Y.prototype.removeAnnotation=Y.prototype.T;Y.prototype.reset=Y.prototype.reset;Y.prototype.setActiveSelector=Y.prototype.lc;Y.prototype.setProperties=Y.prototype.G;Y.prototype.showAnnotations=Y.prototype.Va;Y.prototype.showSelectionWidget=Y.prototype.fa;window.annotorious||(window.annotorious={});window.annotorious.plugin||(window.annotorious.plugin={}); +window.annotorious.geometry||(window.annotorious.geometry={},window.annotorious.geometry.expand=Sd,window.annotorious.geometry.getBoundingRect=Qd);Y.prototype.setSelectionEnabled=Y.prototype.$e;Y.prototype.reload=Y.prototype.reload;Y.prototype.addAnnotations=Y.prototype.Fe;Y.prototype.setColorMode=Y.prototype.Dd;Y.prototype.useSelectEditor=Y.prototype.df;Y.prototype.showCursorAxes=Y.prototype.af;Y.prototype.setArrowMode=Y.prototype.Ze;Y.prototype.useFancyBox=Y.prototype.cf; +Y.prototype.getIntersectedAnnotations=Y.prototype.fc; diff --git a/css/annotorious.css b/css/annotorious.css index d4eba46..46fd310 100644 --- a/css/annotorious.css +++ b/css/annotorious.css @@ -92,7 +92,7 @@ .annotorious-popup-buttons { float:right; margin:0px 0px 1px 10px; - height:16px; + height:auto; -moz-transition-property: opacity; -moz-transition-duration: 1s; @@ -132,13 +132,30 @@ } .annotorious-popup-button:hover { - background-color:transparent; + background-color:transparent; + cursor: pointer; } .annotorious-popup-button-active { opacity:0.9; } +.annotorious-popup-button-move { + background:url(move.png); + width:16px; + height:16px; + text-indent:100px; + overflow:hidden; +} + +.annotorious-popup-button-rotate { + background:url(rotate.png); + width:16px; + height:16px; + text-indent:100px; + overflow:hidden; +} + .annotorious-popup-button-edit { background:url(pencil.png); width:16px; @@ -187,10 +204,8 @@ line-height: normal; background-color:#fff; width:240px; - height:50px; + height:auto; outline:none; - font-family:Verdana, Arial; - font-size:11px; padding:4px; margin:0px; color:#000; @@ -282,4 +297,8 @@ canvas.hidden { html.hasTouch .annotator-viewer li .annotator-controls, html.hasTouch .annotator-viewer li .annotator-controls { opacity: 1; +} + +.d-none{ + display: none !important; } \ No newline at end of file diff --git a/css/move.png b/css/move.png new file mode 100644 index 0000000..614df4a Binary files /dev/null and b/css/move.png differ diff --git a/css/rotate.png b/css/rotate.png new file mode 100644 index 0000000..aad4dad Binary files /dev/null and b/css/rotate.png differ diff --git a/css/theme-dark/annotorious-dark.css b/css/theme-dark/annotorious-dark.css index 66c0fb1..7704470 100644 --- a/css/theme-dark/annotorious-dark.css +++ b/css/theme-dark/annotorious-dark.css @@ -169,6 +169,10 @@ transition-delay:0; } +.annotorious-popup-button:hover{ + cursor: pointer; +} + .annotorious-popup-button-delete:hover { background-color:transparent; background:url(DarkSprite.png) no-repeat; @@ -193,6 +197,22 @@ background-position:0 -99px; } +.annotorious-popup-button-move { + background:url(move.png); + width:16px; + height:16px; + text-indent:100px; + overflow:hidden; +} + +.annotorious-popup-button-rotate { + background:url(rotate.png); + width:16px; + height:16px; + text-indent:100px; + overflow:hidden; +} + .annotorious-popup-field { margin:0px; padding:6px; @@ -236,7 +256,7 @@ line-height:150%; margin:10px 0px 4px 0px; padding:0px 10px; - min-height:50px; + height:auto; width:100%; min-width:200px; outline:none; @@ -343,3 +363,7 @@ -webkit-border-radius: 5px; -khtml-border-radius: 5px; } + +.d-none{ + display: none !important; +} \ No newline at end of file diff --git a/css/theme-dark/move.png b/css/theme-dark/move.png new file mode 100644 index 0000000..614df4a Binary files /dev/null and b/css/theme-dark/move.png differ diff --git a/css/theme-dark/rotate.png b/css/theme-dark/rotate.png new file mode 100644 index 0000000..aad4dad Binary files /dev/null and b/css/theme-dark/rotate.png differ diff --git a/externs/api.externs.js b/externs/api.externs.js index aa2df86..d014257 100644 --- a/externs/api.externs.js +++ b/externs/api.externs.js @@ -3,69 +3,117 @@ /** * Annotorious annotation interface. */ -var Annotation = { +var Annotation = { + + /** @type {integer} annotation id **/ + id: {}, /** @type {string} source URL of the annotated object (e.g. image) **/ - src : {}, - + src: {}, + /** @type {string} source URL of the HTML document containing the annotated object **/ - context : {}, - + context: {}, + /** @type {string} annotation text **/ - text : {}, + text: {}, + + /** @type {integer} annotation text id **/ + textId: {}, /** @type {boolean} flag indicating whether the anntotation is edit-/deletable **/ - editable : {}, - + editable: {}, + + /** @type {boolean} flag indicating whether the anntotation is movable **/ + movable: {}, + + /** @type {boolean} flag indicating whether the anntotation is rotable **/ + rotable: {}, + + /** @type {Date} the timestamp of creation **/ + created_at: {}, + /** @type {Object} the annotation shape **/ - shapes : [{ - + shapes: [{ + /** @type {string} the annotation shape type (e.g. rect, point, polygon) **/ - type : {}, + type: {}, /** @type {string} measurement units used for the geometry (e.g. 'pixel', 'fraction') **/ - units : {}, - + units: {}, + /** @type {Object} the shape geometry **/ - geometry : {}, - + geometry: {}, + + /** @type {string} the annotation mask url - only if type is 'rect' **/ + mask: {}, + /** @type {Object} the shape style **/ style: { - + /** @type {string} outline color **/ outline: {}, /** @type {number} outline width **/ - outline_width: {}, - + outlineWidth: {}, + /** @type {string} outline color when highlighted **/ - hi_outline: {}, + hiOutline: {}, /** @type {number} outline width when hightlighted **/ - hi_outline_width: {}, - + hiOutlineWidth: {}, + /** @type {string} stroke color **/ stroke: {}, /** @type {number} stroke width **/ - stroke_width: {}, + strokeWidth: {}, /** @type {string} stroke color when highlighted **/ - hi_stroke: {}, + hiStroke: {}, /** @type {number} stroke width when highlighted **/ - hi_stroke_width: {}, - + hiStrokeWidth: {}, + /** @type {string} fill color **/ fill: {}, - + /** @type {string} fill color when highlighted **/ - hi_fill: {} - + hiFill: {}, + + /** @type {number} transparency for annotation mask [0-1] **/ + maskTransparency: {}, + + /** @type {boolean} flag indicating whether the mask border is shown **/ + maskBorder: {}, + + /** @type {string} stroke color for arrow shape **/ + arrowStroke: {}, + + /** @type {number} stroke width for arrow shape [1-12] **/ + arrowStrokeWidth: {}, + + /** @type {string} stroke color when highlighted for arrow shape **/ + hiArrowStroke: {}, + + /** @type {number} stroke width when highlighted for arrow shape [1-12] **/ + hiArrowStrokeWidth: {}, + + /** @type {string} color to highlight the tail of the arrow shape **/ + highlightTail: {}, + + /** @type {number} arrow tail highlight radius **/ + hiTailRadius: {}, + + /** @type {string} color to highlight the head of the arrow shape **/ + highlightHead: {}, + + /** @type {number} arrow head highlight radius **/ + hiHeadRadius: {} } - - }] + }], + /** @type {Function} called for set mask on the shape **/ + setMask: function (mask, shapeIdx, transparency, border) { } }; /** @@ -73,13 +121,16 @@ var Annotation = { */ var Rectangle = { - x : {}, + x: {}, + + y: {}, - y : {}, + width: {}, - width : {}, + height: {}, - height : {} + /** @type {number} Rotation of the annotation with respect to the x-axis (degrees). [OPTIONAL - default is 0] **/ + rotation: {} } @@ -88,7 +139,18 @@ var Rectangle = { */ var Polygon = { - points : {} + points: {} + +} + +/** + * Annotation shape type: Arrow + */ +var Arrow = { + + arrowTail: {}, + + arrowHead: {} } @@ -98,11 +160,11 @@ var Polygon = { var AnnotoriousPlugin = { /** @type {Function} called on plugin initialization **/ - initPlugin : function(anno) {}, + initPlugin: function (anno) { }, /** @type {Function} called on initialization of a Popup element **/ - onInitAnnotator : function(annotator) {} - + onInitAnnotator: function (annotator) { } + }; /** @@ -111,13 +173,13 @@ var AnnotoriousPlugin = { var Annotator = { /** @type {Element} the annotator DOM element **/ - element : {}, + element: {}, /** @type {Object} the popup used by this annotator **/ - popup : {}, + popup: {}, /** @type {Object} the editor used by this annotator **/ - editor : {} + editor: {} }; @@ -126,21 +188,21 @@ var Annotator = { */ var Selector = { - init : function() {}, + init: function () { }, - getName : function() {}, + getName: function () { }, - getSupportedShapeType : function() {}, + getSupportedShapeType: function () { }, - startSelection : function() {}, + startSelection: function () { }, - stopSelection : function() {}, + stopSelection: function () { }, - getShape : function() {}, + getShape: function () { }, - getViewportBounds : function() {}, + getViewportBounds: function () { }, - drawShape : function() {} + drawShape: function () { } } @@ -149,11 +211,11 @@ var Selector = { */ var SelectionEvent = { - mouseEvent : {}, + mouseEvent: {}, - shape : {}, + shape: {}, - viewportBounds : {} + viewportBounds: {} } @@ -162,14 +224,14 @@ var SelectionEvent = { */ var Popup = { - startHideTimer : function() {}, + startHideTimer: function () { }, + + clearHideTimer: function () { }, - clearHideTimer : function() {}, + show: function () { }, - show : function() {}, + setPosition: function () { }, - setPosition : function() {}, + setAnnotation: function () { } - setAnnotation : function() {} - } \ No newline at end of file diff --git a/plovr/plovr.jar b/plovr/plovr.jar index b8c0a2c..90426c1 100644 Binary files a/plovr/plovr.jar and b/plovr/plovr.jar differ diff --git a/readme.md b/readme.md index 191c9e1..7e5d280 100644 --- a/readme.md +++ b/readme.md @@ -1,26 +1,618 @@ -# Annotorious - Image Annotation for the Web +# Annotorious - Image Annotation for the Web (custom) -**CURRENTLY UNSUPPORTED** +This library is a custom version of the [Annotorious library](https://github.com/annotorious/annotorious) (currently deprecated / dead). Show the new [Annotorious library](https://github.com/recogito/annotorious). -Annotorious is an Open Source image annotation toolkit written in JavaScript. Online demos are available -[on our project Website](https://annotorious.github.io). +Annotorious is an Open Source image annotation toolkit written in JavaScript. It allows you to add drawing, commenting and labeling capabilities to your web page images with a few lines of code. + +Are you looking for toolkit written in jQuery to annotate an image by dragging and dropping annotations? Check out [this project](https://github.com/AntoninoBonanno/DragDropAnnotate). ## Getting Started -Instructions on getting started using Annotorious on your own Web pages are [on the project Website](https://annotorious.github.io/getting-started.html) or -[on the Wiki](https://github.com/annotorious/annotorious/wiki/Getting-Started). Instructions on using it as a plugin to the -[Annotator](http://okfnlabs.org/projects/annotator/) Web annotation system are [here](http://annotorious.github.io/plug-outs/okfn-annotator.html). -If you require support, get in touch [via our mailing list](https://groups.google.com/forum/#!forum/annotorious). +Instructions on getting started using Original Annotorious library [on the Wiki](https://github.com/annotorious/annotorious/wiki/Getting-Started). +Instructions on using it as a plugin to the [Annotator](http://okfnlabs.org/projects/annotator/) Web annotation system are [here](http://annotorious.github.io/plug-outs/okfn-annotator.html). + +## How Do I Add Annotorious to My Web Page? + +* Unzip the contents of the package on your server +* Link the __Annotorious CSS file__ into the <head> of your Web pages +* Link the __Annotorious JavaScript__ file into the <head> of your Web pages + +Example: + +``` + + + + +``` + +## How Do I Enable Annotation for an Image on My Page? + +Once you have linked Annotorious into your Web page, you need to tell it which images should be annotatable. +There are two ways you can do this: + +__Option 1.__ Add a CSS class called ``annotatable`` to the image. This is the easiest way to add annotation functionality, and I'd always recommend using this approach unless your page loads images dynamically via JavaScript, after the page has loaded. + +Example: + +``` + + + + + + + + + + +``` + +__Option 2:__ Annotation-enable your images via JavaScript API, using ``anno.makeAnnotatable(img);`` You can use +this approach if your page loads images dynamically via JavaScript. + +# JavaScript API + +Annotorious provides a JavaScript API you can use to get, add or remove annotations, and hook into the Annotorious event lifecycle. All functionality is exposed via the global _anno_ object. The _anno_ object has the following methods + +**Show [JavaScript-API.md](https://github.com/AntoninoBonanno/annotorious/blob/master/JavaScript-API.md) for more details** + +## My Contribution (Extended JavaScript API) + +### Functionality +* [Mask](#mask): Added the ability to insert an image inside a annotation with shapes rect. +* [ColorMode](#colormode): Added the ability to draw custom shape without make an annotation. The drawn pixels coordinate are returned when the mouse is released. +* [SelectEditor](#selecteditor): Added the ability to use a select inside the editor. (Dropdown menu) +* [CursorAxes](#cursoraxes): Added the ability to show cursor axes inside the image. +* [ArrowMode](#arrowmode): Added the ability to annotate with draw arrow shape. (Arrow selector) +* [Fancy Box](#fancy-box): Added ['Fancy Box Selector'](https://annotorious.github.io/demos/fancybox-preview.html) plugin integration. +* [ExtraFields](#extrafields): Added the ability to add many fields to the annotation GUI widget from properties. +* [IntersectedAnnotations](#intersectedAnnotations): Added the ability to retrieves all annotations that intersect the centroid of an annotation. + +### Changes +* Added the ability to [reload the annotations](#reload-the-annotations). +* Added the `onMouseMoveOverItem` [event](#events) - fired when the mouse enters an annotatable item. +* Added an old annotation text inside the [event](#events) `onAnnotationUpdated`, when editing the annotation. +* Added the ability to insert [more new annotation](#add-multiple-annotations) to an item on the page. +* Added attributes in to the ["annotation" variable](#annotation-variable). +* Added more [properties](#properties) for editing style and functionality on runtime. +* Added the ability to set the measurement units used for the output geometry ['pixel', 'fraction']. +* Added the ability to custom the editor. +* Added the ability to move the annotations. The `onAnnotationMoved` [event](#events) - fired when the annotation was moved. +* Added the ability to rotate the annotations. The `onAnnotationRotated` [event](#events) - fired when the annotation was rotated. +* Fixed bug: + - resizing the image: now the annotations are resized with the image. (The image must have the `annotatable` class). + - add new annotation: if the new annotation forms are exactly the same as the other annotation forms, the new annotation is not inserted. + - measurement units 'pixel': now the measurement units 'pixel' are relative to the original image size. This because the image is responsive and the annotation has need of reference measurement units. + +## Usage + +### Mask + +Ability to insert an image inside a annotation with shapes rect. + +- set *Mask* on annotation variable + + ``` + shapes: [{ + type: 'rect', + geometry: { x: 0.2, y: 0.2, width: 0.50, height: 0.5 }, + mask: "http://www.example.com/mymask.jpg", + + style: { //style [OPTIONAL] + maskTransparency: 0.8, //transparency for annotation mask [0-1] [OPTIONAL] + maskBorder: true //if false, not show the mask border [OPTIONAL] + } + }] + ``` + +- set *Mask* dynamically - setMask(***mask***, ***shapeIdx***, ***transparency***, ***border***); + + - **mask**: the URL of the mask + - **shapeIdx**: index of shapes (default: 0) + - **transparency** transparency of mask [0-1](default: 0.8) + - **border** flag indicating whether the mask border is shown (default: true) +
+ + ``` + anno.addHandler('onAnnotationCreated', function (annotation) { + annotation.setMask("http://www.example.com/mymask.jpg"); + anno.reload(); + }); + ``` + +### ColorMode + +Ability to draw custom shape without make an annotation. The last drawn pixels coordinate are returned when the mouse is released on `onDrawnPixels` event. + +**`anno.setColorMode(*enabled*, *insideAnno*, *mode*, *color*, *strokeWidth*);`** + +Modalities of save the drawn pixels: +- **permanent**: pixels are saved until `anno.reset()` or `anno.reload()`. +- **active**: pixels are saved as long as *ColorMode* is enabled. +- **release**: the pixels are not saved, they are deleted when the mouse is released. + +- set *ColorMode* in the properties + + ``` + anno.setProperties({ + colorMode: { + /* DEFAULT VALUES */ + enabled: false, //if true, enable the colorMode + insideAnno: false, //if true, is possible draw only inside the annotations [OPTIONAL] + mode: "active", // mode of save the drawn pixels ["permanent", "active", "release"] [OPTIONAL] + color: "#2ECC71", //color of pixels [OPTIONAL] + strokeWidth: 2, //stroke width of pixels [1-12] [OPTIONAL] + } + }); + ``` + +- set *ColorMode* using the function + + ``` + // ENABLE - insideAnno, mode, color and strokeWidth are OPTIONAL + anno.setColorMode(true, true, 'release', '#E74C3C', 1); + + // DISABLE + anno.setColorMode(false); + ``` + +- Get drawn pixels + + ``` + anno.addHandler('onDrawnPixels', function (event) { + console.log(event); + event.drawnPixels; //array of last drawn pixels {x, y} + event.annotation; //the annotation on which it was drawn inside (only if the 'insideAnno' property is enabled) + }); + ``` + +### SelectEditor + +Ability to use a select, with custom options, inside the editor. (Dropdown menu) + +**`anno.useSelectEditor(*enabled*, *options*, *emptyOption*, *customLabel*);`** + +- define custom options + + ``` + var selectOptions = [ + { + id: 1, //the annotation text id [OPTIONAL] + value: 'My annotation' //the annotation text + }, + ]; + ``` + +- set *SelectEditor* in the properties + + ``` + anno.setProperties({ + selectEditor: { + /* DEFAULT VALUES */ + enabled: false, //if true, enable the select editor + options: undefined, //the options of select, use format of 'selectOptions' variable + emptyOption: false, //if true, enable the empty select option [OPTIONAL] + customLabel: "<--- Select one option --->" //the custom first label if not use empty options [OPTIONAL] + } + }); + ``` + +- set *SelectEditor* using the function + + ``` + // ENABLE - emptyOption and customLabel are OPTIONAL + anno.useSelectEditor(true, selectOptions, false, "Select one"); + + // DISABLE + anno.useSelectEditor(false); + ``` + +### CursorAxes + +Ability to show cursor axes inside the image. + +**`anno.showCursorAxes(*enabled*, *dash*, *color*, *strokeWidth*);`** + +- set *CursorAxes* in the properties + + ``` + anno.setProperties({ + cursorAxes: { + /* DEFAULT VALUES */ + enabled: false, //if true, enable the cursor axes + dash: false, //if true, draw dashed stroke [OPTIONAL] + color: "#ffffff", //color of cursor axes [OPTIONAL] + strokeWidth: 2 //stroke width of axes [1-12] [OPTIONAL] + } + }); + ``` + +- set *CursorAxes* using the function + + ``` + // ENABLE - dash, color and strokeWidth are OPTIONAL + anno.showCursorAxes(true, true, '#E74C3C', 2); + + // DISABLE + anno.showCursorAxes(false); + ``` + +### ArrowMode + +Ability to annotate with draw arrow shape. (Arrow selector) + +**` anno.setArrowMode(*enabled*); `** + +- set *ArrowMode* in the properties + + ``` + anno.setProperties({ + /* DEFAULT VALUES */ + arrowMode: false, //if true, enable the arrowMode + shapeStyle: { // set style + arrowStroke: '#ffffff', // stroke color for arrow shape + arrowStrokeWidth: 2, // stroke width for arrow shape [1-12] + hiArrowStroke: '#fff000', // stroke color for arrow shape + hiArrowStrokeWidth: 2.2 //stroke width for arrow shape [1-12] + + highlightTail: undefined, //color to highlight the tail of the arrow shape (draw a circle on the tail) [es "rgba(0, 0, 255, 0.7)"] + hiTailRadius: 5, // arrow tail highlight radius + highlightHead: undefined, //color to highlight the head of the arrow shape (draw a circle on the head) [es "rgba(0, 0, 255, 0.7)"] + hiHeadRadius: 5 // arrow head highlight radius + } + }); + ``` + +- set *ArrowMode* using the function + + ``` + // ENABLE + anno.setArrowMode(true); + + // DISABLE + anno.setArrowMode(false); + ``` + +- set *arrow* shape on annotation variable + + ``` + shapes: [{ + type: 'arrow', + geometry: { //the shape geometry (relative coordinates) [you can also use the coordinates in pixels] + arrowTail: { x: 0.20 y: 0.74 }, // coordinate of arrow tail + arrowHead: { x: 0.77 y: 0.66 } // coordinate of arrowhead + }, + + style: { //style [OPTIONAL] + arrowStroke: '#ffffff', // stroke color for arrow shape + arrowStrokeWidth: 2, // stroke width for arrow shape [1-12] + hiArrowStroke: '#fff000', // stroke color for arrow shape + hiArrowStrokeWidth: 2.2 //stroke width for arrow shape [1-12] + + highlightTail: undefined, //color to highlight the tail of the arrow shape (draw a circle on the tail) [es "rgba(0, 0, 255, 0.7)"] + hiTailRadius: 5, // arrow tail highlight radius + highlightHead: undefined, //color to highlight the head of the arrow shape (draw a circle on the head) [es "rgba(0, 0, 255, 0.7)"] + hiHeadRadius: 5 // arrow head highlight radius + } + }] + ``` + +### Fancy Box + +Added ['Fancy Box Selector'](https://annotorious.github.io/demos/fancybox-preview.html) plugin integration. + +**` anno.useFancyBox(*enabled*); `** + +- set *Fancy Box* in the properties + + ``` + anno.setProperties({ + /* DEFAULT VALUES */ + fancyBox: false, //if true, enable the Fancy Box Selector + }); + ``` + +- set *Fancy Box* using the function + + ``` + // ENABLE + anno.useFancyBox(true); + + // DISABLE + anno.useFancyBox(false); + ``` + +### ExtraFields + +Ability to add many fields to the annotation GUI widget from properties. A field can be either an (HTML) string, or a function that takes an Annotation as argument and returns an (HTML) string or a DOM element. + +- set *ExtraFields* for editor GUI widget + + ``` + anno.setProperties({ + editor: { + extraFields: [ + "
MyExtraField
", //a field (HTML) string + ] + } + }); + ``` + +- set *ExtraFields* for popup GUI widget + + ``` + anno.setProperties({ + popup: { + extraFields: [ + "
MyExtraField
", //a field (HTML) string + ] + } + }); + ``` + +### IntersectedAnnotations + +Ability to retrieves all annotations that intersect the centroid of an annotation. + +**` anno.getIntersectedAnnotations(*annotation*, *shapeType*); `** + +Function parameters: +- **annotation**: an annotation. The centroid is calculated from the shapes[0] of this variable. +- **shapeType**: if you want to filter the results by shape type; Default `undefined`, possible values: ['rect', 'point', 'arrow', 'polygon'] + +The function return an array of the intersect annotations sorted by size, smallest first. + +Example of use: +``` +anno.addHandler('onAnnotationCreated', function (annotation, item) { + var intersectedAnnotations = anno.getIntersectedAnnotations(annotation); + var intersectedFilteredAnnotations = anno.getIntersectedAnnotations(annotation, 'rect'); + + console.log(intersectedAnnotations, intersectedFilteredAnnotations); +}); +``` + +### Reload the annotations + +Ability to reload the annotations, the image must have the `annotatable` class. + +**` anno.reload(*removeProperties*); `** + +``` +anno.reload(); +``` + +If you want to reload the annotations and remove the properties use: `anno.reload(true);`. + +### Events + +* Added the `onMouseMoveOverItem` event, fired when the mouse enters an annotatable item. Return the cursor coordinates and bounding box coordinates [measurement units are 'pixels' relative to the original image size] + + ``` + anno.addHandler('onMouseMoveOverItem', function (pixels, event) { + console.log(pixels); // {cursor: { x, y }, box: { x, y, width, height }} + }); + ``` + +* Added an old annotation text inside the event `onAnnotationUpdated`, when editing the annotation. + + ``` + anno.addHandler('onAnnotationUpdated', function (annotation, old_value) { + console.log(old_value); // {text: "My annotation", textId: "1"} + }); + ``` + +* Added the `onAnnotationMoved` event, fired when the annotation was moved. Return the annotation moved. + + ``` + anno.addHandler('onAnnotationMoved', function (annotation) { + console.log(annotation); + }); + ``` + +* Added the `onAnnotationRotated` event, fired when the annotation was rotated. Return the annotation rotated. + + ``` + anno.addHandler('onAnnotationRotated', function (annotation) { + console.log(annotation); + }); + ``` + +### Add Multiple Annotations + +Added the ability to insert more new annotation to an item on the page. The function accepts an array of annotations. + + ``` + anno.addAnnotations([myannotation, myannotation1]); + ``` + +### Annotation Variable + +Added attributes to the "annotation" variable [OPTIONAL - if you create this variable] + +``` +var myAnnotation = { + + /* COMPLETE ATTRIBUTES */ + + id: undefined, //id assignable to annotation + + src : 'http://www.example.com/myimage.jpg', //the URL of the image where the annotation should go + context: '', //source URL of the HTML document containing the annotated object [OPTIONAL] + + text : 'My annotation', //the annotation text + textId : 1, //the annotation text id [OPTIONAL] + editable: true, //if false, there will be no delete icon in the annotation popup. Make annotation 'read-only' [OPTIONAL] + + movable: true, //if false, there will be no move icon in the annotation popup. Make annotation not movable [OPTIONAL] + rotable: true, //if false, there will be no rotate icon in the annotation popup. Make annotation not rotable [OPTIONAL] + + created_at: 1579909408935, //the timestamp of annotation creation [OPTIONAL] + + shapes : [{ //the annotation shape + + type : 'rect', //the shape type ['rect', 'point', 'arrow', 'polygon'] for enable 'polygon' shape show the official page + mask : 'http://www.example.com/mymask.jpg', //the URL of the mask - only if type is 'rect' [OPTIONAL] + geometry : { //the shape geometry (relative coordinates) + x : 0.1, + y: 0.1, + width : 0.4, + height: 0.3, + rotation: 0 // Rotation of the annotation with respect to the x-axis (degrees). [OPTIONAL - default is 0] + } + + //geometry : { x : 10, y: 10, width : 40, height: 60 } //the shape geometry (pixel coordinates relative to the original image size) + units: 'fraction', //measurement units used for the geometry ['pixel', 'fraction'] [OPTIONAL - only required for pixel coordinates] + + style: { //the shape style, override the shapeStyle properties for this annotation [OPTIONAL] + outline: '#000000', //outline color for annotation and selection shapes [OPTIONAL] + outlineWidth: 1, //outline width for annotation and selection shapes [1-12] [OPTIONAL] + + hiOutline: '#000000', // outline color for highlighted annotation shapes [OPTIONAL] + hiOutlineWidth: 1, // outline width for highlighted annotation shapes [1-12] [OPTIONAL] + + stroke: '#ffffff', // stroke color for annotation and selection shapes [OPTIONAL] + strokeWidth: 1, // stroke width for annotation and selection shapes [1-12] [OPTIONAL] + + hiStroke: '#fff000', // stroke color for highlighted annotation shapes [OPTIONAL] + hiStrokeWidth: 1.2, //stroke width for highlighted annotation shapes [1-12] [OPTIONAL] + + fill: undefined, //fill color for annotation and selection shapes [OPTIONAL] + hiFill: undefined, //fill color for highlighted annotation shapes [OPTIONAL] + + maskTransparency: 0.8, //transparency for annotation mask [0-1] [OPTIONAL] + maskBorder: true, //if false, not show the mask border [OPTIONAL] + + arrowStroke: '#ffffff', // stroke color for arrow shape [OPTIONAL] + arrowStrokeWidth: 2, // stroke width for arrow shape [1-12] [OPTIONAL] + hiArrowStroke: '#fff000', // stroke color for arrow shape [OPTIONAL] + hiArrowStrokeWidth: 2.2 //stroke width for arrow shape [1-12] [OPTIONAL] + + highlightTail: undefined, //color to highlight the tail of the arrow shape (draw a circle on the tail) [OPTIONAL] + hiTailRadius: 5, // arrow tail highlight radius [OPTIONAL] + highlightHead: undefined, //color to highlight the head of the arrow shape (draw a circle on the head) [OPTIONAL] + hiHeadRadius: 5 // arrow head highlight radius [OPTIONAL] + } + }], + + setMask(mask, shapeIdx, transparency, border) //function to set mask dinamically on the shape + +``` + +### Properties + +Added more properties for editing style and functionality on runtime. + +- All properties are **OPTIONAL** and they merge with the existing. +- If you set the property such as `undefined` or empty object (for the objects), then set the default value. +- The properties are removed if the `anno.reset();` function is called or the `anno.reload(true);` function is called, retained if the `anno.reload();` function is called. + +``` +anno.setProperties({ + + /* COMPLETE and DEFAULT VALUES */ + + displayMessage: "Click and Drag to Annotate", //the message to display as hint + hideSelectionWidget: false, //if true, disables the selection widget on all + hideAnnotations: false, //if true, hides existing annotations on all + + outputUnits: 'fraction', //measurement units used for the output geometry ['pixel', 'fraction'] [pixels are relative to the original image size] + drawInsideRectAnno: false, //if true, new annotations can only be made within annotations with 'rect' shape + + colorMode: { + enabled: false, //if true, enable the colorMode + insideAnno: false, //if true, is possible draw only inside the annotations + mode: "active", // mode of save the drawn pixels ["permanent", "active", "release"] + color: "#2ECC71", //color of pixels + strokeWidth: 2, //stroke width of pixels [1-12] + }, + + selectEditor: { + enabled: false, //if true, enable the select editor + options: undefined, //the options of select, use format of 'selectOptions' variable [only required if is enabled] + emptyOption: false, //if true, enable the empty select option + customLabel: "<--- Select one option --->" //the custom first label if not use empty options + }, + + cursorAxes: { + enabled: false, //if true, enable the cursor axes + dash: false, //if true, draw dashed stroke + color: "#ffffff", //color of cursor axes + strokeWidth: 2 //stroke width of axes [1-12] + }, + + arrowMode: false, //if true, enable the arrowMode + + fancyBox: false, //if true, enable the Fancy Box Selector + + editor: { //properties for editor GUI widget + enterText: true, //if false, not show the textarea or select for enter text + saveReadOnly: false, // if true, the new annotation is 'read-only' (the users can't delete or edit the new annotation - does not apply to edited annotations) + textarea: { + placeholder: "Add a Comment...", //placeholder of textarea + className: "annotorious-editor-text" //class of textarea + }, + buttons: { + save: { + text: "Save", //text of save button + className: "annotorious-editor-button annotorious-editor-button-save" //class of save button + }, + abort: { + text: "Cancel", //text of abort button + className: "annotorious-editor-button annotorious-editor-button-cancel" //class of abort button + } + }, + extraFields: undefined //add many fields to the annotation editor GUI widget. Show 'ExtraFields' section for more details + }, + + popup: { //properties for popup GUI widget + extraFields: undefined, //add many fields to the annotation popup GUI widget. Show 'ExtraFields' section for more + showMoveButton: true, //if false, not show the move button + showRotateButton: true //if false, not show the rotate button + }, + + shapeStyle: { //global style + outline: '#000000', //outline color for annotation and selection shapes + outlineWidth: 1, //outline width for annotation and selection shapes [1-12] + + hiOutline: '#000000', // outline color for highlighted annotation shapes + hiOutlineWidth: 1, // outline width for highlighted annotation shapes [1-12] + + stroke: '#ffffff', // stroke color for annotation and selection shapes + strokeWidth: 1, // stroke width for annotation and selection shapes [1-12] + + hiStroke: '#fff000', // stroke color for highlighted annotation shapes + hiStrokeWidth: 1.2, //stroke width for highlighted annotation shapes [1-12] + + fill: undefined, //fill color for annotation and selection shapes + hiFill: undefined, //fill color for highlighted annotation shapes + + maskTransparency: 0.8, //transparency for annotation mask [0-1] + maskBorder: true, //if false, not show the mask border + + arrowStroke: '#ffffff', // stroke color for arrow shape + arrowStrokeWidth: 2, // stroke width for arrow shape [1-12] + hiArrowStroke: '#fff000', // stroke color for arrow shape + hiArrowStrokeWidth: 2.2 //stroke width for arrow shape [1-12] + + highlightTail: undefined, //you can set a color to highlight the tail of the arrow shape (draw a circle on the tail) + hiTailRadius: 5, // arrow tail highlight radius + highlightHead: undefined, //you can set a color to highlight the head of the arrow shape (draw a circle on the head) + hiHeadRadius: 5 // arrow head highlight radius + } +}); +``` + +## Test Library + +Run `java -jar plovr/plovr.jar serve standalone.json` command on root directory project -## Getting Involved +Open test\image\index.html -Want to help out? There are many ways you can contribute! +## Build library -* Using Annotorious? Be sure to give us a shout and let us know your feedback via [our mailing list](https://groups.google.com/forum/#!forum/annotorious). -* Found a bug? [Drop us a line](https://groups.google.com/forum/#!forum/annotorious) or post an issue on our [issue tracker](https://github.com/annotorious/annotorious/issues). -* Want to improve our documentation? We'd really appreciate an extra hand [on our Wiki](https://github.com/annotorious/annotorious/wiki)! -* Missing a feature? [Learn how to develop your own Annotorious Plugins](https://github.com/annotorious/annotorious/wiki/Developing-Plugins). -* Want to hack on Annotorious? We welcome contributions no matter how small or big! +Run `java -jar plovr/plovr.jar build standalone.json > annotorious.min.js` command on root directory of project, then the file **annotorious.min.js** will be generated. ## License diff --git a/src/annotation.js b/src/annotation.js index fa5bd7e..5c93ce1 100644 --- a/src/annotation.js +++ b/src/annotation.js @@ -7,11 +7,33 @@ goog.require('annotorious.shape'); * @param {string} src the source URL of the annotated object * @param {string} text the annotation text * @param {annotorious.shape.Shape} shape the annotated fragment shape + * @param {Date} created_at the timestamp of annotation creation [OPTIONAL] + * @param {number} textId the id of text + * @param {number} id the id of annotation * @constructor */ -annotorious.Annotation = function(src, text, shape) { +annotorious.Annotation = function (src, text, shape, created_at, textId, id) { + this.id = id; this.src = src; this.text = text; - this.shapes = [ shape ]; - this['context'] = document.URL; // Prevents dead code removal + this.textId = textId; + this.shapes = [shape]; + this.created_at = created_at || Date.now(); + this['context'] = document.URL; // Prevents dead code removal + this['setMask'] = this.setMask; } + +/** + * Set mask on the shape + * @param {string} mask the URL of the mask - only if type is 'rect' + * @param {number} shapeIdx the index of shape to set the mask [default: 0] (optional) + * @param {number} transparency transparency for annotation mask [0-1] [default: 0.8] (optional) + * @param {boolean} border if false, not show the mask border [default: true] (optional) + */ +annotorious.Annotation.prototype.setMask = function (mask, shapeIdx, transparency, border) { + if (!shapeIdx || this.shapes.length >= shapeIdx) shapeIdx = 0; + if (this.shapes[shapeIdx].type != annotorious.shape.ShapeType.RECTANGLE) console.log('WARNING: impossible to set mask in shape ' + this.shapes[shapeIdx].type); + else this.shapes[shapeIdx].mask = mask; + if (transparency) this.shapes[shapeIdx].style.maskTransparency = transparency; + if (border != undefined) this.shapes[shapeIdx].style.maskBorder = border; +} \ No newline at end of file diff --git a/src/annotorious.js b/src/annotorious.js index baae997..0f2cc0e 100644 --- a/src/annotorious.js +++ b/src/annotorious.js @@ -16,44 +16,52 @@ goog.require('annotorious.mediatypes.openseadragon.OpenSeadragonModule'); * type - image, OpenLayers, etc.) * @constructor */ -annotorious.Annotorious = function() { +annotorious.Annotorious = function () { /** @private **/ this._isInitialized = false; - + /** @private **/ - this._modules = [ new annotorious.mediatypes.image.ImageModule() ]; - + this._modules = [new annotorious.mediatypes.image.ImageModule()]; + if (window['OpenLayers']) this._modules.push(new annotorious.mediatypes.openlayers.OpenLayersModule()); - + if (window['OpenSeadragon']) this._modules.push(new annotorious.mediatypes.openseadragon.OpenSeadragonModule()); - + /** @private **/ this._plugins = []; var self = this; - annotorious.dom.addOnLoadHandler(function() { self._init(); }); + annotorious.dom.addOnLoadHandler(function () { self._init(); }); } -annotorious.Annotorious.prototype._init = function() { +annotorious.Annotorious.prototype._init = function () { if (this._isInitialized) return; - + var self = this; - goog.array.forEach(this._modules, function(module) { + goog.array.forEach(this._modules, function (module) { module.init(); }); - goog.array.forEach(this._plugins, function(plugin) { + goog.array.forEach(this._plugins, function (plugin) { if (plugin.initPlugin) plugin.initPlugin(self); - - goog.array.forEach(self._modules, function(module) { + + goog.array.forEach(self._modules, function (module) { module.addPlugin(plugin); }); }); - + + /** + * Each time the window is resized, it reloads the annotations - for responsive images + * The image must have the 'annotatable' class + */ + goog.events.listen(window, goog.events.EventType.RESIZE, function (event) { + self.reload(); + }); + this._isInitialized = true; } @@ -64,8 +72,8 @@ annotorious.Annotorious.prototype._init = function() { * @return {Object | null} * @private */ -annotorious.Annotorious.prototype._getModuleForItemSrc = function(item_src) { - return goog.array.find(this._modules, function(module) { +annotorious.Annotorious.prototype._getModuleForItemSrc = function (item_src) { + return goog.array.find(this._modules, function (module) { return module.annotatesItem(item_src); }); } @@ -81,9 +89,9 @@ annotorious.Annotorious.prototype._getModuleForItemSrc = function(item_src) { * @param {string | Function} opt_item_url_or_callback the URL of the item, or a callback function * @param {Function} opt_callback a callback function (if the first parameter was a URL) */ -annotorious.Annotorious.prototype.activateSelector = function(opt_item_url_or_callback, opt_callback) { +annotorious.Annotorious.prototype.activateSelector = function (opt_item_url_or_callback, opt_callback) { var item_url = undefined, - callback = undefined; + callback = undefined; if (goog.isString(opt_item_url_or_callback)) { item_url = opt_item_url_or_callback; @@ -97,7 +105,7 @@ annotorious.Annotorious.prototype.activateSelector = function(opt_item_url_or_ca if (module) module.activateSelector(item_url, callback); } else { - goog.array.forEach(this._modules, function(module) { + goog.array.forEach(this._modules, function (module) { module.activateSelector(callback); }); } @@ -108,11 +116,42 @@ annotorious.Annotorious.prototype.activateSelector = function(opt_item_url_or_ca * @param {annotorious.Annotation} annotation the annotation * @param {annotorious.Annotation} opt_replace optionally, an existing annotation to replace */ -annotorious.Annotorious.prototype.addAnnotation = function(annotation, opt_replace) { - annotation.src = annotorious.dom.toAbsoluteURL(annotation.src); - var module = this._getModuleForItemSrc(annotation.src); +annotorious.Annotorious.prototype.addAnnotation = function (annotation, opt_replace) { + if (!annotation.src) { + console.error("Error: The src attribute is required for identification the image associated to annotation. See the documentation for more details.", annotation); + return; + } + if (!annotation.shapes) { + console.error("Error: The shapes attribute is required and must necessarily include the type and geometry field. See the documentation for more details.", annotation); + return; + } + + var newAnnotation = new annotorious.Annotation( + annotorious.dom.toAbsoluteURL(annotation.src), + annotation.text, + annotation.shapes[0], + annotation.created_at, + annotation.textId, + annotation.id + ); + newAnnotation.editable = annotation.editable; + newAnnotation.movable = annotation.movable; + newAnnotation.rotable = annotation.rotable; + var module = this._getModuleForItemSrc(newAnnotation.src); if (module) - module.addAnnotation(annotation, opt_replace); + module.addAnnotation(newAnnotation, opt_replace); +} + +/** + * Adds more annotations to an item on the page. + * @param {Array[annotorious.Annotation]} annotations the annotations + */ +annotorious.Annotorious.prototype.addAnnotations = function (annotations) { + if (!Array.isArray(annotations) || !annotations.length) return; + var self = this; + annotations.forEach(function (annotation) { + self.addAnnotation(annotation); + }); } /** @@ -120,8 +159,8 @@ annotorious.Annotorious.prototype.addAnnotation = function(annotation, opt_repla * @param {annotorious.events.EventType} type the event type * @param {Function} handler the handler function */ -annotorious.Annotorious.prototype.addHandler = function(type, handler) { - goog.array.forEach(this._modules, function(module) { +annotorious.Annotorious.prototype.addHandler = function (type, handler) { + goog.array.forEach(this._modules, function (module) { module.addHandler(type, handler); }); } @@ -131,8 +170,8 @@ annotorious.Annotorious.prototype.addHandler = function(type, handler) { * @param {annotorious.events.EventType} type the event type * @param {Function} handler the handler function */ -annotorious.Annotorious.prototype.removeHandler = function(type, handler) { - goog.array.forEach(this._modules, function(module) { +annotorious.Annotorious.prototype.removeHandler = function (type, handler) { + goog.array.forEach(this._modules, function (module) { module.removeHandler(type, handler); }); } @@ -142,7 +181,7 @@ annotorious.Annotorious.prototype.removeHandler = function(type, handler) { * @param {string} plugin_name the plugin name * @param {Object} opt_config_options an optional object literal with plugin config options */ -annotorious.Annotorious.prototype.addPlugin = function(plugin_name, opt_config_options) { +annotorious.Annotorious.prototype.addPlugin = function (plugin_name, opt_config_options) { try { var plugin = new window['annotorious']['plugin'][plugin_name](opt_config_options); @@ -150,13 +189,13 @@ annotorious.Annotorious.prototype.addPlugin = function(plugin_name, opt_config_o // Document loaded -- init immediately if (plugin.initPlugin) plugin.initPlugin(this); - - goog.array.forEach(this._modules, function(module) { + + goog.array.forEach(this._modules, function (module) { module.addPlugin(plugin); - }); + }); } else { // Document not loaded yet -- defer init - this._plugins.push(plugin); + this._plugins.push(plugin); } } catch (error) { console.log('Could not load plugin: ' + plugin_name); @@ -170,16 +209,16 @@ annotorious.Annotorious.prototype.addPlugin = function(plugin_name, opt_config_o * made annotatable again via anno.makeAnnotatable(). * @param {string=} opt_item_url the URL of the item on which to destroy annotation functionality */ -annotorious.Annotorious.prototype.destroy = function(opt_item_url) { +annotorious.Annotorious.prototype.destroy = function (opt_item_url) { if (opt_item_url) { var module = this._getModuleForItemSrc(opt_item_url); if (module) module.destroy(opt_item_url); } else { - goog.array.forEach(this._modules, function(module) { + goog.array.forEach(this._modules, function (module) { module.destroy(); }); - } + } } /** @@ -187,7 +226,7 @@ annotorious.Annotorious.prototype.destroy = function(opt_item_url) { * particular item. * @param {string} item_url the URL of the item to query for the active selector */ -annotorious.Annotorious.prototype.getActiveSelector = function(item_url) { +annotorious.Annotorious.prototype.getActiveSelector = function (item_url) { var module = this._getModuleForItemSrc(item_url); if (module) return module.getActiveSelector(item_url); @@ -201,7 +240,7 @@ annotorious.Annotorious.prototype.getActiveSelector = function(item_url) { * @param {string | undefined} opt_item_url an item URL (optional) * @return {Array.} the annotations */ -annotorious.Annotorious.prototype.getAnnotations = function(opt_item_url) { +annotorious.Annotorious.prototype.getAnnotations = function (opt_item_url) { if (opt_item_url) { var module = this._getModuleForItemSrc(opt_item_url); if (module) @@ -210,7 +249,7 @@ annotorious.Annotorious.prototype.getAnnotations = function(opt_item_url) { return []; } else { var annotations = []; - goog.array.forEach(this._modules, function(module) { + goog.array.forEach(this._modules, function (module) { goog.array.extend(annotations, module.getAnnotations()); }); return annotations; @@ -222,7 +261,7 @@ annotorious.Annotorious.prototype.getAnnotations = function(opt_item_url) { * @param {string} item_url the URL of the item to query for available selectors * @returns {Array.} the list of selector names */ -annotorious.Annotorious.prototype.getAvailableSelectors = function(item_url) { +annotorious.Annotorious.prototype.getAvailableSelectors = function (item_url) { var module = this._getModuleForItemSrc(item_url); if (module) return module.getAvailableSelectors(item_url); @@ -234,13 +273,13 @@ annotorious.Annotorious.prototype.getAvailableSelectors = function(item_url) { * Hides existing annotations on all, or a specific item. * @param {string} opt_item_url the URL of the item */ -annotorious.Annotorious.prototype.hideAnnotations = function(opt_item_url) { +annotorious.Annotorious.prototype.hideAnnotations = function (opt_item_url) { if (opt_item_url) { var module = this._getModuleForItemSrc(opt_item_url); if (module) module.hideAnnotations(opt_item_url); } else { - goog.array.forEach(this._modules, function(module) { + goog.array.forEach(this._modules, function (module) { module.hideAnnotations(); }); } @@ -252,25 +291,25 @@ annotorious.Annotorious.prototype.hideAnnotations = function(opt_item_url) { * items on the page. * @param {string | undefined} opt_item_url the URL of the item on which to hide the selection widget */ -annotorious.Annotorious.prototype.hideSelectionWidget = function(opt_item_url) { +annotorious.Annotorious.prototype.hideSelectionWidget = function (opt_item_url) { if (opt_item_url) { var module = this._getModuleForItemSrc(opt_item_url); if (module) module.hideSelectionWidget(opt_item_url); } else { - goog.array.forEach(this._modules, function(module) { + goog.array.forEach(this._modules, function (module) { module.hideSelectionWidget(); }); } } -annotorious.Annotorious.prototype.stopSelection = function(opt_item_url) { +annotorious.Annotorious.prototype.stopSelection = function (opt_item_url) { if (opt_item_url) { var module = this._getModuleForItemSrc(opt_item_url); if (module) module.stopSelection(opt_item_url); } else { - goog.array.forEach(this._modules, function(module) { + goog.array.forEach(this._modules, function (module) { module.stopSelection(); }); } @@ -281,14 +320,14 @@ annotorious.Annotorious.prototype.stopSelection = function(opt_item_url) { * Highlights the specified annotation. * @param {annotorious.Annotation} annotation the annotation */ -annotorious.Annotorious.prototype.highlightAnnotation = function(annotation) { +annotorious.Annotorious.prototype.highlightAnnotation = function (annotation) { if (annotation) { var module = this._getModuleForItemSrc(annotation.src); if (module) module.highlightAnnotation(annotation); } else { - goog.array.forEach(this._modules, function(module) { + goog.array.forEach(this._modules, function (module) { module.highlightAnnotation(); }); } @@ -298,18 +337,18 @@ annotorious.Annotorious.prototype.highlightAnnotation = function(annotation) { * Makes an item annotatable, if there is a module that supports the item type. * @param {Object} item the annotatable item */ -annotorious.Annotorious.prototype.makeAnnotatable = function(item) { +annotorious.Annotorious.prototype.makeAnnotatable = function (item) { // Be sure to init if the load handler hasn't already taken care of it this._init(); - - var module = goog.array.find(this._modules, function(module) { + + var module = goog.array.find(this._modules, function (module) { return module.supports(item); }); if (module) module.makeAnnotatable(item); else - throw('Error: Annotorious does not support this media type in the current version or build configuration.'); + throw ('Error: Annotorious does not support this media type in the current version or build configuration.'); } /** @@ -318,12 +357,12 @@ annotorious.Annotorious.prototype.makeAnnotatable = function(item) { * annotations on all items on the page will be removed. * @param {string} opt_item_url the src URL of the item */ -annotorious.Annotorious.prototype.removeAll = function(opt_item_url) { +annotorious.Annotorious.prototype.removeAll = function (opt_item_url) { // TODO this could be optimized a lot by adding a .removeAll method // to modules and annotators! var self = this; - goog.array.forEach(this.getAnnotations(opt_item_url), function(annotation) { - self.removeAnnotation(annotation); + goog.array.forEach(this.getAnnotations(opt_item_url), function (annotation) { + self.removeAnnotation(annotation); }); } @@ -331,20 +370,39 @@ annotorious.Annotorious.prototype.removeAll = function(opt_item_url) { * Removes an annotation from an item on the page. * @param {annotorious.Annotation} annotation the annotation to remove */ -annotorious.Annotorious.prototype.removeAnnotation = function(annotation) { +annotorious.Annotorious.prototype.removeAnnotation = function (annotation) { var module = this._getModuleForItemSrc(annotation.src); if (module) module.removeAnnotation(annotation); } +/** + * Reload the annotations. Images with the 'annotatable' + * CSS class will have been re-initialized (i.e. they will be annotatable, with + * a fresh annotator). + * @param {bool} removeProperties if true, reset the properties + */ +annotorious.Annotorious.prototype.reload = function (removeProperties) { + goog.array.forEach(this._modules, function (module) { + var annotations = module.getAnnotations(); + if (removeProperties) module.removeProperties(); + module.destroy(); + module.init(); + goog.array.forEach(annotations, function (annotation) { + module.addAnnotation(annotation); + }); + }); +} + /** * Resets annotation functionality on this page. After the reset, annotation * functionality will be reomved from all items. Images with the 'annotatable' * CSS class will have been re-initialized (i.e. they will be annotatable, with * a fresh annotator). */ -annotorious.Annotorious.prototype.reset = function(annotation) { - goog.array.forEach(this._modules, function(module) { +annotorious.Annotorious.prototype.reset = function (annotation) { + goog.array.forEach(this._modules, function (module) { + module.removeProperties(); module.destroy(); module.init(); }); @@ -355,28 +413,109 @@ annotorious.Annotorious.prototype.reset = function(annotation) { * @param {string} item_url the URL of the item on which to set the selector * @param {string} selector the name of the selector to set on the item */ -annotorious.Annotorious.prototype.setActiveSelector = function(item_url, selector) { +annotorious.Annotorious.prototype.setActiveSelector = function (item_url, selector) { var module = this._getModuleForItemSrc(item_url); if (module) - module.setActiveSelector(item_url, selector); + module.setActiveSelector(item_url, selector); } - + /** * Sets system-wide properties. The 'props' object is a key/value hash and - * supports the following properties: - * - * outline: outline color for annotation and selection shapes - * stroke: stroke color for annotation and selection shapes - * fill: fill color for annotation and selection shapes - * hi_stroke: stroke color for highlighted annotation shapes - * hi_fill: fill color for highlighted annotation shapes + * supports the following properties: [show Readme.md] * * @param {Object} props the properties object */ -annotorious.Annotorious.prototype.setProperties = function(props) { - goog.array.forEach(this._modules, function(module) { +annotorious.Annotorious.prototype.setProperties = function (props) { + goog.array.forEach(this._modules, function (module) { module.setProperties(props); - }); + }); +} + +/** + * Ability to draw custom shape without make an annotation.The drawn pixels coordinate are returned when the mouse is released. + * @param {boolean} enabled if true, enable the colorMode + * @param {boolean} insideAnno if true, is possible draw only inside the annotations + * @param {string} mode mode of save the drawn pixels + * @param {string} color color of pixels + * @param {integer} strokeWidth stroke width of pixels [1-12] + */ +annotorious.Annotorious.prototype.setColorMode = function (enabled, insideAnno, mode, color, strokeWidth) { + this.setProperties({ + "colorMode": { + "enabled": enabled, + "insideAnno": insideAnno, + "mode": mode, + "color": color, + "strokeWidth": strokeWidth + } + }); +} + +/** + * Enables (or disables) the ability to show the select editor + * @param {boolean} enabled true to enable the select editor + * @param {Array[Object]} options the options of select + * @param {boolean} emptyOption true to enable the empty select option + * @param {string} customLabel the custom first label if not use empty options + */ +annotorious.Annotorious.prototype.useSelectEditor = function (enabled, options, emptyOption, customLabel) { + this.setProperties({ + "selectEditor": { + "enabled": enabled, + "options": options, + "emptyOption": emptyOption, + "customLabel": customLabel + } + }); +} + +/** + * Enables (or disables) the ability to show the cursor axes + * @param {boolean} enabled true to enable the cursors axes + * @param {boolean} dash true if draw dashed line + * @param {string} color color of axes + * @param {integer} strokeWidth stroke width of axes [1-12] + */ +annotorious.Annotorious.prototype.showCursorAxes = function (enabled, dash, color, strokeWidth) { + this.setProperties({ + "cursorAxes": { + "enabled": enabled, + "dash": dash, + "color": color, + "strokeWidth": strokeWidth + } + }); +} + +/** + * Enables (or disables) the ability to draw arrow shape + * @param {boolean} enabled true to enable the arrow mode + */ +annotorious.Annotorious.prototype.setArrowMode = function (enabled) { + this.setProperties({ + "arrowMode": enabled + }); +} + +/** + * Enables (or disables) the ability to use Fancy Box Selector + * @param {boolean} enabled true to enable the Fancy Box Selector + */ +annotorious.Annotorious.prototype.useFancyBox = function (enabled) { + this.setProperties({ + "fancyBox": enabled + }); +} + +/** + * Retrieves all annotations that intersect the centroid of an annotation + * @param {annotorious.Annotation} annotation annotation from which to retrieve the centroid + * @param {String} type filter by shape type + * @return {Array.} the annotations sorted by size, smallest first + */ +annotorious.Annotorious.prototype.getIntersectedAnnotations = function (annotation, type) { + var module = this._getModuleForItemSrc(annotorious.dom.toAbsoluteURL(annotation.src)); + return (module) ? module.getIntersectedAnnotations(annotation, type) : []; } /** @@ -387,7 +526,7 @@ annotorious.Annotorious.prototype.setProperties = function(props) { * @deprecated will be removed in v1.0! * !!!! */ -annotorious.Annotorious.prototype.setSelectionEnabled = function(enabled) { +annotorious.Annotorious.prototype.setSelectionEnabled = function (enabled) { if (enabled) this.showSelectionWidget(undefined); else @@ -398,16 +537,16 @@ annotorious.Annotorious.prototype.setSelectionEnabled = function(enabled) { * Shows existing annotations on all, or a specific item. * @param {string} opt_item_url the URL of the item */ -annotorious.Annotorious.prototype.showAnnotations = function(opt_item_url) { +annotorious.Annotorious.prototype.showAnnotations = function (opt_item_url) { if (opt_item_url) { var module = this._getModuleForItemSrc(opt_item_url); if (module) module.showAnnotations(opt_item_url); } else { - goog.array.forEach(this._modules, function(module) { + goog.array.forEach(this._modules, function (module) { module.showAnnotations(); }); - } + } } /** @@ -416,13 +555,13 @@ annotorious.Annotorious.prototype.showAnnotations = function(opt_item_url) { * annotatable items on the page. * @param {string | undefined} opt_item_url the URL of the item on which to show the selection widget */ -annotorious.Annotorious.prototype.showSelectionWidget = function(opt_item_url) { +annotorious.Annotorious.prototype.showSelectionWidget = function (opt_item_url) { if (opt_item_url) { var module = this._getModuleForItemSrc(opt_item_url); if (module) module.showSelectionWidget(opt_item_url); } else { - goog.array.forEach(this._modules, function(module) { + goog.array.forEach(this._modules, function (module) { module.showSelectionWidget(); }); } diff --git a/src/api.js b/src/api.js index 85f55d9..bd6820c 100644 --- a/src/api.js +++ b/src/api.js @@ -25,7 +25,7 @@ if (!window['annotorious']) if (!window['annotorious']['plugin']) window['annotorious']['plugin'] = {} - + /** Geometry API exports **/ if (!window['annotorious']['geometry']) { window['annotorious']['geometry'] = {}; @@ -35,3 +35,13 @@ if (!window['annotorious']['geometry']) { /** @deprecated **/ annotorious.Annotorious.prototype['setSelectionEnabled'] = annotorious.Annotorious.prototype.setSelectionEnabled; + +/** My API **/ +annotorious.Annotorious.prototype['reload'] = annotorious.Annotorious.prototype.reload; +annotorious.Annotorious.prototype['addAnnotations'] = annotorious.Annotorious.prototype.addAnnotations; +annotorious.Annotorious.prototype['setColorMode'] = annotorious.Annotorious.prototype.setColorMode; +annotorious.Annotorious.prototype['useSelectEditor'] = annotorious.Annotorious.prototype.useSelectEditor; +annotorious.Annotorious.prototype['showCursorAxes'] = annotorious.Annotorious.prototype.showCursorAxes; +annotorious.Annotorious.prototype['setArrowMode'] = annotorious.Annotorious.prototype.setArrowMode; +annotorious.Annotorious.prototype['useFancyBox'] = annotorious.Annotorious.prototype.useFancyBox; +annotorious.Annotorious.prototype['getIntersectedAnnotations'] = annotorious.Annotorious.prototype.getIntersectedAnnotations; \ No newline at end of file diff --git a/src/editor.js b/src/editor.js index f023d6b..21d6977 100644 --- a/src/editor.js +++ b/src/editor.js @@ -14,15 +14,15 @@ goog.require('annotorious.templates'); * @param {Object} annotator reference to the annotator * @constructor */ -annotorious.Editor = function(annotator) { +annotorious.Editor = function (annotator) { this.element = goog.soy.renderAsElement(annotorious.templates.editform); - + /** @private **/ this._annotator = annotator; /** @private **/ this._item = annotator.getItem(); - + /** @private **/ this._original_annotation; @@ -44,30 +44,61 @@ annotorious.Editor = function(annotator) { /** @private **/ this._extraFields = []; + /** @private **/ + this._useSelect = false; + + /** @private **/ + this._properties = { + enterText: true, + saveReadOnly: false, + textarea: { + placeholder: "Add a Comment...", //placeholder of textarea + className: "annotorious-editor-text" //className of textarea + }, + buttons: { + save: { + text: "Save", //text of save button + className: "annotorious-editor-button annotorious-editor-button-save" //className of save button + }, + abort: { + text: "Cancel",//text of abort button + className: "annotorious-editor-button annotorious-editor-button-cancel" //className of abort button + } + }, + extraFields: undefined + }; + + /** @private **/ + this._defaultProperties = JSON.parse(JSON.stringify(this._properties)); + var self = this; - goog.events.listen(this._btnCancel, goog.events.EventType.CLICK, function(event) { + goog.events.listen(this._btnCancel, goog.events.EventType.CLICK, function (event) { event.preventDefault(); annotator.stopSelection(self._original_annotation); self.close(); }); - goog.events.listen(this._btnSave, goog.events.EventType.CLICK, function(event) { + goog.events.listen(this._btnSave, goog.events.EventType.CLICK, function (event) { event.preventDefault(); + if (self._useSelect && self._select.options[0].disabled && (self._select.selectedIndex == 0 || !self._select.value)) return; //Not is possible set empty text. + + var oldText = (self._original_annotation) ? { "text": self._original_annotation.text, "textId": self._original_annotation.textId } : undefined; + var annotation = self.getAnnotation(); annotator.addAnnotation(annotation); annotator.stopSelection(); if (self._original_annotation) - annotator.fireEvent(annotorious.events.EventType.ANNOTATION_UPDATED, annotation, annotator.getItem()); + annotator.fireEvent(annotorious.events.EventType.ANNOTATION_UPDATED, annotation, oldText); else - annotator.fireEvent(annotorious.events.EventType.ANNOTATION_CREATED, annotation, annotator.getItem()); + annotator.fireEvent(annotorious.events.EventType.ANNOTATION_CREATED, annotation, annotator.getItem()); self.close(); }); - + goog.style.showElement(this.element, false); goog.dom.appendChild(annotator.element, this.element); this._textarea.decorate(goog.dom.query('.annotorious-editor-text', this.element)[0]); - annotorious.dom.makeHResizable(this.element, function() { self._textarea.resize(); }); + annotorious.dom.makeHResizable(this.element, function () { self._textarea.resize(); }); } /** @@ -76,13 +107,13 @@ annotorious.Editor = function(annotator) { * a DOM element. * @param {string | Function} field the field */ -annotorious.Editor.prototype.addField = function(field) { +annotorious.Editor.prototype.addField = function (field) { var fieldEl = goog.dom.createDom('div', 'annotorious-editor-field'); - - if (goog.isString(field)) { + + if (goog.isString(field)) { fieldEl.innerHTML = field; } else if (goog.isFunction(field)) { - this._extraFields.push({el: fieldEl, fn: field}); + this._extraFields.push({ el: fieldEl, fn: field }); } else if (goog.dom.isElement(field)) { goog.dom.appendChild(fieldEl, field); } @@ -95,22 +126,34 @@ annotorious.Editor.prototype.addField = function(field) { * @param {annotorious.Annotation=} opt_annotation the annotation to edit (or undefined) * @param {Object=} opt_event the event, if any */ -annotorious.Editor.prototype.open = function(opt_annotation, opt_event) { +annotorious.Editor.prototype.open = function (opt_annotation, opt_event) { this._annotator.fireEvent(annotorious.events.EventType.BEFORE_EDITOR_SHOWN, opt_annotation); this._original_annotation = opt_annotation; this._current_annotation = opt_annotation; - if (opt_annotation) - this._textarea.setValue(opt_annotation.text); - goog.style.showElement(this.element, true); - this._textarea.getElement().focus(); - + if (!this._useSelect) { + if (!this._properties.enterText) this._textarea.addClassName("d-none"); + else { + this._textarea.removeClassName("d-none"); + if (opt_annotation) this._textarea.setValue(opt_annotation.text); + this._textarea.getElement().focus(); + } + } + else { + if (!this._properties.enterText) this._select.classList.add("d-none"); + else { + this._select.classList.remove("d-none"); + if (opt_annotation && this._select.innerHTML.indexOf('value="' + opt_annotation.text + '"') > -1) this._select.value = opt_annotation.text; + else this._select.options[0].selected = true; + } + } + // Update extra fields (if any) - goog.array.forEach(this._extraFields, function(field) { + goog.array.forEach(this._extraFields, function (field) { var f = field.fn(opt_annotation); - if (goog.isString(f)) { + if (goog.isString(f)) { field.el.innerHTML = f; } else if (goog.dom.isElement(f)) { goog.dom.removeChildren(field.el); @@ -123,16 +166,17 @@ annotorious.Editor.prototype.open = function(opt_annotation, opt_event) { /** * Closes the editor. */ -annotorious.Editor.prototype.close = function() { +annotorious.Editor.prototype.close = function () { goog.style.showElement(this.element, false); - this._textarea.setValue(''); + if (!this._useSelect) this._textarea.setValue(''); + else this._select.value = ""; } /** * Sets the position (i.e. CSS left/top value) of the editor element. * @param {annotorious.shape.geom.Point} xy the viewport coordinate */ -annotorious.Editor.prototype.setPosition = function(xy) { +annotorious.Editor.prototype.setPosition = function (xy) { goog.style.setPosition(this.element, xy.x, xy.y); } @@ -140,21 +184,132 @@ annotorious.Editor.prototype.setPosition = function(xy) { * Returns the annotation that is the current state of the editor. * @return {annotorious.Annotation} the annotation */ -annotorious.Editor.prototype.getAnnotation = function() { - var sanitized = goog.string.html.htmlSanitize(this._textarea.getValue(), function(url) { +annotorious.Editor.prototype.getAnnotation = function () { + var sanitized = goog.string.html.htmlSanitize((this._useSelect ? this._select.value : this._textarea.getValue()), function (url) { return url; }); + var textId = (this._useSelect) ? this._select.options[this._select.selectedIndex].getAttribute("valueid") : undefined; + if (this._current_annotation) { - this._current_annotation.text = sanitized; + this._current_annotation.text = self._properties.enterText ? sanitized : undefined; + this._current_annotation.textId = textId; } else { - this._current_annotation = - new annotorious.Annotation(this._item.src, sanitized, this._annotator.getActiveSelector().getShape()); + this._current_annotation = + new annotorious.Annotation( + this._item.src, + sanitized, + this._annotator.getActiveSelector().getShape(), + undefined, + textId + ); + this._current_annotation.editable = !this._properties.saveReadOnly; } return this._current_annotation; } +/** + * Sets the properties on this editor. + */ +annotorious.Editor.prototype.setProperties = function (props) { + // TODO streamline this code -> remember: if set the property such as `undefined` or empty object (for the objects), then set the default value. + if (!(props instanceof Object) || Object.keys(props).length === 0) this._properties = JSON.parse(JSON.stringify(this._defaultProperties)); + else { + if (props.hasOwnProperty('enterText')) this._properties.enterText = (typeof props['enterText'] === "boolean") ? props['enterText'] : this._defaultProperties.enterText; + if (props.hasOwnProperty('saveReadOnly')) this._properties.saveReadOnly = props["saveReadOnly"] || this._defaultProperties.saveReadOnly; + + if (props.hasOwnProperty('textarea')) { + if (!(props['textarea'] instanceof Object) || Object.keys(props['textarea']).length === 0) this._properties.textarea = Object.assign({}, this._defaultProperties.textarea); + else { + if (props['textarea'].hasOwnProperty('placeholder')) this._properties.textarea.placeholder = props['textarea']['placeholder'] || this._defaultProperties.textarea.placeholder; + if (props['textarea'].hasOwnProperty('className')) this._properties.textarea.className = props['textarea']['className'] || this._defaultProperties.textarea.className; + } + } + if (props.hasOwnProperty('buttons')) { + if (!(props['buttons'] instanceof Object) || Object.keys(props['buttons']).length === 0) this._properties.buttons = JSON.parse(JSON.stringify(this._defaultProperties.buttons)); + else { + if (props['buttons'].hasOwnProperty('save')) { + if (!(props['buttons']['save'] instanceof Object) || Object.keys(props['buttons']['save']).length === 0) this._properties.buttons.save = Object.assign({}, this._defaultProperties.buttons.save); + else { + if (props['buttons']['save'].hasOwnProperty('text')) this._properties.buttons.save.text = props['buttons']['save']['text'] || this._defaultProperties.buttons.save.text; + if (props['buttons']['save'].hasOwnProperty('className')) this._properties.buttons.save.className = props['buttons']['save']['className'] || this._defaultProperties.buttons.save.className; + } + } + if (props['buttons'].hasOwnProperty('abort')) { + if (!(props['buttons']['abort'] instanceof Object) || Object.keys(props['buttons']['abort']).length === 0) this._properties.buttons.abort = Object.assign({}, this._defaultProperties.buttons.abort); + else { + if (props['buttons']['abort'].hasOwnProperty('text')) this._properties.buttons.abort.text = props['buttons']['abort']['text'] || this._defaultProperties.buttons.abort.text; + if (props['buttons']['abort'].hasOwnProperty('className')) this._properties.buttons.abort.className = props['buttons']['abort']['className'] || this._defaultProperties.buttons.abort.className; + } + } + } + } + if (props.hasOwnProperty('extraFields')) this._properties.extraFields = props["extraFields"]; + } + + //Apply the properties + this._textarea.getElement().placeholder = this._properties.textarea.placeholder; + this._textarea.getElement().className = this._properties.textarea.className; + this._btnSave.innerHTML = this._properties.buttons.save.text; + this._btnSave.className = this._properties.buttons.save.className; + this._btnCancel.innerHTML = this._properties.buttons.abort.text; + this._btnCancel.className = this._properties.buttons.abort.className; + + var container = goog.dom.query('.annotorious-editor-field', this.element)[0]; + if (container) container.remove(); + if (this._properties.extraFields && Array.isArray(this._properties.extraFields)) { + var self = this; + goog.array.forEach(this._properties.extraFields, function (field) { + self.addField(field); + }); + } +} + +/** + * Enables (or disables) the ability to use select (dropdown menu) instead of textarea + * @param {Object} selectEditor {enabled:bool, options:array[Object], emptyOption:bool, customLabel:string} if is enabled, options of select, true to enable the empty select option, the custom first label if not use empty options + */ +annotorious.Editor.prototype.setSelectEditor = function (selectEditor) { + var optionIsArray = false; + this._useSelect = (selectEditor && selectEditor instanceof Object && selectEditor["enabled"] && (optionIsArray = Array.isArray(selectEditor["options"]))) ? true : false; + + if (!this._useSelect) { + if (!optionIsArray) console.log('WARNING: invalid option format'); + this._textarea.removeClassName("d-none"); + if (this._select) { + this._select.remove(); + delete this._select; + } + return; + } + + this._textarea.addClassName("d-none"); + if (!this._select) { + this._select = document.createElement("select"); + this._select.classList.add("annotorious-editor-text"); + } else this._select.innerHTML = ""; + + var self = this; + var newOptions = selectEditor["options"].slice(); + newOptions.unshift({ "value": "" }) + goog.array.forEach(newOptions, function (option) { + var opt = new Option(option["value"], option["value"]); + if (option["id"]) { + var attr = document.createAttribute("valueid"); + attr.value = option["id"]; + opt.setAttributeNode(attr); + } + self._select.appendChild(opt); + }); + + if (!selectEditor["emptyOption"]) { + this._select.options[0].disabled = true; + this._select.options[0].innerHTML = selectEditor["customLabel"] || "<--- Select one option --->" + } + goog.dom.insertSiblingBefore(this._select, this._btnContainer); +} + /** API exports **/ annotorious.Editor.prototype['addField'] = annotorious.Editor.prototype.addField; annotorious.Editor.prototype['getAnnotation'] = annotorious.Editor.prototype.getAnnotation; diff --git a/src/events.js b/src/events.js index 19d9e93..86248da 100644 --- a/src/events.js +++ b/src/events.js @@ -7,7 +7,7 @@ goog.require('goog.events'); * A central 'event bus' to distribute the annotation lifecycle events. * @constructor */ -annotorious.events.EventBroker = function() { +annotorious.events.EventBroker = function () { /** @private **/ this._handlers = []; } @@ -17,11 +17,11 @@ annotorious.events.EventBroker = function() { * @param {annotorious.events.EventType} type the event type * @param {Function} handler the handler function to add */ -annotorious.events.EventBroker.prototype.addHandler = function(type, handler) { - if (!this._handlers[type]) +annotorious.events.EventBroker.prototype.addHandler = function (type, handler) { + if (!this._handlers[type]) this._handlers[type] = []; - this._handlers[type].push(handler); + this._handlers[type].push(handler); } /** @@ -29,10 +29,10 @@ annotorious.events.EventBroker.prototype.addHandler = function(type, handler) { * @param {annotorious.events.EventType} type the event type * @param {Function} handler the handler function to remove */ -annotorious.events.EventBroker.prototype.removeHandler = function(type, handler) { +annotorious.events.EventBroker.prototype.removeHandler = function (type, handler) { var handlers = this._handlers[type]; if (handlers) - goog.array.remove(handlers, handler); + goog.array.remove(handlers, handler); } /** @@ -45,16 +45,16 @@ annotorious.events.EventBroker.prototype.removeHandler = function(type, handler) * @param {Object=} opt_event the event object * @return {boolean} the 'cancel event' flag */ -annotorious.events.EventBroker.prototype.fireEvent = function(type, opt_event, opt_extra) { +annotorious.events.EventBroker.prototype.fireEvent = function (type, opt_event, opt_extra) { var cancelEvent = false; var handlers = this._handlers[type]; if (handlers) { - goog.array.forEach(handlers, function(handler, idx, array) { + goog.array.forEach(handlers, function (handler, idx, array) { var retVal = handler(opt_event, opt_extra); if (goog.isDef(retVal) && !retVal) cancelEvent = true; }); - } + } return cancelEvent; } @@ -70,6 +70,11 @@ annotorious.events.EventType = { */ MOUSE_OVER_ANNOTATABLE_ITEM: 'onMouseOverItem', + /** + * The mouse move inside the annotatable media area + */ + MOUSE_MOVE_ANNOTATABLE_ITEM: 'onMouseMoveOverItem', + /** * The mouse moved out of the annotatable media area */ @@ -77,35 +82,40 @@ annotorious.events.EventType = { /** * The mouse entered an annotation - */ + */ MOUSE_OVER_ANNOTATION: 'onMouseOverAnnotation', + /** + * The mouse entered an annotation and draw pixel (drawPixel Mode) + */ + DRAWN_PIXELS: 'onDrawnPixels', + /** * The mouse moved out of an annotation - */ + */ MOUSE_OUT_OF_ANNOTATION: 'onMouseOutOfAnnotation', /** * A new selection was started */ SELECTION_STARTED: 'onSelectionStarted', - + /** * The current selection was canceled */ SELECTION_CANCELED: 'onSelectionCanceled', - + /** * The current selection was completed */ SELECTION_COMPLETED: 'onSelectionCompleted', - + /** * The current selection was changed */ SELECTION_CHANGED: 'onSelectionChanged', - + /** * The annotation editor is opening. Pass the annotation object if it exists. */ @@ -115,7 +125,7 @@ annotorious.events.EventType = { * The annotation editor was opened. Pass the annotation object if it exists. */ EDITOR_SHOWN: 'onEditorShown', - + /** * The annotation popop was opened. Pass the annotation object. */ @@ -140,7 +150,7 @@ annotorious.events.EventType = { * An annotation was created */ ANNOTATION_CREATED: 'onAnnotationCreated', - + /** * An existing annotation was updated */ @@ -149,6 +159,15 @@ annotorious.events.EventType = { /** * The annotation was clicked. Pass the annotation object. */ - ANNOTATION_CLICKED: 'onAnnotationClicked' - + ANNOTATION_CLICKED: 'onAnnotationClicked', + + /** + * The annotation was moved. Pass the annotation object. + */ + ANNOTATION_MOVED: 'onAnnotationMoved', + + /** + * The annotation was rotated. Pass the annotation object. + */ + ANNOTATION_ROTATED: 'onAnnotationRotated' }; diff --git a/src/mediatypes/annotator.js b/src/mediatypes/annotator.js index 56b2db0..b593e02 100644 --- a/src/mediatypes/annotator.js +++ b/src/mediatypes/annotator.js @@ -4,70 +4,101 @@ goog.provide('annotorious.mediatypes.Annotator'); * A base class for Annotorious Annotator implementations. * @constructor */ -annotorious.mediatypes.Annotator = function() { } +annotorious.mediatypes.Annotator = function () { } -annotorious.mediatypes.Annotator.prototype.addAnnotation = function(annotation, opt_replace) { +annotorious.mediatypes.Annotator.prototype.addAnnotation = function (annotation, opt_replace) { this._viewer.addAnnotation(annotation, opt_replace); } -annotorious.mediatypes.Annotator.prototype.addHandler = function(type, handler) { - this._eventBroker.addHandler(type, handler); +/** + * Retrieves all annotations that intersect the centroid of an annotation + * @param {annotorious.Annotation} annotation annotation from which to retrieve the centroid + * @param {String} type filter by shape type + * @return {Array.} the annotations sorted by size, smallest first + */ +annotorious.mediatypes.Annotator.prototype.getIntersectedAnnotations = function (annotation, type) { + var self = this; + var shape = (annotation.shapes[0].units == annotorious.shape.Units.PIXEL) ? annotorious.shape.transform(annotation.shapes[0], function (xy) { return self.fromItemPixelCoordinates(xy); }) : + annotorious.shape.transform(annotation.shapes[0], function (xy) { return self.fromItemCoordinates(xy); }); + + var point = annotorious.shape.getCentroid(shape); + var annotations = this.getAnnotationsAt(point.x, point.y); + + return annotations.filter(function (annota) { + var notSelfAnno = annotorious.shape.hashCode(annota.shapes[0]) != annotorious.shape.hashCode(annotation.shapes[0]); + return notSelfAnno && (!type || annota.shapes[0].type == type); + }); } -annotorious.mediatypes.Annotator.prototype.removeHandler = function(type) { - this._eventBroker.removeHandler(type); +annotorious.mediatypes.Annotator.prototype.addHandler = function (type, handler) { + this._eventBroker.addHandler(type, handler); } -annotorious.mediatypes.Annotator.prototype.fireEvent = function(type, event, opt_extra) { +annotorious.mediatypes.Annotator.prototype.removeHandler = function (type) { + this._eventBroker.removeHandler(type); +} + +annotorious.mediatypes.Annotator.prototype.fireEvent = function (type, event, opt_extra) { return this._eventBroker.fireEvent(type, event, opt_extra); } -annotorious.mediatypes.Annotator.prototype.getActiveSelector = function() { +annotorious.mediatypes.Annotator.prototype.getActiveSelector = function () { return this._currentSelector; } -annotorious.mediatypes.Annotator.prototype.highlightAnnotation = function(annotation) { +annotorious.mediatypes.Annotator.prototype.highlightAnnotation = function (annotation) { this._viewer.highlightAnnotation(annotation); } -annotorious.mediatypes.Annotator.prototype.removeAnnotation = function(annotation) { +annotorious.mediatypes.Annotator.prototype.removeAnnotation = function (annotation) { this._viewer.removeAnnotation(annotation); } -annotorious.mediatypes.Annotator.prototype.removeHandler = function(type, handler) { +annotorious.mediatypes.Annotator.prototype.removeHandler = function (type, handler) { this._eventBroker.removeHandler(type, handler); } -annotorious.mediatypes.Annotator.prototype.stopSelection = function(original_annotation) { +annotorious.mediatypes.Annotator.prototype.stopSelection = function (original_annotation) { if (annotorious.events.ui.hasMouse) goog.style.showElement(this._editCanvas, false); - + if (this._stop_selection_callback) { this._stop_selection_callback(); delete this._stop_selection_callback; } this._currentSelector.stopSelection(); - + // If this was an edit of an annotation (rather than creation of a new one) re-add to viewer! if (original_annotation) this._viewer.addAnnotation(original_annotation); } -annotorious.mediatypes.Annotator.prototype._attachListener = function(activeCanvas) { +annotorious.mediatypes.Annotator.prototype._attachListener = function (activeCanvas) { var self = this; - goog.events.listen(activeCanvas, annotorious.events.ui.EventType.DOWN, function(event) { - console.log('start selection event'); - console.log(event); + goog.events.listen(activeCanvas, annotorious.events.ui.EventType.DOWN, function (event) { + //console.log('start selection event'); + //console.log(event); var coords = annotorious.events.ui.sanitizeCoordinates(event, activeCanvas); self._viewer.highlightAnnotation(false); - if (self._selectionEnabled) { - goog.style.showElement(self._editCanvas, true); - self._currentSelector.startSelection(coords.x, coords.y); - } else { - var annotations = self._viewer.getAnnotationsAt(coords.x, coords.y); - if (annotations.length > 0) - self._viewer.highlightAnnotation(annotations[0]); - } - }); + + var annotations = undefined; + if (self._selectionEnabled) { + + if (self._drawInsideRectAnno) { + annotations = self._viewer.getAnnotationsAt(coords.x, coords.y); + if (!annotations.length) return; + annotations = annotations.filter(function (anno) { + return anno.shapes[0].type == annotorious.shape.ShapeType.RECTANGLE; + }); + if (!annotations.length) return; + } + goog.style.showElement(self._editCanvas, true); + self._currentSelector.startSelection(coords.x, coords.y, annotations ? self._viewer.getSystemShape(annotations[0]) : undefined); + } else { + annotations = self._viewer.getAnnotationsAt(coords.x, coords.y); + if (annotations.length > 0) + self._viewer.highlightAnnotation(annotations[0]); + } + }); } diff --git a/src/mediatypes/image/image.annotator.js b/src/mediatypes/image/image.annotator.js index f54df63..5e5a9a2 100644 --- a/src/mediatypes/image/image.annotator.js +++ b/src/mediatypes/image/image.annotator.js @@ -14,6 +14,7 @@ goog.require('annotorious.Popup'); goog.require('annotorious.mediatypes.Annotator'); goog.require('annotorious.mediatypes.image.Viewer'); goog.require('annotorious.plugins.selection.RectDragSelector'); +goog.require('annotorious.plugins.selection.ArrowDragSelector'); goog.require('annotorious.templates.image'); /** @@ -22,14 +23,14 @@ goog.require('annotorious.templates.image'); * @param {annotorious.Popup=} opt_popup a popup implementation to use instead of the default one * @constructor */ -annotorious.mediatypes.image.ImageAnnotator = function(item, opt_popup) { +annotorious.mediatypes.image.ImageAnnotator = function (item, opt_popup) { annotorious.mediatypes.Annotator.call(); var hint; - + /** The container DOM element (DIV) for the annotation layer **/ this.element; - + /** The editor for this annotator (public for use by plugins) **/ this.editor; @@ -41,28 +42,48 @@ annotorious.mediatypes.image.ImageAnnotator = function(item, opt_popup) { /** @private **/ this._original_bufferspace = { padding: item.style.padding, margin: item.style.margin } - + /** @private **/ this._viewer; - + /** @private **/ this._editCanvas; - + /** @private **/ this._hint; - + + /** @private **/ + this._displayMessage = undefined; + + /** @private **/ + this.outputUnits = annotorious.shape.Units.FRACTION; + /** @private **/ this._eventBroker = new annotorious.events.EventBroker(); /** @private **/ this._selectors = []; - + /** @private **/ this._currentSelector; /** @private **/ this._selectionEnabled = true; + /** @private **/ + this._drawInsideRectAnno = false; + + /** @private **/ + this._cursorAxes = { + enabled: false, + dash: false, + color: "#ffffff", + strokeWidth: 2 + }; + + /** @private **/ + this._defaultCursorAxes = Object.assign({}, this._cursorAxes); + this.element = goog.dom.createDom('div', 'annotorious-annotationlayer'); goog.style.setStyle(this.element, 'position', 'relative'); goog.style.setStyle(this.element, 'display', 'inline-block'); @@ -71,18 +92,23 @@ annotorious.mediatypes.image.ImageAnnotator = function(item, opt_popup) { goog.dom.replaceNode(this.element, item); goog.dom.appendChild(this.element, item); - var img_bounds = goog.style.getBounds(item); + var img_bounds = goog.style.getBounds(item); + + this._cursorCanvas = goog.soy.renderAsElement(annotorious.templates.image.canvas, + { width: img_bounds.width, height: img_bounds.height }); + goog.dom.appendChild(this.element, this._cursorCanvas); + this._viewCanvas = goog.soy.renderAsElement(annotorious.templates.image.canvas, - { width:img_bounds.width, height:img_bounds.height }); + { width: img_bounds.width, height: img_bounds.height }); if (annotorious.events.ui.hasMouse) goog.dom.classes.add(this._viewCanvas, 'annotorious-item-unfocus'); - goog.dom.appendChild(this.element, this._viewCanvas); + goog.dom.appendChild(this.element, this._viewCanvas); - this._editCanvas = goog.soy.renderAsElement(annotorious.templates.image.canvas, - { width:img_bounds.width, height:img_bounds.height }); + this._editCanvas = goog.soy.renderAsElement(annotorious.templates.image.canvas, + { width: img_bounds.width, height: img_bounds.height }); if (annotorious.events.ui.hasMouse) - goog.style.showElement(this._editCanvas, false); + goog.style.showElement(this._editCanvas, false); goog.dom.appendChild(this.element, this._editCanvas); if (opt_popup) @@ -91,26 +117,25 @@ annotorious.mediatypes.image.ImageAnnotator = function(item, opt_popup) { this.popup = new annotorious.Popup(this); var default_selector = new annotorious.plugins.selection.RectDragSelector(); - default_selector.init(this, this._editCanvas); - this._selectors.push(default_selector); + this.addSelector(default_selector); + this.addSelector(new annotorious.plugins.selection.ArrowDragSelector()); this._currentSelector = default_selector; this.editor = new annotorious.Editor(this); - this._viewer = new annotorious.mediatypes.image.Viewer(this._viewCanvas, this); + this._viewer = new annotorious.mediatypes.image.Viewer(this._viewCanvas, this); this._hint = new annotorious.Hint(this, this.element); - - var self = this; + var self = this; if (annotorious.events.ui.hasMouse) { - goog.events.listen(this.element, annotorious.events.ui.EventType.OVER, function(event) { + goog.events.listen(this.element, annotorious.events.ui.EventType.OVER, function (event) { var relatedTarget = event.relatedTarget; if (!relatedTarget || !goog.dom.contains(self.element, relatedTarget)) { self._eventBroker.fireEvent(annotorious.events.EventType.MOUSE_OVER_ANNOTATABLE_ITEM); goog.dom.classes.addRemove(self._viewCanvas, 'annotorious-item-unfocus', 'annotorious-item-focus'); } }); - - goog.events.listen(this.element, annotorious.events.ui.EventType.OUT, function(event) { + + goog.events.listen(this.element, annotorious.events.ui.EventType.OUT, function (event) { var relatedTarget = event.relatedTarget; if (!relatedTarget || !goog.dom.contains(self.element, relatedTarget)) { self._eventBroker.fireEvent(annotorious.events.EventType.MOUSE_OUT_OF_ANNOTATABLE_ITEM); @@ -122,14 +147,14 @@ annotorious.mediatypes.image.ImageAnnotator = function(item, opt_popup) { var activeCanvas = (annotorious.events.ui.hasTouch) ? this._editCanvas : this._viewCanvas; this._attachListener(activeCanvas); - this._eventBroker.addHandler(annotorious.events.EventType.SELECTION_COMPLETED, function(event) { + this._eventBroker.addHandler(annotorious.events.EventType.SELECTION_COMPLETED, function (event) { var bounds = event.viewportBounds; self.editor.setPosition(new annotorious.shape.geom.Point(bounds.left + self._image.offsetLeft, - bounds.bottom + 4 + self._image.offsetTop)); + bounds.bottom + 4 + self._image.offsetTop)); self.editor.open(false, event); }); - - this._eventBroker.addHandler(annotorious.events.EventType.SELECTION_CANCELED, function() { + + this._eventBroker.addHandler(annotorious.events.EventType.SELECTION_CANCELED, function () { if (annotorious.events.ui.hasMouse) goog.style.showElement(self._editCanvas, false); self._currentSelector.stopSelection(); @@ -141,16 +166,16 @@ goog.inherits(annotorious.mediatypes.image.ImageAnnotator, annotorious.mediatype * Helper function to transfer relevant styles from the to the annotation layer
element. * @private */ -annotorious.mediatypes.image.ImageAnnotator.prototype._transferStyles = function(image, annotationLayer) { - var transferMargin = function(direction, value) { - goog.style.setStyle(annotationLayer, 'margin-' + direction, value + 'px'); +annotorious.mediatypes.image.ImageAnnotator.prototype._transferStyles = function (image, annotationLayer) { + var transferMargin = function (direction, value) { + goog.style.setStyle(annotationLayer, 'margin-' + direction, value + 'px'); goog.style.setStyle(image, 'margin-' + direction, 0); goog.style.setStyle(image, 'padding-' + direction, 0); } var margin = goog.style.getMarginBox(image); var padding = goog.style.getPaddingBox(image); - + if (margin.top != 0 || padding.top != 0) transferMargin('top', margin.top + padding.top); @@ -159,7 +184,7 @@ annotorious.mediatypes.image.ImageAnnotator.prototype._transferStyles = function if (margin.bottom != 0 || padding.bottom != 0) transferMargin('bottom', margin.bottom + padding.bottom); - + if (margin.left != 0 || padding.left != 0) transferMargin('left', margin.left + padding.left); } @@ -167,20 +192,20 @@ annotorious.mediatypes.image.ImageAnnotator.prototype._transferStyles = function /** * NOT NEEDED/SUPPORTED on ImageAnnotator. */ -annotorious.mediatypes.image.ImageAnnotator.prototype.activateSelector = function(callback) { } +annotorious.mediatypes.image.ImageAnnotator.prototype.activateSelector = function (callback) { } /** * Adds a selector */ -annotorious.mediatypes.image.ImageAnnotator.prototype.addSelector = function(selector) { - selector.init(this, this._editCanvas); +annotorious.mediatypes.image.ImageAnnotator.prototype.addSelector = function (selector) { + selector.init(this, this._editCanvas); this._selectors.push(selector); } /** * Destroys this annotator instance. */ -annotorious.mediatypes.image.ImageAnnotator.prototype.destroy = function() { +annotorious.mediatypes.image.ImageAnnotator.prototype.destroy = function () { var img = this._image; img.style.margin = this._original_bufferspace.margin; img.style.padding = this._original_bufferspace.padding; @@ -191,59 +216,58 @@ annotorious.mediatypes.image.ImageAnnotator.prototype.destroy = function() { * Edits the specified existing annotation. * @param {annotorious.Annotation} annotation the annotation */ -annotorious.mediatypes.image.ImageAnnotator.prototype.editAnnotation = function(annotation) { +annotorious.mediatypes.image.ImageAnnotator.prototype.editAnnotation = function (annotation) { // Step 1 - remove from viewer this._viewer.removeAnnotation(annotation); - + // Step 2 - find a suitable selector for the shape - var selector = goog.array.find(this._selectors, function(selector) { - return selector.getSupportedShapeType() == annotation.shapes[0].type; + var selector = goog.array.find(this._selectors, function (selector) { + var types = selector.getSupportedShapeType(); + return Array.isArray(types) ? goog.array.indexOf(types, annotation.shapes[0].type) != -1 : types == annotation.shapes[0].type; }); - + + var shape = annotation.shapes[0]; + var self = this; + var viewportShape = (shape.units == annotorious.shape.Units.PIXEL) ? annotorious.shape.transform(shape, function (xy) { return self.fromItemPixelCoordinates(xy); }) : annotorious.shape.transform(shape, function (xy) { return self.fromItemCoordinates(xy); }); + // Step 3 - open annotation in editor if (selector) { goog.style.showElement(this._editCanvas, true); this._viewer.highlightAnnotation(false); - + // TODO make editable - not just draw (selector implementation required) var g2d = this._editCanvas.getContext('2d'); - var shape = annotation.shapes[0]; - - var self = this; - var viewportShape = (shape.units == 'pixel') ? shape : annotorious.shape.transform(shape, function(xy) { return self.fromItemCoordinates(xy); }) ; selector.drawShape(g2d, viewportShape); } - - var bounds = annotorious.shape.getBoundingRect(annotation.shapes[0]).geometry; - var anchor = (annotation.shapes[0].units == 'pixel') ? - new annotorious.shape.geom.Point(bounds.x, bounds.y + bounds.height) : - this.fromItemCoordinates(new annotorious.shape.geom.Point(bounds.x, bounds.y + bounds.height)); - + + var bounds = annotorious.shape.getBoundingRect(viewportShape).geometry; + var anchor = new annotorious.shape.geom.Point(bounds.x, bounds.y + bounds.height); this.editor.setPosition(new annotorious.shape.geom.Point(anchor.x + this._image.offsetLeft, - anchor.y + 4 + this._image.offsetTop)); - this.editor.open(annotation); + anchor.y + 4 + this._image.offsetTop)); + this.editor.open(annotation); } /** - * Converts the specified viewport coordinate to the - * coordinate system used by the annotatable item. - * @param {annotorious.shape.geom.Point} xy the viewport coordinate - * @returns the corresponding item coordinate + * Move the specified existing annotation. + * @param {annotorious.Annotation} annotation the annotation */ -annotorious.mediatypes.image.ImageAnnotator.prototype.fromItemCoordinates = function(xy_wh) { - var imgSize = goog.style.getSize(this._image); - if (xy_wh.width) { - return { x: xy_wh.x * imgSize.width, y: xy_wh.y * imgSize.height, width: xy_wh.width * imgSize.width, height: xy_wh.height * imgSize.height }; - } else { - return { x: xy_wh.x * imgSize.width, y: xy_wh.y * imgSize.height }; - } +annotorious.mediatypes.image.ImageAnnotator.prototype.moveAnnotation = function (annotation) { + this._viewer.moveAnnotation(annotation); +} + +/** + * Rotate the specified existing annotation. + * @param {annotorious.Annotation} annotation the annotation + */ +annotorious.mediatypes.image.ImageAnnotator.prototype.rotateAnnotation = function (annotation) { + this._viewer.rotateAnnotation(annotation); } /** * Returns the currently active selector. * @returns {Object} the currently active selector */ -annotorious.mediatypes.image.ImageAnnotator.prototype.getActiveSelector = function() { +annotorious.mediatypes.image.ImageAnnotator.prototype.getActiveSelector = function () { return this._currentSelector; } @@ -251,7 +275,7 @@ annotorious.mediatypes.image.ImageAnnotator.prototype.getActiveSelector = functi * Returns all annotations on the annotatable media. * @returns {Array.} the annotations */ -annotorious.mediatypes.image.ImageAnnotator.prototype.getAnnotations = function() { +annotorious.mediatypes.image.ImageAnnotator.prototype.getAnnotations = function () { return this._viewer.getAnnotations(); } @@ -261,7 +285,7 @@ annotorious.mediatypes.image.ImageAnnotator.prototype.getAnnotations = function( * @param {number} cy the client Y coordinate * @return {Array.} the annotations sorted by size, smallest first */ -annotorious.mediatypes.image.ImageAnnotator.prototype.getAnnotationsAt = function(cx, cy) { +annotorious.mediatypes.image.ImageAnnotator.prototype.getAnnotationsAt = function (cx, cy) { return goog.array.clone(this._viewer.getAnnotationsAt(cx, cy)); } @@ -269,7 +293,7 @@ annotorious.mediatypes.image.ImageAnnotator.prototype.getAnnotationsAt = functio * Returns the available selectors for this item. * @returns {Array.} the list of selectors */ -annotorious.mediatypes.image.ImageAnnotator.prototype.getAvailableSelectors = function() { +annotorious.mediatypes.image.ImageAnnotator.prototype.getAvailableSelectors = function () { return this._selectors; } @@ -277,7 +301,7 @@ annotorious.mediatypes.image.ImageAnnotator.prototype.getAvailableSelectors = fu * Returns the image that this annotator is responsible for. * @returns {Object} the image */ -annotorious.mediatypes.image.ImageAnnotator.prototype.getItem = function() { +annotorious.mediatypes.image.ImageAnnotator.prototype.getItem = function () { return { src: annotorious.mediatypes.image.ImageAnnotator.getItemURL(this._image), element: this._image }; } @@ -289,7 +313,7 @@ annotorious.mediatypes.image.ImageAnnotator.prototype.getItem = function() { * @param {Element} item the image DOM element * @return {string} the URL */ -annotorious.mediatypes.image.ImageAnnotator.getItemURL = function(item) { +annotorious.mediatypes.image.ImageAnnotator.getItemURL = function (item) { var src = item.getAttribute('data-original'); if (src) return src; @@ -300,14 +324,14 @@ annotorious.mediatypes.image.ImageAnnotator.getItemURL = function(item) { /** * Hides annotations (and all other Annotorious elements). */ -annotorious.mediatypes.image.ImageAnnotator.prototype.hideAnnotations = function() { +annotorious.mediatypes.image.ImageAnnotator.prototype.hideAnnotations = function () { goog.style.showElement(this._viewCanvas, false); } /** * Hides the selection widget, thus preventing users from creating new annotations. */ -annotorious.mediatypes.image.ImageAnnotator.prototype.hideSelectionWidget = function() { +annotorious.mediatypes.image.ImageAnnotator.prototype.hideSelectionWidget = function () { this._selectionEnabled = false; if (this._hint) { this._hint.destroy(); @@ -318,14 +342,18 @@ annotorious.mediatypes.image.ImageAnnotator.prototype.hideSelectionWidget = func /** * Sets the active selector for this item to the specified selector. * @param {Object} selector the selector object + * @return {Boolean} true if the selector was found */ -annotorious.mediatypes.image.ImageAnnotator.prototype.setCurrentSelector = function(selector) { - this._currentSelector = goog.array.find(this._selectors, function(sel) { +annotorious.mediatypes.image.ImageAnnotator.prototype.setCurrentSelector = function (selector) { + this._currentSelector = goog.array.find(this._selectors, function (sel) { return sel.getName() == selector; }); - if (!this._currentSelector) - console.log('WARNING: selector "' + selector + '" not available'); + if (!this._currentSelector) { + console.log('WARNING: selector "' + selector + '" not available'); + return false; + } + return true; } /** @@ -333,57 +361,223 @@ annotorious.mediatypes.image.ImageAnnotator.prototype.setCurrentSelector = funct * affecting the selectors). * @param {Object} props the properties object */ -annotorious.mediatypes.image.ImageAnnotator.prototype.setProperties = function(props) { - goog.array.forEach(this._selectors, function(selector) { - selector.setProperties(props); - }); - this._viewer.redraw(); +annotorious.mediatypes.image.ImageAnnotator.prototype.setProperties = function (props) { + + /** DisplayMessage **/ + if (props.hasOwnProperty("displayMessage")) { + this._displayMessage = props["displayMessage"]; + if (this._selectionEnabled) { + this.hideSelectionWidget(); + this.showSelectionWidget(); + } + } + + /** Output Shape Units **/ + if (props.hasOwnProperty("outputUnits")) + this.outputUnits = (props["outputUnits"] == annotorious.shape.Units.PIXEL) ? annotorious.shape.Units.PIXEL : annotorious.shape.Units.FRACTION; + + /** Draw Only Inside Rect Annotation **/ + if (props.hasOwnProperty("drawInsideRectAnno")) + this._drawInsideRectAnno = props["drawInsideRectAnno"]; + + /** Color Mode **/ + if (props.hasOwnProperty("colorMode")) + this._viewer.setColorMode(props["colorMode"]); + + /** Select Editor **/ + if (props.hasOwnProperty("selectEditor")) + this.editor.setSelectEditor(props["selectEditor"]); + + /** Cursor Axes **/ + if (props.hasOwnProperty("cursorAxes")) + this._showCursorAxes(props["cursorAxes"]); + + /** Arrow Mode **/ + if (props.hasOwnProperty("arrowMode")) + this._setDrawArrowMode(props["arrowMode"]); + + /** Editor Properties **/ + if (props.hasOwnProperty("editor")) + this.editor.setProperties(props["editor"]); + + /** Popup Properties **/ + if (props.hasOwnProperty("popup")) + this.popup.setProperties(props["popup"]); + + /** Fancy Box Selector **/ + if (props.hasOwnProperty("fancyBox")) { + var default_selector = goog.array.find(this._selectors, function (sel) { + return sel.getName() == "rect_drag"; + }); + default_selector.setFancyBox(props["fancyBox"]); + } + + /** Shape Style **/ + if (props.hasOwnProperty("shapeStyle")) { + goog.array.forEach(this._selectors, function (selector) { + selector.setProperties(props["shapeStyle"]); + }); + this._viewer.redraw(); + } } /** * Shows annotations (and all other Annotorious elements). */ -annotorious.mediatypes.image.ImageAnnotator.prototype.showAnnotations = function() { +annotorious.mediatypes.image.ImageAnnotator.prototype.showAnnotations = function () { goog.style.showElement(this._viewCanvas, true); } /** * Shows the selection widget, thus enabling users to create new annotations. */ -annotorious.mediatypes.image.ImageAnnotator.prototype.showSelectionWidget = function() { - this._selectionEnabled = true; +annotorious.mediatypes.image.ImageAnnotator.prototype.showSelectionWidget = function () { + this._selectionEnabled = true; if (!this._hint) - this._hint = new annotorious.Hint(this, this.element); + this._hint = new annotorious.Hint(this, this.element, this._displayMessage); } /** * Stops the selection (if any). * @param {annotorious.Annotation=} opt_original_annotation the original annotation being edited (if any) */ -annotorious.mediatypes.image.ImageAnnotator.prototype.stopSelection = function(opt_original_annotation) { - if (annotorious.events.ui.hasMouse) - goog.style.showElement(this._editCanvas, false); - - this._currentSelector.stopSelection(); - - // If this was an edit of an annotation (rather than creation of a new one) re-add to viewer! - if (opt_original_annotation) - this._viewer.addAnnotation(opt_original_annotation); +annotorious.mediatypes.image.ImageAnnotator.prototype.stopSelection = function (opt_original_annotation) { + if (annotorious.events.ui.hasMouse) + goog.style.showElement(this._editCanvas, false); + + this._currentSelector.stopSelection(); + + // If this was an edit of an annotation (rather than creation of a new one) re-add to viewer! + if (opt_original_annotation) + this._viewer.addAnnotation(opt_original_annotation); } /** - * Converts the specified coordinate from the - * coordinate system used by the annotatable item to viewport coordinates. - * @param {annotorious.shape.geom.Point} xy the item coordinate - * @returns the corresponding viewport coordinate + * Converts the geometry 'fraction' coordinate to the coordinate used by system. + * @param {annotorious.shape.geom.Point | annotorious.shape.geom.Rectangle} xy_wh the geometry 'fraction' coordinate + * @returns the corresponding coordinate used by system */ -annotorious.mediatypes.image.ImageAnnotator.prototype.toItemCoordinates = function(xy_wh) { - var imgSize = goog.style.getSize(this._image); +annotorious.mediatypes.image.ImageAnnotator.prototype.fromItemCoordinates = function (xy_wh) { + var imgSize = goog.style.getSize(this._image); + var systemPixel = { x: xy_wh.x * imgSize.width, y: xy_wh.y * imgSize.height }; if (xy_wh.width) { - return { x: xy_wh.x / imgSize.width, y: xy_wh.y / imgSize.height, width: xy_wh.width / imgSize.width, height: xy_wh.height /imgSize.height }; - } else { - return { x: xy_wh.x / imgSize.width, y: xy_wh.y / imgSize.height }; + systemPixel.width = xy_wh.width * imgSize.width; + systemPixel.height = xy_wh.height * imgSize.height; } + return systemPixel; +} + +/** + * Converts the geometry 'pixel' coordinate to the coordinate used by system. + * [pixels are relative to the original image size] + * @param {annotorious.shape.geom.Point | annotorious.shape.geom.Rectangle} xy_wh the geometry 'pixel' coordinate + * @returns the corresponding coordinate used by system + */ +annotorious.mediatypes.image.ImageAnnotator.prototype.fromItemPixelCoordinates = function (xy_wh) { + var imgSize = goog.style.getSize(this._image); + + var systemPixel = { x: parseInt((xy_wh.x / this._image.naturalWidth) * imgSize.width), y: parseInt((xy_wh.y / this._image.naturalHeight) * imgSize.height) }; + if (xy_wh.width) { + systemPixel.width = parseInt((xy_wh.width / this._image.naturalWidth) * imgSize.width); + systemPixel.height = parseInt((xy_wh.height / this._image.naturalHeight) * imgSize.height); + } + + return systemPixel; +} + +/** + * Converts the specified coordinate used by system to geometry 'fraction' coordinate + * @param {annotorious.shape.geom.Point | annotorious.shape.geom.Rectangle} xy_wh the system coordinate + * @returns the corresponding geometry 'fraction' coordinate + */ +annotorious.mediatypes.image.ImageAnnotator.prototype.toItemCoordinates = function (xy_wh) { + var imgSize = goog.style.getSize(this._image); + var newGeo = { x: xy_wh.x / imgSize.width, y: xy_wh.y / imgSize.height }; + if (xy_wh.width) { + newGeo.width = xy_wh.width / imgSize.width; + newGeo.height = xy_wh.height / imgSize.height; + } + return newGeo; +} + +/** + * Converts the specified coordinate used by system to geometry 'pixel' coordinate + * [pixels are relative to the original image size] + * @param {annotorious.shape.geom.Point | annotorious.shape.geom.Rectangle} xy_wh the system coordinate + * @returns the corresponding geometry 'pixel' coordinate + */ +annotorious.mediatypes.image.ImageAnnotator.prototype.toItemPixelCoordinates = function (xy_wh) { + var imgSize = goog.style.getSize(this._image); + var newGeo = { x: parseInt((xy_wh.x * this._image.naturalWidth) / imgSize.width), y: parseInt((xy_wh.y * this._image.naturalHeight) / imgSize.height) }; + if (xy_wh.width) { + newGeo.width = parseInt((xy_wh.width * this._image.naturalWidth) / imgSize.width); + newGeo.height = parseInt((xy_wh.height * this._image.naturalHeight) / imgSize.height); + } + return newGeo; +} + + +/** + * Enable or Disable show cursor axes + * @param {Object} cursorAxes {enabled:bool, dash:bool, color:string, strokeWidth:int} if is enabled, if draw dashed line, color of axes, stroke width of axes + */ +annotorious.mediatypes.image.ImageAnnotator.prototype._showCursorAxes = function (cursorAxes) { + if (!(cursorAxes instanceof Object) || Object.keys(cursorAxes).length === 0) this._cursorAxes = Object.assign({ _listener: this._cursorAxes._listener }, this._defaultCursorAxes); + else { + if (cursorAxes.hasOwnProperty('enabled')) + this._cursorAxes.enabled = cursorAxes['enabled'] || this._defaultCursorAxes.enabled; + if (cursorAxes.hasOwnProperty('dash')) + this._cursorAxes.dash = cursorAxes['dash'] || this._defaultCursorAxes.dash; + if (cursorAxes.hasOwnProperty('color')) + this._cursorAxes.color = cursorAxes['color'] || this._defaultCursorAxes.color; + if (cursorAxes.hasOwnProperty('strokeWidth')) + this._cursorAxes.strokeWidth = cursorAxes['strokeWidth'] || this._defaultCursorAxes.strokeWidth; + } + + if (this._cursorAxes.enabled) { + if (!this._cursorAxes._listener) { + var self = this; + var g2d = this._cursorCanvas.getContext('2d'); + var axesListener = function (event) { + var coords = annotorious.events.ui.sanitizeCoordinates(event, self._activeCanvas); + g2d.clearRect(0, 0, g2d.canvas.width, g2d.canvas.height); + if (self._cursorAxes.dash) g2d.setLineDash([5, 3]); /*dashes are 5px and spaces are 3px */ + else { + g2d.setLineDash([]); + g2d.lineCap = 'square'; + } + g2d.strokeStyle = self._cursorAxes.color; + g2d.lineWidth = self._cursorAxes.strokeWidth; + + g2d.beginPath(); + g2d.moveTo(0, coords.y); + g2d.lineTo(g2d.canvas.width, coords.y); + g2d.moveTo(coords.x, 0); + g2d.lineTo(coords.x, g2d.canvas.height); + g2d.stroke(); + }; + this._cursorAxes._listener = []; + this._cursorAxes._listener[0] = goog.events.listen(this._viewCanvas, annotorious.events.ui.EventType.MOVE, axesListener); + this._cursorAxes._listener[1] = goog.events.listen(this._editCanvas, annotorious.events.ui.EventType.MOVE, axesListener); + } + } + else if (this._cursorAxes._listener) { + goog.array.forEach(this._cursorAxes._listener, function (listener) { + goog.events.unlistenByKey(listener); + }); + var g2d = this._cursorCanvas.getContext('2d'); + g2d.clearRect(0, 0, g2d.canvas.width, g2d.canvas.height); + delete this._cursorAxes._listener; + } +} + +/** + * Enable or Disable drawArrowMode + * @param {boolean} enabled + */ +annotorious.mediatypes.image.ImageAnnotator.prototype._setDrawArrowMode = function (enabled) { + if (enabled) this.setCurrentSelector('arrow_drag') + else this.setCurrentSelector('rect_drag'); } /** API exports **/ diff --git a/src/mediatypes/image/image.viewer.js b/src/mediatypes/image/image.viewer.js index e00396e..9b44bd5 100644 --- a/src/mediatypes/image/image.viewer.js +++ b/src/mediatypes/image/image.viewer.js @@ -7,10 +7,10 @@ goog.provide('annotorious.mediatypes.image.Viewer'); * @param {annotorious.mediatypes.image.ImageAnnotator} annotator reference to the annotator * @constructor */ -annotorious.mediatypes.image.Viewer = function(canvas, annotator) { +annotorious.mediatypes.image.Viewer = function (canvas, annotator) { /** @private **/ this._canvas = canvas; - + /** @private **/ this._annotator = annotator; @@ -35,42 +35,89 @@ annotorious.mediatypes.image.Viewer = function(canvas, annotator) { /** @private **/ this._keepHighlighted = false; - var self = this; - goog.events.listen(this._canvas, annotorious.events.ui.EventType.MOVE, function(event) { - if (self._eventsEnabled) { - self._onMouseMove(event); - } else { - self._cachedMouseEvent = event; - } + /** @private **/ + this._colorMode = { + enabled: false, + insideAnno: false, + mode: "active", + color: "#2ECC71", + strokeWidth: 2, + + _mouseClick: false, + _drawnPixels: [], + _drawnShapes: [], + _nAnnotations: 0 + }; + + /** @private **/ + this._defaultColorMode = Object.assign({}, this._colorMode); + + var self = this; + goog.events.listen(this._canvas, annotorious.events.ui.EventType.MOVE, function (event) { + var pixCurs = self._annotator.toItemPixelCoordinates(annotorious.events.ui.sanitizeCoordinates(event, self._canvas)); + if (self._eventsEnabled) self._onMouseMove(event, pixCurs); + else self._cachedMouseEvent = event; + + self._annotator.fireEvent(annotorious.events.EventType.MOUSE_MOVE_ANNOTATABLE_ITEM, { "cursor": pixCurs }, event); }); - goog.events.listen(this._canvas, annotorious.events.ui.EventType.DOWN, function(event) { - if(self._currentAnnotation !== undefined && self._currentAnnotation != false){ - self._annotator.fireEvent(annotorious.events.EventType.ANNOTATION_CLICKED, self._currentAnnotation); + goog.events.listen(this._canvas, annotorious.events.ui.EventType.DOWN, function (event) { + if (self._moveAnnotation || self._rotateAnnotation) { + var newAnnotation, newShape; + + if (self._moveAnnotation) { + newAnnotation = Object.assign({}, self._moveAnnotation.annotation); + newShape = self._moveAnnotation.newShape; + } else { + newAnnotation = Object.assign({}, self._rotateAnnotation.annotation); + newShape = self._rotateAnnotation.newShape; } + + var shape = newAnnotation.shapes[0]; + if (shape.units == annotorious.shape.Units.PIXEL) { + newAnnotation.shapes[0] = annotorious.shape.transform(newShape, function (xy) { return self._annotator.toItemPixelCoordinates(xy); }); + } else { + newAnnotation.shapes[0] = annotorious.shape.transform(newShape, function (xy) { return self._annotator.toItemCoordinates(xy); }); + } + self.addAnnotation(this._currentAnnotation, newAnnotation); + + if (self._moveAnnotation) { + self._annotator.fireEvent(annotorious.events.EventType.ANNOTATION_MOVED, newAnnotation); + self._moveAnnotation = undefined; + } + if (self._rotateAnnotation) { + self._annotator.fireEvent(annotorious.events.EventType.ANNOTATION_ROTATED, newAnnotation); + self._rotateAnnotation = undefined; + } + } + + if (self._colorMode.enabled) self._colorMode._mouseClick = true; + if (self._currentAnnotation !== undefined && self._currentAnnotation != false) { + self._annotator.fireEvent(annotorious.events.EventType.ANNOTATION_CLICKED, self._currentAnnotation); + } }); - annotator.addHandler(annotorious.events.EventType.MOUSE_OUT_OF_ANNOTATABLE_ITEM, function(event) { + annotator.addHandler(annotorious.events.EventType.MOUSE_OUT_OF_ANNOTATABLE_ITEM, function (event) { delete self._currentAnnotation; self._eventsEnabled = true; }); - annotator.addHandler(annotorious.events.EventType.BEFORE_POPUP_HIDE, function() { + annotator.addHandler(annotorious.events.EventType.BEFORE_POPUP_HIDE, function () { if (!self._eventsEnabled && self._cachedMouseEvent) { var mouseX = self._cachedMouseEvent.offsetX; var mouseY = self._cachedMouseEvent.offsetY; - + var previousAnnotation = self._currentAnnotation; self._currentAnnotation = self.topAnnotationAt(mouseX, mouseY); - + self._eventsEnabled = true; - + if (previousAnnotation != self._currentAnnotation) { // Annotation under mouse has changed in the mean time - redraw self.redraw(); self._annotator.fireEvent(annotorious.events.EventType.MOUSE_OUT_OF_ANNOTATION, { annotation: previousAnnotation, mouseEvent: self._cachedMouseEvent }); - + self._annotator.fireEvent(annotorious.events.EventType.MOUSE_OVER_ANNOTATION, { annotation: self._currentAnnotation, mouseEvent: self._cachedMouseEvent }); } else { @@ -87,32 +134,52 @@ annotorious.mediatypes.image.Viewer = function(canvas, annotator) { }); } +/** + * Returns the shape of an annotation used by the system + * @param {annotorious.Annotation} annotation the annotation + */ +annotorious.mediatypes.image.Viewer.prototype.getSystemShape = function (annotation) { + return this._shapes[annotorious.shape.hashCode(annotation.shapes[0])] +} + /** * Adds an annotation to the viewer. * @param {annotorious.Annotation} annotation the annotation * @param {annotorious.Annotation=} opt_replace optionally, an existing annotation to replace */ -annotorious.mediatypes.image.Viewer.prototype.addAnnotation = function(annotation, opt_replace) { +annotorious.mediatypes.image.Viewer.prototype.addAnnotation = function (annotation, opt_replace) { // Remove opt_replace, if specified if (opt_replace) { if (opt_replace == this._currentAnnotation) delete this._currentAnnotation; - - goog.array.remove(this._annotations, opt_replace); - delete this._shapes[annotorious.shape.hashCode(opt_replace.shapes[0])]; + + goog.array.remove(this._annotations, opt_replace); + delete this._shapes[annotorious.shape.hashCode(opt_replace.shapes[0])]; + + annotation = annotation || opt_replace; } + if (this.getSystemShape(annotation)) return; //The new annotation has shapes exactly equals other annotation. Not insert if not delete old annotation. this._annotations.push(annotation); - + // The viewer always operates in pixel coordinates for efficiency reasons var shape = annotation.shapes[0]; + var self = this; if (shape.units == annotorious.shape.Units.PIXEL) { - this._shapes[annotorious.shape.hashCode(annotation.shapes[0])] = shape; + //convert the pixel relative from original image size to the pixel system used by the annotatable item. + shape = annotorious.shape.transform(shape, function (xy) { return self._annotator.fromItemPixelCoordinates(xy); }); + if (this._annotator.outputUnits == annotorious.shape.Units.FRACTION) { + annotation.shapes[0] = annotorious.shape.transform(shape, function (xy) { return self._annotator.toItemCoordinates(xy); }); + annotation.shapes[0].units = annotorious.shape.Units.FRACTION; + } + this._shapes[annotorious.shape.hashCode(annotation.shapes[0])] = shape; } else { - var self = this; - var viewportShape = annotorious.shape.transform(shape, function(xy) { - return self._annotator.fromItemCoordinates(xy); - }); + //convert the fraction to the pixel system used by the annotatable item. + var viewportShape = annotorious.shape.transform(shape, function (xy) { return self._annotator.fromItemCoordinates(xy); }); + if (this._annotator.outputUnits == annotorious.shape.Units.PIXEL) { + annotation.shapes[0] = annotorious.shape.transform(viewportShape, function (xy) { return self._annotator.toItemPixelCoordinates(xy); }); + annotation.shapes[0].units = annotorious.shape.Units.PIXEL; + } this._shapes[annotorious.shape.hashCode(annotation.shapes[0])] = viewportShape; } @@ -123,21 +190,39 @@ annotorious.mediatypes.image.Viewer.prototype.addAnnotation = function(annotatio * Removes an annotation from the viewer. * @param {annotorious.Annotation} annotation the annotation */ -annotorious.mediatypes.image.Viewer.prototype.removeAnnotation = function(annotation) { +annotorious.mediatypes.image.Viewer.prototype.removeAnnotation = function (annotation) { if (annotation == this._currentAnnotation) delete this._currentAnnotation; - + goog.array.remove(this._annotations, annotation); delete this._shapes[annotorious.shape.hashCode(annotation.shapes[0])]; this.redraw(); } +/** + * Move an annotation from the viewer. + * @param {annotorious.Annotation} annotation the annotation + */ +annotorious.mediatypes.image.Viewer.prototype.moveAnnotation = function (annotation) { + this._moveAnnotation = { annotation: annotation }; + this._rotateAnnotation = undefined; +} + +/** + * Move an annotation from the viewer. + * @param {annotorious.Annotation} annotation the annotation + */ +annotorious.mediatypes.image.Viewer.prototype.rotateAnnotation = function (annotation) { + this._rotateAnnotation = { annotation: annotation }; + this._moveAnnotation = undefined; +} + /** * Returns all annotations in this viewer. * @return {Array.} the annotations */ -annotorious.mediatypes.image.Viewer.prototype.getAnnotations = function() { - return goog.array.clone(this._annotations) +annotorious.mediatypes.image.Viewer.prototype.getAnnotations = function () { + return goog.array.clone(this._annotations) } /** @@ -145,7 +230,7 @@ annotorious.mediatypes.image.Viewer.prototype.getAnnotations = function() { * word...) all, if no annotation is passed to the method. * @param {annotorious.Annotation | undefined} opt_annotation the annotation */ -annotorious.mediatypes.image.Viewer.prototype.highlightAnnotation = function(opt_annotation) { +annotorious.mediatypes.image.Viewer.prototype.highlightAnnotation = function (opt_annotation) { this._currentAnnotation = opt_annotation; if (opt_annotation) this._keepHighlighted = true; @@ -160,7 +245,7 @@ annotorious.mediatypes.image.Viewer.prototype.highlightAnnotation = function(opt * Returns the currently highlighted annotation (or 'undefined' if none). * @returns {Object} the currently highlighted annotation */ -annotorious.mediatypes.image.Viewer.prototype.getHighlightedAnnotation = function() { +annotorious.mediatypes.image.Viewer.prototype.getHighlightedAnnotation = function () { return this._currentAnnotation; } @@ -169,7 +254,7 @@ annotorious.mediatypes.image.Viewer.prototype.getHighlightedAnnotation = functio * @param {number} px the X coordinate * @param {number} py the Y coordinates */ -annotorious.mediatypes.image.Viewer.prototype.topAnnotationAt = function(px, py) { +annotorious.mediatypes.image.Viewer.prototype.topAnnotationAt = function (px, py) { var annotations = this.getAnnotationsAt(px, py); if (annotations.length > 0) { return annotations[0]; @@ -184,57 +269,88 @@ annotorious.mediatypes.image.Viewer.prototype.topAnnotationAt = function(px, py) * @param {number} py the Y coordinate * @return {Array.} the annotations sorted by size, smallest first */ -annotorious.mediatypes.image.Viewer.prototype.getAnnotationsAt = function(px, py) { +annotorious.mediatypes.image.Viewer.prototype.getAnnotationsAt = function (px, py) { // TODO for large numbers of annotations, we can optimize this // using a tree- or grid-like data structure instead of a list var intersectedAnnotations = []; var self = this; - goog.array.forEach(this._annotations, function(annotation) { + goog.array.forEach(this._annotations, function (annotation) { if (annotorious.shape.intersects(self._shapes[annotorious.shape.hashCode(annotation.shapes[0])], px, py)) { intersectedAnnotations.push(annotation); } }); - goog.array.sort(intersectedAnnotations, function(a, b) { + goog.array.sort(intersectedAnnotations, function (a, b) { var shape_a = self._shapes[annotorious.shape.hashCode(a.shapes[0])]; var shape_b = self._shapes[annotorious.shape.hashCode(b.shapes[0])]; - return annotorious.shape.getSize(shape_a) - annotorious.shape.getSize(shape_b); + return annotorious.shape.getSize(shape_a) - annotorious.shape.getSize(shape_b); }); - + return intersectedAnnotations; } /** * @private */ -annotorious.mediatypes.image.Viewer.prototype._onMouseMove = function(event) { - var topAnnotation = this.topAnnotationAt(event.offsetX, event.offsetY); - - // TODO remove code duplication +annotorious.mediatypes.image.Viewer.prototype._onMouseMove = function (event, pixCurs) { + if (this._moveAnnotation || this._rotateAnnotation) { + this._currentAnnotation = this._moveAnnotation ? this._moveAnnotation.annotation : this._rotateAnnotation.annotation; - var self = this; - if (topAnnotation) { - this._keepHighlighted = this._keepHighlighted && (topAnnotation == this._currentAnnotation); + this._g2d.clearRect(0, 0, this._canvas.width, this._canvas.height); + + var self = this; + goog.array.forEach(this._annotations, function (annotation) { + if (annotation != self._currentAnnotation) + self._draw(self._shapes[annotorious.shape.hashCode(annotation.shapes[0])]); + }); - if (!this._currentAnnotation) { - // Mouse moved into annotation from empty space - highlight immediately - this._currentAnnotation = topAnnotation; - this.redraw(); - this._annotator.fireEvent(annotorious.events.EventType.MOUSE_OVER_ANNOTATION, - { annotation: this._currentAnnotation, mouseEvent: event }); - } else if (this._currentAnnotation != topAnnotation) { - // Mouse changed from one annotation to another one - this._eventsEnabled = false; - this._annotator.popup.startHideTimer(); - } - } else if (!this._keepHighlighted) { if (this._currentAnnotation) { - // Mouse moved out of an annotation, into empty space + var shape = this._shapes[annotorious.shape.hashCode(this._currentAnnotation.shapes[0])]; + var selector = goog.array.find(this._annotator.getAvailableSelectors(), function (selector) { + var types = selector.getSupportedShapeType(); + return Array.isArray(types) ? goog.array.indexOf(types, shape.type) != -1 : types == shape.type; + }); + + + if (selector) { + if (this._moveAnnotation) this._moveAnnotation.newShape = selector.moveShape(this._g2d, shape, event.offsetX, event.offsetY); + else this._rotateAnnotation.newShape = selector.rotateShape(this._g2d, shape, event.offsetX, event.offsetY); + } else console.log('WARNING unsupported shape type: ' + shape.type); + } + return; + } + + var topAnnotation = this.topAnnotationAt(event.offsetX, event.offsetY); + if (topAnnotation) this._keepHighlighted = this._keepHighlighted && (topAnnotation == this._currentAnnotation); + + if (this._colorMode.enabled && this._colorMode._mouseClick) { /** Color Mode **/ + if (this._colorMode.insideAnno) { + if (!this._colorMode._annotation) this._colorMode._annotation = this._currentAnnotation || topAnnotation; + if (!this._currentAnnotation || !topAnnotation || !annotorious.shape.intersects(this._shapes[annotorious.shape.hashCode(this._colorMode._annotation.shapes[0])], event.offsetX, event.offsetY)) return; //can color only selected annotation + } + var shape = new annotorious.shape.Shape("point", new annotorious.shape.geom.Point(event.offsetX, event.offsetY), false, { fill: this._colorMode.color, strokeWidth: this._colorMode.strokeWidth }) + var drawnPixel = (this._annotator.outputUnits == annotorious.shape.Units.PIXEL) ? pixCurs : this._annotator.toItemCoordinates(shape.geometry); + this._colorMode._drawnPixels.push(drawnPixel); + if (this._colorMode.mode != "release") this._colorMode._drawnShapes.push(shape); + this._draw(shape); + return; + } + + if (this._currentAnnotation) { + if (this._currentAnnotation != topAnnotation || !this._keepHighlighted) { + // Mouse moved out of an annotation, into empty space or mouse changed from one annotation to another one this._eventsEnabled = false; this._annotator.popup.startHideTimer(); } + return; } + + // Mouse moved into annotation from empty space - highlight immediately + this._currentAnnotation = topAnnotation; + this.redraw(); + this._annotator.fireEvent(annotorious.events.EventType.MOUSE_OVER_ANNOTATION, + { annotation: this._currentAnnotation, mouseEvent: event }); } /** @@ -242,10 +358,11 @@ annotorious.mediatypes.image.Viewer.prototype._onMouseMove = function(event) { * @param {boolean=} highlight set true to highlight the shape * @private */ -annotorious.mediatypes.image.Viewer.prototype._draw = function(shape, highlight) { - var selector = goog.array.find(this._annotator.getAvailableSelectors(), function(selector) { - return selector.getSupportedShapeType() == shape.type; - }); +annotorious.mediatypes.image.Viewer.prototype._draw = function (shape, highlight) { + var selector = goog.array.find(this._annotator.getAvailableSelectors(), function (selector) { + var types = selector.getSupportedShapeType(); + return Array.isArray(types) ? goog.array.indexOf(types, shape.type) != -1 : types == shape.type; + }); if (selector) selector.drawShape(this._g2d, shape, highlight); @@ -256,16 +373,17 @@ annotorious.mediatypes.image.Viewer.prototype._draw = function(shape, highlight) /** * @private */ -annotorious.mediatypes.image.Viewer.prototype.redraw = function() { +annotorious.mediatypes.image.Viewer.prototype.redraw = function () { + if (this._colorMode.enabled && this._colorMode._mouseClick) return; this._g2d.clearRect(0, 0, this._canvas.width, this._canvas.height); var self = this; - goog.array.forEach(this._annotations, function(annotation) { - if (annotation != self._currentAnnotation) + goog.array.forEach(this._annotations, function (annotation) { + if (annotation != self._currentAnnotation) self._draw(self._shapes[annotorious.shape.hashCode(annotation.shapes[0])]); }); - - if (this._currentAnnotation) { + + if (this._currentAnnotation && !this._moveAnnotation && !this._rotateAnnotation) { var shape = this._shapes[annotorious.shape.hashCode(this._currentAnnotation.shapes[0])]; this._draw(shape, true); var bbox = annotorious.shape.getBoundingRect(shape).geometry; @@ -273,4 +391,63 @@ annotorious.mediatypes.image.Viewer.prototype.redraw = function() { // TODO Orientation check - what if the popup would be outside the viewport? } + + if (this._colorMode.mode != "release") goog.array.forEach(this._colorMode._drawnShapes, function (shape) { + self._draw(shape); + }); } + +/** + * Enable or Disable colorMode + * @param {Object} colorMode {enabled: false, insideAnno: false, mode: "active", color: "#2ECC71", strokeWidth: 2} + * - enabled if true, enable the colorMode + * - insideAnno if true, is possible draw only inside the annotations + * - mode mode of save the drawn pixels + * - color color of pixels + * - strokeWidth stroke width of pixels [1-12] + */ +annotorious.mediatypes.image.Viewer.prototype.setColorMode = function (colorMode) { + if (!(colorMode instanceof Object) || Object.keys(colorMode).length === 0) { + var mode = this._colorMode.mode; + this._colorMode = Object.assign({ _listener: this._colorMode._listener }, this._defaultColorMode); + if (mode === "permanent") this._colorMode.mode = mode; + } + else { + this._colorMode.enabled = colorMode["enabled"]; + this._colorMode.insideAnno = (typeof colorMode["insideAnno"] === "boolean") ? colorMode["insideAnno"] : this._defaultColorMode.insideAnno; + if (colorMode["mode"] === "permanent" || colorMode["mode"] === "release") this._colorMode.mode = colorMode["mode"]; + else if (this._colorMode.mode !== "permanent") this._colorMode.mode = this._defaultColorMode.mode; + this._colorMode.color = (colorMode["color"]) ? colorMode["color"] : this._defaultColorMode.color; + this._colorMode.strokeWidth = (colorMode["strokeWidth"]) ? colorMode["strokeWidth"] : this._defaultColorMode.strokeWidth; + } + + if (!this._colorMode.enabled) { + if (this._colorMode._listener) { + goog.events.unlistenByKey(this._colorMode._listener); + delete this._colorMode._listener; + } + if (this._colorMode.mode === "active") { + this._colorMode._drawnShapes = []; + this.redraw(); + } + this._colorMode._drawnPixels = []; + this._annotator.showSelectionWidget(); + return; + } + + if (!this._colorMode._listener) { + var self = this; + this._colorMode._listener = goog.events.listen(this._canvas, annotorious.events.ui.EventType.UP, function (event) { + self._colorMode._mouseClick = false; + if (self._colorMode._drawnPixels.length > 0) { + self._annotator.fireEvent(annotorious.events.EventType.DRAWN_PIXELS, + { "drawnPixels": self._colorMode._drawnPixels, "annotation": self._colorMode._annotation }); + } + self._colorMode._drawnPixels = []; + self._colorMode._annotation = undefined; + self._colorMode._nAnnotations = 0; + if (self._colorMode.mode === "release") self.redraw(); + }); + } + this._annotator.hideSelectionWidget(); +} \ No newline at end of file diff --git a/src/mediatypes/module.js b/src/mediatypes/module.js index c0aad3f..e8c4ccf 100644 --- a/src/mediatypes/module.js +++ b/src/mediatypes/module.js @@ -11,7 +11,7 @@ goog.require('annotorious.Annotation'); * A base class for Annotorious Module implementations. * @constructor */ -annotorious.mediatypes.Module = function() { } +annotorious.mediatypes.Module = function () { } /** * Initializes the module instance's fields. Note that subclasses to Module @@ -25,33 +25,33 @@ annotorious.mediatypes.Module = function() { } * @param {Function=} opt_preload_fn a function providing a list of pre-loadable items * @protected */ -annotorious.mediatypes.Module.prototype._initFields = function(opt_preload_fn) { +annotorious.mediatypes.Module.prototype._initFields = function (opt_preload_fn) { /** @private **/ this._annotators = new goog.structs.Map(); - + /** @private **/ this._eventHandlers = []; /** @private **/ - this._plugins = []; + this._plugins = []; /** @private **/ this._itemsToLoad = []; - + /** @private **/ this._bufferedForAdding = []; - + /** @private **/ this._bufferedForRemoval = []; /** @private **/ this._cachedGlobalSettings = { hide_selection_widget: false, hide_annotations: false }; - + /** @private **/ this._cachedItemSettings = new goog.structs.Map(); /** @private **/ - this._cachedProperties = undefined; + this._cachedProperties = {}; /** @private **/ this._preLoad = opt_preload_fn; @@ -64,7 +64,7 @@ annotorious.mediatypes.Module.prototype._initFields = function(opt_preload_fn) { * @private * @suppress {missingProperties} */ -annotorious.mediatypes.Module.prototype._getSettings = function(item_url) { +annotorious.mediatypes.Module.prototype._getSettings = function (item_url) { var settings = this._cachedItemSettings.get(item_url); if (!settings) { settings = { hide_selection_widget: false, hide_annotations: false }; @@ -76,9 +76,9 @@ annotorious.mediatypes.Module.prototype._getSettings = function(item_url) { /** * @private */ -annotorious.mediatypes.Module.prototype._initAnnotator = function(item) { +annotorious.mediatypes.Module.prototype._initAnnotator = function (item) { var self = this, - item_src = this.getItemURL(item); + item_src = this.getItemURL(item); // Guard condition: don't make items annotatable if they already are if (this._annotators.get(item_src)) @@ -91,24 +91,24 @@ annotorious.mediatypes.Module.prototype._initAnnotator = function(item) { var removedAnnotations = []; // Attach handlers that are already registered - goog.array.forEach(this._eventHandlers, function(eventHandler) { + goog.array.forEach(this._eventHandlers, function (eventHandler) { annotator.addHandler(eventHandler.type, eventHandler.handler); }); // Callback to registered plugins - goog.array.forEach(this._plugins, function(plugin) { + goog.array.forEach(this._plugins, function (plugin) { self._initPlugin(plugin, annotator); }); - + // Cross-check with annotation add/remove buffers - goog.array.forEach(this._bufferedForAdding, function(annotation) { + goog.array.forEach(this._bufferedForAdding, function (annotation) { if (annotation.src == item_src) { annotator.addAnnotation(annotation); addedAnnotations.push(annotation); } }); - - goog.array.forEach(this._bufferedForRemoval, function(annotation) { + + goog.array.forEach(this._bufferedForRemoval, function (annotation) { if (annotation.src == item_src) { annotator.removeAnnotation(annotation); removedAnnotations.push(annotation); @@ -116,11 +116,11 @@ annotorious.mediatypes.Module.prototype._initAnnotator = function(item) { }); // Apply changes - goog.array.forEach(addedAnnotations, function(annotation) { + goog.array.forEach(addedAnnotations, function (annotation) { goog.array.remove(self._bufferedForAdding, annotation); }); - - goog.array.forEach(removedAnnotations, function(annotation) { + + goog.array.forEach(removedAnnotations, function (annotation) { goog.array.remove(self._bufferedForRemoval, annotation); }); @@ -141,9 +141,9 @@ annotorious.mediatypes.Module.prototype._initAnnotator = function(item) { if (this._cachedGlobalSettings.hide_annotations) annotator.hideAnnotations(); } - if (this._cachedProperties) + if (this._cachedProperties && Object.keys(this._cachedProperties).length !== 0) annotator.setProperties(this._cachedProperties); - + // Update _annotators and _itemsToLoad lists this._annotators.set(item_src, annotator); goog.array.remove(this._itemsToLoad, item); @@ -152,7 +152,7 @@ annotorious.mediatypes.Module.prototype._initAnnotator = function(item) { /** * @private */ -annotorious.mediatypes.Module.prototype._initPlugin = function(plugin, annotator) { +annotorious.mediatypes.Module.prototype._initPlugin = function (plugin, annotator) { if (plugin.onInitAnnotator) plugin.onInitAnnotator(annotator); } @@ -160,9 +160,9 @@ annotorious.mediatypes.Module.prototype._initPlugin = function(plugin, annotator /** * @private */ -annotorious.mediatypes.Module.prototype._lazyLoad = function() { +annotorious.mediatypes.Module.prototype._lazyLoad = function () { var item, i; - for (i=this._itemsToLoad.length; i>0; i--) { + for (i = this._itemsToLoad.length; i > 0; i--) { item = this._itemsToLoad[i - 1]; if (annotorious.dom.isInViewport(item)) this._initAnnotator(item); @@ -172,14 +172,14 @@ annotorious.mediatypes.Module.prototype._lazyLoad = function() { /** * @private */ -annotorious.mediatypes.Module.prototype._setAnnotationVisibility = function(opt_item_url, visibility) { +annotorious.mediatypes.Module.prototype._setAnnotationVisibility = function (opt_item_url, visibility) { if (opt_item_url) { var annotator = this._annotators.get(opt_item_url); if (annotator) { // Item URL is provided, and item is loaded - set directly if (visibility) annotator.showAnnotations(); - else + else annotator.hideAnnotations(); } else { // Item URL is provided, but item not yet loaded - cache for later @@ -187,7 +187,7 @@ annotorious.mediatypes.Module.prototype._setAnnotationVisibility = function(opt_ } } else { // Item URL is not provided - update all annotators... - goog.array.forEach(this._annotators.getValues(), function(annotator) { + goog.array.forEach(this._annotators.getValues(), function (annotator) { if (visibility) annotator.showAnnotations(); else @@ -198,7 +198,7 @@ annotorious.mediatypes.Module.prototype._setAnnotationVisibility = function(opt_ this._cachedGlobalSettings.hide_annotations = !visibility; // ...and update all cached item settings - goog.array.forEach(this._cachedItemSettings.getValues(), function(settings) { + goog.array.forEach(this._cachedItemSettings.getValues(), function (settings) { settings.hide_annotations = !visibility; }); } @@ -207,14 +207,14 @@ annotorious.mediatypes.Module.prototype._setAnnotationVisibility = function(opt_ /** * @private */ -annotorious.mediatypes.Module.prototype._setSelectionWidgetVisibility = function(opt_item_url, visibility) { +annotorious.mediatypes.Module.prototype._setSelectionWidgetVisibility = function (opt_item_url, visibility) { if (opt_item_url) { var annotator = this._annotators.get(opt_item_url); if (annotator) { // Item URL is provided, and item is loaded - set directly if (visibility) annotator.showSelectionWidget(); - else + else annotator.hideSelectionWidget(); } else { // Item URL is provided, but item not yet loaded - cache for later @@ -222,10 +222,10 @@ annotorious.mediatypes.Module.prototype._setSelectionWidgetVisibility = function } } else { // Item URL is not provided - update all annotators... - goog.array.forEach(this._annotators.getValues(), function(annotator) { + goog.array.forEach(this._annotators.getValues(), function (annotator) { if (visibility) annotator.showSelectionWidget(); - else + else annotator.hideSelectionWidget(); }); @@ -233,7 +233,7 @@ annotorious.mediatypes.Module.prototype._setSelectionWidgetVisibility = function this._cachedGlobalSettings.hide_selection_widget = !visibility; // ...and update all cached item settings - goog.array.forEach(this._cachedItemSettings.getValues(), function(settings) { + goog.array.forEach(this._cachedItemSettings.getValues(), function (settings) { settings.hide_selection_widget = !visibility; }); } @@ -246,9 +246,9 @@ annotorious.mediatypes.Module.prototype._setSelectionWidgetVisibility = function * @param {string | Function} opt_item_url_or_callback the URL of the item, or a callback function * @param {Function} opt_callback a callback function (if the first parameter was a URL) */ -annotorious.mediatypes.Module.prototype.activateSelector = function(opt_item_url_or_callback, opt_callback) { +annotorious.mediatypes.Module.prototype.activateSelector = function (opt_item_url_or_callback, opt_callback) { var item_url = undefined, - callback = undefined; + callback = undefined; if (goog.isString(opt_item_url_or_callback)) { item_url = opt_item_url_or_callback; @@ -262,19 +262,19 @@ annotorious.mediatypes.Module.prototype.activateSelector = function(opt_item_url if (annotator) annotator.activateSelector(callback); } else { - goog.array.forEach(this._annotators.getValues(), function(annotator) { + goog.array.forEach(this._annotators.getValues(), function (annotator) { annotator.activateSelector(callback); }); } } -annotorious.mediatypes.Module.prototype.stopSelection = function(opt_item_url) { +annotorious.mediatypes.Module.prototype.stopSelection = function (opt_item_url) { if (opt_item_url) { var annotator = this._annotators.get(opt_item_url); if (annotator) annotator.stopSelection(); } else { - goog.array.forEach(this._annotators.getValues(), function(annotator) { + goog.array.forEach(this._annotators.getValues(), function (annotator) { annotator.stopSelection(); }); } @@ -285,7 +285,7 @@ annotorious.mediatypes.Module.prototype.stopSelection = function(opt_item_url) { * @param {annotorious.Annotation} annotation the annotation * @param {annotorious.Annotation} opt_replace optionally, an existing annotation to replace */ -annotorious.mediatypes.Module.prototype.addAnnotation = function(annotation, opt_replace) { +annotorious.mediatypes.Module.prototype.addAnnotation = function (annotation, opt_replace) { if (this.annotatesItem(annotation.src)) { var annotator = this._annotators.get(annotation.src); if (annotator) { @@ -303,10 +303,10 @@ annotorious.mediatypes.Module.prototype.addAnnotation = function(annotation, opt * @param {annotorious.events.EventType} type the event type * @param {Function} handler the handler function */ -annotorious.mediatypes.Module.prototype.addHandler = function(type, handler) { - goog.array.forEach(this._annotators.getValues(), function(annotator, idx, array) { +annotorious.mediatypes.Module.prototype.addHandler = function (type, handler) { + goog.array.forEach(this._annotators.getValues(), function (annotator, idx, array) { annotator.addHandler(type, handler); - }); + }); this._eventHandlers.push({ type: type, handler: handler }); } @@ -315,27 +315,27 @@ annotorious.mediatypes.Module.prototype.addHandler = function(type, handler) { * @param {annotorious.events.EventType} type the event type * @param {Function} handler the handler function (optional) */ -annotorious.mediatypes.Module.prototype.removeHandler = function(type, handler) { - goog.array.forEach(this._annotators.getValues(), function(annotator, idx, array) { +annotorious.mediatypes.Module.prototype.removeHandler = function (type, handler) { + goog.array.forEach(this._annotators.getValues(), function (annotator, idx, array) { annotator.removeHandler(type, handler); }); - goog.array.forEach(this._eventHandlers, function(elem, index, array) { - if(elem.type === type) { - if(!handler || elem.handler === handler) { - goog.array.removeAt(array, index); - } - } - }); + goog.array.forEach(this._eventHandlers, function (elem, index, array) { + if (elem.type === type) { + if (!handler || elem.handler === handler) { + goog.array.removeAt(array, index); + } + } + }); } /** * Adds a plugin to this module. * @param {Plugin} plugin the plugin */ -annotorious.mediatypes.Module.prototype.addPlugin = function(plugin) { +annotorious.mediatypes.Module.prototype.addPlugin = function (plugin) { this._plugins.push(plugin); var self = this; - goog.array.forEach(this._annotators.getValues(), function(annotator) { + goog.array.forEach(this._annotators.getValues(), function (annotator) { self._initPlugin(plugin, annotator); }); } @@ -344,16 +344,16 @@ annotorious.mediatypes.Module.prototype.addPlugin = function(plugin) { * Tests if this module is in charge of managing the item with the specified URL. * @param {string} item_url the URL of the item * @return {boolean} true if this module is in charge of the media - */ -annotorious.mediatypes.Module.prototype.annotatesItem = function(item_url) { + */ +annotorious.mediatypes.Module.prototype.annotatesItem = function (item_url) { if (this._annotators.containsKey(item_url)) { return true; } else { var self = this; - var item = goog.array.find(this._itemsToLoad, function(item) { + var item = goog.array.find(this._itemsToLoad, function (item) { return self.getItemURL(item) == item_url; }); - + return goog.isDefAndNotNull(item); } } @@ -362,7 +362,7 @@ annotorious.mediatypes.Module.prototype.annotatesItem = function(item_url) { * Destroys the annotator on the specified item, or all annotators managed by this module. * @param {string=} opt_item_url the URL of the item on which to destroy the annotator. */ -annotorious.mediatypes.Module.prototype.destroy = function(opt_item_url) { +annotorious.mediatypes.Module.prototype.destroy = function (opt_item_url) { if (opt_item_url) { var annotator = this._annotators.get(opt_item_url); if (annotator) { @@ -370,9 +370,9 @@ annotorious.mediatypes.Module.prototype.destroy = function(opt_item_url) { this._annotators.remove(opt_item_url); } } else { - goog.array.forEach(this._annotators.getValues(), function(annotator) { + goog.array.forEach(this._annotators.getValues(), function (annotator) { annotator.destroy(); - }); + }); this._annotators.clear(); } } @@ -383,7 +383,7 @@ annotorious.mediatypes.Module.prototype.destroy = function(opt_item_url) { * @param {string} item_url the URL of the item to query for the active selector * @return {string | undefined} the name of the active selector (or undefined) */ -annotorious.mediatypes.Module.prototype.getActiveSelector = function(item_url) { +annotorious.mediatypes.Module.prototype.getActiveSelector = function (item_url) { if (this.annotatesItem(item_url)) { var annotator = this._annotators.get(item_url); if (annotator) @@ -398,19 +398,19 @@ annotorious.mediatypes.Module.prototype.getActiveSelector = function(item_url) { * @param {string | undefined} opt_item_url an item URL (optional) * @return {Array.} the annotations */ -annotorious.mediatypes.Module.prototype.getAnnotations = function(opt_item_url) { +annotorious.mediatypes.Module.prototype.getAnnotations = function (opt_item_url) { if (opt_item_url) { var annotator = this._annotators.get(opt_item_url); if (annotator) { return annotator.getAnnotations(); } else { - return goog.array.filter(this._bufferedForAdding, function(annotation) { + return goog.array.filter(this._bufferedForAdding, function (annotation) { return annotation.src == opt_item_url; }); } } else { var annotations = []; - goog.array.forEach(this._annotators.getValues(), function(annotator) { + goog.array.forEach(this._annotators.getValues(), function (annotator) { goog.array.extend(annotations, annotator.getAnnotations()); }); goog.array.extend(annotations, this._bufferedForAdding); @@ -423,11 +423,11 @@ annotorious.mediatypes.Module.prototype.getAnnotations = function(opt_item_url) * @param {string} item_url the URL of the item to query for available selectors * @returns {Array. | undefined} the list of selector names */ -annotorious.mediatypes.Module.prototype.getAvailableSelectors = function(item_url) { +annotorious.mediatypes.Module.prototype.getAvailableSelectors = function (item_url) { if (this.annotatesItem(item_url)) { var annotator = this._annotators.get(item_url); if (annotator) { - return goog.array.map(annotator.getAvailableSelectors(), function(selector) { + return goog.array.map(annotator.getAvailableSelectors(), function (selector) { return selector.getName(); }); } @@ -439,7 +439,7 @@ annotorious.mediatypes.Module.prototype.getAvailableSelectors = function(item_ur * Hides existing annotations on all, or a specific item. * @param {string} opt_item_url the URL of the item */ -annotorious.mediatypes.Module.prototype.hideAnnotations = function(opt_item_url) { +annotorious.mediatypes.Module.prototype.hideAnnotations = function (opt_item_url) { this._setAnnotationVisibility(opt_item_url, false); } @@ -448,7 +448,7 @@ annotorious.mediatypes.Module.prototype.hideAnnotations = function(opt_item_url) * The selection widget can be hidden on a specific item or all. * @param {string} opt_item_url the URL of the item on which to hide the selection widget */ -annotorious.mediatypes.Module.prototype.hideSelectionWidget = function(opt_item_url) { +annotorious.mediatypes.Module.prototype.hideSelectionWidget = function (opt_item_url) { this._setSelectionWidgetVisibility(opt_item_url, false); } @@ -456,15 +456,15 @@ annotorious.mediatypes.Module.prototype.hideSelectionWidget = function(opt_item_ * Highlights the specified annotation. * @param {annotorious.Annotation} annotation the annotation */ -annotorious.mediatypes.Module.prototype.highlightAnnotation = function(annotation) { +annotorious.mediatypes.Module.prototype.highlightAnnotation = function (annotation) { if (annotation) { if (this.annotatesItem(annotation.src)) { var annotator = this._annotators.get(annotation.src); if (annotator) annotator.highlightAnnotation(annotation); - } + } } else { - goog.array.forEach(this._annotators.getValues(), function(annotator) { + goog.array.forEach(this._annotators.getValues(), function (annotator) { annotator.highlightAnnotation(); }); } @@ -473,14 +473,14 @@ annotorious.mediatypes.Module.prototype.highlightAnnotation = function(annotatio /** * Lifecycle method: called by Annotorious on module initialization. */ -annotorious.mediatypes.Module.prototype.init = function() { +annotorious.mediatypes.Module.prototype.init = function () { if (this._preLoad) - goog.array.extend(this._itemsToLoad, this._preLoad()); + goog.array.extend(this._itemsToLoad, this._preLoad()); this._lazyLoad(); - + var self = this; - var key = goog.events.listen(window, goog.events.EventType.SCROLL, function() { + var key = goog.events.listen(window, goog.events.EventType.SCROLL, function () { if (self._itemsToLoad.length > 0) self._lazyLoad(); else @@ -492,7 +492,7 @@ annotorious.mediatypes.Module.prototype.init = function() { * Makes an item annotatable, if it is supported by this module. * @param {Object} item the annotatable item */ -annotorious.mediatypes.Module.prototype.makeAnnotatable = function(item) { +annotorious.mediatypes.Module.prototype.makeAnnotatable = function (item) { if (this.supports(item)) this._initAnnotator(item); } @@ -501,7 +501,7 @@ annotorious.mediatypes.Module.prototype.makeAnnotatable = function(item) { * Removes an annotation from the item with the specified URL. * @param {annotorious.Annotation} annotation the annotation */ -annotorious.mediatypes.Module.prototype.removeAnnotation = function(annotation) { +annotorious.mediatypes.Module.prototype.removeAnnotation = function (annotation) { if (this.annotatesItem(annotation.src)) { var annotator = this._annotators.get(annotation.src); if (annotator) @@ -516,7 +516,7 @@ annotorious.mediatypes.Module.prototype.removeAnnotation = function(annotation) * @param {string} item_url the URL of the item on which to set the selector * @param {string} selector the name of the selector to set on the item */ -annotorious.mediatypes.Module.prototype.setActiveSelector = function(item_url, selector) { +annotorious.mediatypes.Module.prototype.setActiveSelector = function (item_url, selector) { if (this.annotatesItem(item_url)) { var annotator = this._annotators.get(item_url); if (annotator) @@ -524,14 +524,47 @@ annotorious.mediatypes.Module.prototype.setActiveSelector = function(item_url, s } } +/** + * Save the properties in the cache + * @param {Object} props the properties object + * @param {Object} cachedProperties variable to save properties + */ +annotorious.mediatypes.Module.prototype._saveProperties = function (props, cachedProperties) { + var self = this; + if (props instanceof Object && Object.keys(props).length !== 0) { + goog.array.forEach(Object.keys(props), function (key) { + if (props[key] instanceof Object) { + if (!cachedProperties.hasOwnProperty(key) || !cachedProperties[key]) cachedProperties[key] = (props[key] instanceof Array) ? [] : {}; + self._saveProperties(props[key], cachedProperties[key]); + } + else cachedProperties[key] = props[key]; + }); + } +} + +/** + * Remove the properties from the cache + */ +annotorious.mediatypes.Module.prototype.removeProperties = function () { + this._cachedProperties = {}; + this.showAnnotations(); + this.showSelectionWidget(); +} + /** * Sets the properties on this module. * @param {Object} props the properties object */ -annotorious.mediatypes.Module.prototype.setProperties = function(props) { - // TODO these should merge with the existing props rather than replace them! - this._cachedProperties = props; - goog.array.forEach(this._annotators.getValues(), function(annotator) { +annotorious.mediatypes.Module.prototype.setProperties = function (props) { + if (!(props instanceof Object) || Object.keys(props).length === 0) return; + this._saveProperties(props, this._cachedProperties); + + if (props.hasOwnProperty("hideSelectionWidget")) + this._setSelectionWidgetVisibility(undefined, !props['hideSelectionWidget']); + if (props.hasOwnProperty("hideAnnotations")) + this._setAnnotationVisibility(undefined, !props['hideAnnotations']); + + goog.array.forEach(this._annotators.getValues(), function (annotator) { annotator.setProperties(props); }); } @@ -540,16 +573,27 @@ annotorious.mediatypes.Module.prototype.setProperties = function(props) { * Shows existing annotations on all, or a specific item. * @param {string} opt_item_url the URL of the item */ -annotorious.mediatypes.Module.prototype.showAnnotations = function(opt_item_url) { +annotorious.mediatypes.Module.prototype.showAnnotations = function (opt_item_url) { this._setAnnotationVisibility(opt_item_url, true); } +/** + * Retrieves all annotations that intersect the centroid of an annotation + * @param {annotorious.Annotation} annotation annotation from which to retrieve the centroid + * @param {String} type filter by shape type + * @return {Array.} the annotations sorted by size, smallest first + */ +annotorious.mediatypes.Module.prototype.getIntersectedAnnotations = function (annotation, type) { + var annotator = this._annotators.get(annotation.src); + return (annotator) ? annotator.getIntersectedAnnotations(annotation, type) : []; +} + /** * Shows the selection widget, thus enabling users to create new annotations. * The selection widget can be made visible on a specific item or all. * @param {string} opt_item_url the URL of the item on which to show the selection widget */ -annotorious.mediatypes.Module.prototype.showSelectionWidget = function(opt_item_url) { +annotorious.mediatypes.Module.prototype.showSelectionWidget = function (opt_item_url) { this._setSelectionWidgetVisibility(opt_item_url, true); } diff --git a/src/popup.js b/src/popup.js index a0d7a2b..42882b7 100644 --- a/src/popup.js +++ b/src/popup.js @@ -10,12 +10,12 @@ goog.require('goog.style'); * @param {Object} annotator reference to the annotator * @constructor */ -annotorious.Popup = function(annotator) { +annotorious.Popup = function (annotator) { this.element = goog.soy.renderAsElement(annotorious.templates.popup); /** @private **/ - this._annotator = annotator; - + this._annotator = annotator; + /** @private **/ this._currentAnnotation; @@ -24,10 +24,10 @@ annotorious.Popup = function(annotator) { /** @private **/ this._buttons = goog.dom.query('.annotorious-popup-buttons', this.element)[0]; - + /** @private **/ this._popupHideTimer; - + /** @private **/ this._buttonHideTimer; @@ -37,33 +37,45 @@ annotorious.Popup = function(annotator) { /** @private **/ this._extraFields = []; + /** @private **/ + this._properties = { + extraFields: undefined, + showMoveButton: true, + showRotateButton: true + }; + + /** @private **/ + this._defaultProperties = JSON.parse(JSON.stringify(this._properties)); + var btnEdit = goog.dom.query('.annotorious-popup-button-edit', this._buttons)[0]; var btnDelete = goog.dom.query('.annotorious-popup-button-delete', this._buttons)[0]; + this._btnMove = goog.dom.query('.annotorious-popup-button-move', this._buttons)[0]; + this._btnRotate = goog.dom.query('.annotorious-popup-button-rotate', this._buttons)[0]; - var self = this; - goog.events.listen(btnEdit, goog.events.EventType.MOUSEOVER, function(event) { + var self = this; + goog.events.listen(btnEdit, goog.events.EventType.MOUSEOVER, function (event) { goog.dom.classes.add(btnEdit, 'annotorious-popup-button-active'); }); - goog.events.listen(btnEdit, goog.events.EventType.MOUSEOUT, function() { + goog.events.listen(btnEdit, goog.events.EventType.MOUSEOUT, function () { goog.dom.classes.remove(btnEdit, 'annotorious-popup-button-active'); }); - goog.events.listen(btnEdit, goog.events.EventType.CLICK, function(event) { + goog.events.listen(btnEdit, goog.events.EventType.CLICK, function (event) { goog.style.setOpacity(self.element, 0); goog.style.setStyle(self.element, 'pointer-events', 'none'); - annotator.editAnnotation(self._currentAnnotation); + annotator.editAnnotation(self._currentAnnotation); }); - goog.events.listen(btnDelete, goog.events.EventType.MOUSEOVER, function(event) { + goog.events.listen(btnDelete, goog.events.EventType.MOUSEOVER, function (event) { goog.dom.classes.add(btnDelete, 'annotorious-popup-button-active'); }); - - goog.events.listen(btnDelete, goog.events.EventType.MOUSEOUT, function() { + + goog.events.listen(btnDelete, goog.events.EventType.MOUSEOUT, function () { goog.dom.classes.remove(btnDelete, 'annotorious-popup-button-active'); }); - goog.events.listen(btnDelete, goog.events.EventType.CLICK, function(event) { + goog.events.listen(btnDelete, goog.events.EventType.CLICK, function (event) { var cancelEvent = annotator.fireEvent(annotorious.events.EventType.BEFORE_ANNOTATION_REMOVED, self._currentAnnotation); if (!cancelEvent) { goog.style.setOpacity(self.element, 0); @@ -73,24 +85,52 @@ annotorious.Popup = function(annotator) { } }); - if (annotorious.events.ui.hasMouse) { - goog.events.listen(this.element, goog.events.EventType.MOUSEOVER, function(event) { + goog.events.listen(self._btnMove, goog.events.EventType.MOUSEOVER, function (event) { + goog.dom.classes.add(self._btnMove, 'annotorious-popup-button-active'); + }); + + goog.events.listen(self._btnMove, goog.events.EventType.MOUSEOUT, function () { + goog.dom.classes.remove(self._btnMove, 'annotorious-popup-button-active'); + }); + + goog.events.listen(self._btnMove, goog.events.EventType.CLICK, function (event) { + goog.style.setOpacity(self.element, 0); + goog.style.setStyle(self.element, 'pointer-events', 'none'); + annotator.moveAnnotation(self._currentAnnotation); + }); + + goog.events.listen(self._btnRotate, goog.events.EventType.MOUSEOVER, function (event) { + goog.dom.classes.add(self._btnRotate, 'annotorious-popup-button-active'); + }); + + goog.events.listen(self._btnRotate, goog.events.EventType.MOUSEOUT, function () { + goog.dom.classes.remove(self._btnRotate, 'annotorious-popup-button-active'); + }); + + goog.events.listen(self._btnRotate, goog.events.EventType.CLICK, function (event) { + goog.style.setOpacity(self.element, 0); + goog.style.setStyle(self.element, 'pointer-events', 'none'); + annotator.rotateAnnotation(self._currentAnnotation); + }); + + if (annotorious.events.ui.hasMouse) { + goog.events.listen(this.element, goog.events.EventType.MOUSEOVER, function (event) { window.clearTimeout(self._buttonHideTimer); if (goog.style.getStyle(self._buttons, 'opacity') < 0.9) goog.style.setOpacity(self._buttons, 0.9); self.clearHideTimer(); }); - - goog.events.listen(this.element, goog.events.EventType.MOUSEOUT, function(event) { + + goog.events.listen(this.element, goog.events.EventType.MOUSEOUT, function (event) { goog.style.setOpacity(self._buttons, 0); self.startHideTimer(); }); - annotator.addHandler(annotorious.events.EventType.MOUSE_OUT_OF_ANNOTATABLE_ITEM, function(event) { + annotator.addHandler(annotorious.events.EventType.MOUSE_OUT_OF_ANNOTATABLE_ITEM, function (event) { self.startHideTimer(); }); } - + goog.style.setOpacity(this._buttons, 0); goog.style.setOpacity(this.element, 0); goog.style.setStyle(this.element, 'pointer-events', 'none'); @@ -103,13 +143,13 @@ annotorious.Popup = function(annotator) { * a DOM element. * @param {string | Function} field the field */ -annotorious.Popup.prototype.addField = function(field) { +annotorious.Popup.prototype.addField = function (field) { var fieldEl = goog.dom.createDom('div', 'annotorious-popup-field'); - - if (goog.isString(field)) { + + if (goog.isString(field)) { fieldEl.innerHTML = field; } else if (goog.isFunction(field)) { - this._extraFields.push({el: fieldEl, fn: field}); + this._extraFields.push({ el: fieldEl, fn: field }); } else if (goog.dom.isElement(field)) { goog.dom.appendChild(fieldEl, field); } @@ -120,11 +160,11 @@ annotorious.Popup.prototype.addField = function(field) { /** * Start the popup hide timer. */ -annotorious.Popup.prototype.startHideTimer = function() { +annotorious.Popup.prototype.startHideTimer = function () { this._cancelHide = false; if (!this._popupHideTimer) { var self = this; - this._popupHideTimer = window.setTimeout(function() { + this._popupHideTimer = window.setTimeout(function () { self._annotator.fireEvent(annotorious.events.EventType.BEFORE_POPUP_HIDE, self); if (!self._cancelHide) { goog.style.setOpacity(self.element, 0.0); @@ -139,7 +179,7 @@ annotorious.Popup.prototype.startHideTimer = function() { /** * Clear the popup hide timer. */ -annotorious.Popup.prototype.clearHideTimer = function() { +annotorious.Popup.prototype.clearHideTimer = function () { this._cancelHide = true; if (this._popupHideTimer) { window.clearTimeout(this._popupHideTimer); @@ -152,7 +192,7 @@ annotorious.Popup.prototype.clearHideTimer = function() { * @param {annotorious.Annotation} annotation the annotation * @param {annotorious.shape.geom.Point} xy the viewport coordinate */ -annotorious.Popup.prototype.show = function(annotation, xy) { +annotorious.Popup.prototype.show = function (annotation, xy) { this.clearHideTimer(); if (xy) @@ -163,16 +203,16 @@ annotorious.Popup.prototype.show = function(annotation, xy) { if (this._buttonHideTimer) window.clearTimeout(this._buttonHideTimer); - + goog.style.setOpacity(this._buttons, 0.9); if (annotorious.events.ui.hasMouse) { var self = this; - this._buttonHideTimer = window.setTimeout(function() { + this._buttonHideTimer = window.setTimeout(function () { goog.style.setOpacity(self._buttons, 0); }, 1000); } - + goog.style.setOpacity(this.element, 0.9); goog.style.setStyle(this.element, 'pointer-events', 'auto'); this._annotator.fireEvent(annotorious.events.EventType.POPUP_SHOWN, this._currentAnnotation); @@ -182,7 +222,7 @@ annotorious.Popup.prototype.show = function(annotation, xy) { * Set the position of the popup. * @param {annotorious.shape.geom.Point} xy the viewport coordinate */ -annotorious.Popup.prototype.setPosition = function(xy) { +annotorious.Popup.prototype.setPosition = function (xy) { goog.style.setPosition(this.element, new goog.math.Coordinate(xy.x, xy.y)); } @@ -190,7 +230,7 @@ annotorious.Popup.prototype.setPosition = function(xy) { * Set the annotation for the popup. * @param {annotorious.Annotation} annotation the annotation */ -annotorious.Popup.prototype.setAnnotation = function(annotation) { +annotorious.Popup.prototype.setAnnotation = function (annotation) { this._currentAnnotation = annotation; if (annotation.text) this._text.innerHTML = annotation.text.replace(/\n/g, '
'); @@ -201,11 +241,23 @@ annotorious.Popup.prototype.setAnnotation = function(annotation) { goog.style.showElement(this._buttons, false); else goog.style.showElement(this._buttons, true); - + + if (!this._properties.showMoveButton || (annotation.shapes[0].type != annotorious.shape.ShapeType.RECTANGLE || + ('movable' in annotation) && annotation.movable == false)) { + goog.style.showElement(this._btnMove, false); + } + else goog.style.showElement(this._btnMove, true); + + if (!this._properties.showRotateButton || (annotation.shapes[0].type != annotorious.shape.ShapeType.RECTANGLE || + ('rotable' in annotation) && annotation.rotable == false)) { + goog.style.showElement(this._btnRotate, false); + } + else goog.style.showElement(this._btnRotate, true); + // Update extra fields (if any) - goog.array.forEach(this._extraFields, function(field) { + goog.array.forEach(this._extraFields, function (field) { var f = field.fn(annotation); - if (goog.isString(f)) { + if (goog.isString(f)) { field.el.innerHTML = f; } else if (goog.dom.isElement(f)) { goog.dom.removeChildren(field.el); @@ -214,5 +266,27 @@ annotorious.Popup.prototype.setAnnotation = function(annotation) { }); } +/** + * Sets the properties on this popup. + */ +annotorious.Popup.prototype.setProperties = function (props) { + if (!(props instanceof Object) || Object.keys(props).length === 0) this._properties = JSON.parse(JSON.stringify(this._defaultProperties)); + else { + if (props.hasOwnProperty('extraFields')) this._properties.extraFields = props["extraFields"]; + if (props.hasOwnProperty('showMoveButton')) this._properties.showMoveButton = props["showMoveButton"]; + if (props.hasOwnProperty('showRotateButton')) this._properties.showRotateButton = props["showRotateButton"]; + } + + //Apply the properties + var container = goog.dom.query('.annotorious-popup-field', this.element)[0]; + if (container) container.remove(); + if (this._properties.extraFields && Array.isArray(this._properties.extraFields)) { + var self = this; + goog.array.forEach(this._properties.extraFields, function (field) { + self.addField(field); + }); + } +} + /** API exports **/ annotorious.Popup.prototype['addField'] = annotorious.Popup.prototype.addField; diff --git a/src/selection/arrow_drag_selector.js b/src/selection/arrow_drag_selector.js new file mode 100644 index 0000000..d84746d --- /dev/null +++ b/src/selection/arrow_drag_selector.js @@ -0,0 +1,322 @@ +goog.provide('annotorious.plugins.selection.ArrowDragSelector'); + +goog.require('goog.events'); +goog.require('annotorious.events.ui'); +goog.require('annotorious.shape.geom.Arrow'); + +/** + * The selector for draw arrows + * @constructor + */ +annotorious.plugins.selection.ArrowDragSelector = function () { } + +/** + * Initializes the selector. + * @param {Element} canvas the canvas to draw on + * @param {Object} annotator reference to the annotator + */ +annotorious.plugins.selection.ArrowDragSelector.prototype.init = function (annotator, canvas) { + + /** @private **/ + this._canvas = canvas; + + /** @private **/ + this._annotator = annotator; + + /** @private **/ + this._g2d = canvas.getContext('2d'); + + /** @private **/ + this._arrowTail; + + /** @private **/ + this._arrowHead; + + /** @private **/ + this._enabled = false; + + /** @private **/ + this._mouseMoveListener; + + /** @private **/ + this._mouseUpListener; + + /** @private **/ + this._properties = { + arrowStroke: '#ffffff', + arrowStrokeWidth: 2, + hiArrowStroke: '#fff000', + hiArrowStrokeWidth: 2.2, + highlightTail: undefined, + hiTailRadius: 5, + highlightHead: undefined, + hiHeadRadius: 5 + }; + + /** @private **/ + this._defaultProperties = Object.assign({}, this._properties); +} + +/** + * Attaches MOUSEUP and MOUSEMOVE listeners to the editing canvas. + * @private + */ +annotorious.plugins.selection.ArrowDragSelector.prototype._attachListeners = function (startPoint, limitShape) { + var self = this; + var canvas = this._canvas; + + this._mouseMoveListener = goog.events.listen(this._canvas, annotorious.events.ui.EventType.MOVE, function (event) { + var points = annotorious.events.ui.sanitizeCoordinates(event, canvas); + if (!self._enabled || (limitShape && !annotorious.shape.intersects(limitShape, points.x, points.y))) return; + self._arrowHead = new annotorious.shape.geom.Point(points.x, points.y); + + self._g2d.clearRect(0, 0, canvas.width, canvas.height); + + var pixCurs = self._annotator.toItemPixelCoordinates(points); + self._annotator.fireEvent(annotorious.events.EventType.MOUSE_MOVE_ANNOTATABLE_ITEM, { "cursor": pixCurs }, event); + + self.drawShape(self._g2d, { + type: annotorious.shape.ShapeType.ARROW, + geometry: new annotorious.shape.geom.Arrow(self._arrowTail, self._arrowHead), + style: {} + }); + }); + + this._mouseUpListener = goog.events.listen(canvas, annotorious.events.ui.EventType.UP, function (event) { + var points = annotorious.events.ui.sanitizeCoordinates(event, canvas); + var shape = self.getShape(); + event = (event.event_) ? event.event_ : event; + + self._enabled = false; + if (shape) { + self._detachListeners(); + self._annotator.fireEvent(annotorious.events.EventType.SELECTION_COMPLETED, + { mouseEvent: event, shape: shape, viewportBounds: self.getViewportBounds() }); + } else { + self._annotator.fireEvent(annotorious.events.EventType.SELECTION_CANCELED); + + // On cancel, we "relay" the selection event to the annotator + var annotations = self._annotator.getAnnotationsAt(points.x, points.y); + if (annotations.length > 0) + self._annotator.highlightAnnotation(annotations[0]); + } + }); +} + +/** + * Detaches MOUSEUP and MOUSEMOVE listeners from the editing canvas. + * @private + */ +annotorious.plugins.selection.ArrowDragSelector.prototype._detachListeners = function () { + if (this._mouseMoveListener) { + goog.events.unlistenByKey(this._mouseMoveListener); + delete this._mouseMoveListener; + } + + if (this._mouseUpListener) { + goog.events.unlistenByKey(this._mouseUpListener); + delete this._mouseUpListener; + } +} + +/** + * Selector API method: returns the selector name. + * @returns the selector name + */ +annotorious.plugins.selection.ArrowDragSelector.prototype.getName = function () { + return 'arrow_drag'; +} + +/** + * Selector API method: returns the supported shape type. + * + * @return the supported shape type + */ +annotorious.plugins.selection.ArrowDragSelector.prototype.getSupportedShapeType = function () { + return annotorious.shape.ShapeType.ARROW; +} + +/** + * Sets the properties on this selector. + */ +annotorious.plugins.selection.ArrowDragSelector.prototype.setProperties = function (props) { + if (!(props instanceof Object) || Object.keys(props).length === 0) { + this._properties = Object.assign({}, this._defaultProperties); + return; + } + if (props.hasOwnProperty('arrowStroke')) + this._properties.arrowStroke = props['arrowStroke'] || this._defaultProperties.arrowStroke; + + if (props.hasOwnProperty('arrowStrokeWidth')) + this._properties.arrowStrokeWidth = props['arrowStrokeWidth'] || this._defaultProperties.arrowStrokeWidth; + + if (props.hasOwnProperty('hiArrowStroke')) + this._properties.hiArrowStroke = props['hiArrowStroke'] || this._defaultProperties.hiArrowStroke; + + if (props.hasOwnProperty('hiArrowStrokeWidth')) + this._properties.hiArrowStrokeWidth = props['hiArrowStrokeWidth'] || this._defaultProperties.hiArrowStrokeWidth; + + if (props.hasOwnProperty('highlightTail')) + this._properties.highlightTail = props['highlightTail'] || this._defaultProperties.highlightTail; + + if (props.hasOwnProperty('hiTailRadius')) + this._properties.hiTailRadius = props['hiTailRadius'] || this._defaultProperties.hiTailRadius; + + if (props.hasOwnProperty('highlightHead')) + this._properties.highlightHead = props['highlightHead'] || this._defaultProperties.highlightHead; + + if (props.hasOwnProperty('hiHeadRadius')) + this._properties.hiHeadRadius = props['hiHeadRadius'] || this._defaultProperties.hiHeadRadius; + +} + +/** + * Selector API method: starts the selection at the specified coordinates. + * @param {number} x the X coordinate + * @param {number} y the Y coordinate + * @param {annotorious.shape.Shape | undefined} limitShape the shape of limit draw + */ +annotorious.plugins.selection.ArrowDragSelector.prototype.startSelection = function (x, y, limitShape) { + var startPoint = { + x: x, + y: y + }; + this._enabled = true; + this._attachListeners(startPoint, limitShape); + this._arrowTail = new annotorious.shape.geom.Point(x, y); + this._annotator.fireEvent(annotorious.events.EventType.SELECTION_STARTED, { + offsetX: x, offsetY: y + }); + + goog.style.setStyle(document.body, '-webkit-user-select', 'none'); +} + +/** + * Selector API method: stops the selection. + */ +annotorious.plugins.selection.ArrowDragSelector.prototype.stopSelection = function () { + this._detachListeners(); + this._g2d.clearRect(0, 0, this._canvas.width, this._canvas.height); + goog.style.setStyle(document.body, '-webkit-user-select', 'auto'); + delete this._arrowHead; +} + +/** + * Selector API method: returns the currently edited shape. + * @return {annotorious.shape.Shape | undefined} the shape + */ +annotorious.plugins.selection.ArrowDragSelector.prototype.getShape = function () { + if (this._arrowHead && ((Math.abs(this._arrowHead.x - this._arrowTail.x) > 5) || (Math.abs(this._arrowHead.y - this._arrowTail.y) > 5))) { + return new annotorious.shape.Shape( + annotorious.shape.ShapeType.ARROW, + new annotorious.shape.geom.Arrow( + this._annotator.toItemCoordinates(this._arrowTail), + this._annotator.toItemCoordinates(this._arrowHead) + ) + ); + } + return undefined; +} + +/** + * Selector API method: returns the bounds of the selected shape, in viewport (= pixel) coordinates. + * @returns {Object} the shape viewport bounds + */ +annotorious.plugins.selection.ArrowDragSelector.prototype.getViewportBounds = function () { + var right, left; + if (this._arrowHead.x > this._arrowTail.x) { + right = this._arrowHead.x; + left = this._arrowTail.x; + } else { + right = this._arrowTail.x; + left = this._arrowHead.x; + } + + var top, bottom; + if (this._arrowHead.y > this._arrowTail.y) { + top = this._arrowTail.y; + bottom = this._arrowHead.y; + } else { + top = this._arrowHead.y; + bottom = this._arrowTail.y; + } + + return { top: top, right: right, bottom: bottom + 5, left: left }; +} + +/** + * TODO not sure if this is really the best way/architecture to handle viewer shape drawing + * @param {Object} g2d graphics context + * @param {annotorious.shape.Shape} shape the shape to draw + * @param {boolean=} highlight if true, shape will be drawn highlighted + */ +annotorious.plugins.selection.ArrowDragSelector.prototype.drawShape = function (g2d, shape, highlight) { + if (!shape.style) shape.style = {}; + + if (shape.type == annotorious.shape.ShapeType.ARROW) { + var geom = shape.geometry; + + if (highlight) { + g2d.strokeStyle = g2d.fillStyle = shape.style.hiArrowStroke || this._properties.hiArrowStroke; + g2d.lineWidth = shape.style.hiArrowStrokeWidth || this._properties.hiArrowStrokeWidth; + } else { + g2d.strokeStyle = g2d.fillStyle = shape.style.arrowStroke || this._properties.arrowStroke; + g2d.lineWidth = shape.style.arrowStrokeWidth || this._properties.arrowStrokeWidth; + } + + var angle = Math.PI / 5; + var d = g2d.lineWidth * 3; + + var dist = Math.sqrt((geom.arrowHead.x - geom.arrowTail.x) * (geom.arrowHead.x - geom.arrowTail.x) + (geom.arrowHead.y - geom.arrowTail.y) * (geom.arrowHead.y - geom.arrowTail.y)); + var ratio = (dist - d / 3) / dist; + var tox = Math.round(geom.arrowTail.x + (geom.arrowHead.x - geom.arrowTail.x) * ratio); + var toy = Math.round(geom.arrowTail.y + (geom.arrowHead.y - geom.arrowTail.y) * ratio); + + // Draw the shaft of the arrow + g2d.beginPath(); + g2d.moveTo(geom.arrowTail.x, geom.arrowTail.y); + g2d.lineTo(tox, toy); + g2d.stroke(); + + // calculate the angle of the line + var lineangle = Math.atan2(geom.arrowHead.y - geom.arrowTail.y, geom.arrowHead.x - geom.arrowTail.x); + // h is the line length of a side of the arrow head + var h = Math.abs(d / Math.cos(angle)); + + var angle1 = lineangle + Math.PI + angle; + var topx = geom.arrowHead.x + Math.cos(angle1) * h; + var topy = geom.arrowHead.y + Math.sin(angle1) * h; + var angle2 = lineangle + Math.PI - angle; + var botx = geom.arrowHead.x + Math.cos(angle2) * h; + var boty = geom.arrowHead.y + Math.sin(angle2) * h; + + g2d.save(); + g2d.beginPath(); + g2d.moveTo(topx, topy); + g2d.lineTo(geom.arrowHead.x, geom.arrowHead.y); + g2d.lineTo(botx, boty); + + // straight filled, add the bottom as a line and fill. + g2d.beginPath(); + g2d.moveTo(topx, topy); + g2d.lineTo(geom.arrowHead.x, geom.arrowHead.y); + g2d.lineTo(botx, boty); + g2d.lineTo(topx, topy); + g2d.fill(); + + if (shape.style.highlightTail || this._properties.highlightTail) { + g2d.beginPath(); + g2d.arc(geom.arrowTail.x, geom.arrowTail.y, (shape.style.hiTailRadius || this._properties.hiTailRadius), 0, 2 * Math.PI, false); + g2d.fillStyle = shape.style.highlightTail || this._properties.highlightTail; + g2d.fill(); + } + if (shape.style.highlightHead || this._properties.highlightHead) { + g2d.beginPath(); + g2d.arc(geom.arrowHead.x, geom.arrowHead.y, (shape.style.hiHeadRadius || this._properties.hiHeadRadius), 0, 2 * Math.PI, false); + g2d.fillStyle = shape.style.highlightHead || this._properties.highlightHead; + g2d.fill(); + } + + g2d.restore(); + } +} diff --git a/src/selection/rect_drag_selector.js b/src/selection/rect_drag_selector.js index 4273851..3e5b249 100644 --- a/src/selection/rect_drag_selector.js +++ b/src/selection/rect_drag_selector.js @@ -8,57 +8,27 @@ goog.require('annotorious.events.ui'); * The default selector: a simple click-and-drag rectangle selection tool. * @constructor */ -annotorious.plugins.selection.RectDragSelector = function() { } +annotorious.plugins.selection.RectDragSelector = function () { } /** * Initializes the selector. * @param {Element} canvas the canvas to draw on * @param {Object} annotator reference to the annotator */ -annotorious.plugins.selection.RectDragSelector.prototype.init = function(annotator, canvas) { - /** @private **/ - this._OUTLINE = '#000000'; - - /** @private **/ - this._STROKE = '#ffffff'; - - /** @private **/ - this._FILL = false; - - /** @private **/ - this._HI_OUTLINE = '#000000'; +annotorious.plugins.selection.RectDragSelector.prototype.init = function (annotator, canvas) { - /** @private **/ - this._HI_STROKE = '#fff000'; - - /** @private **/ - this._HI_FILL = false; - - /** @private **/ - this._OUTLINE_WIDTH = 1; - - /** @private **/ - this._STROKE_WIDTH = 1; - - /** @private **/ - this._HI_OUTLINE_WIDTH = 1; - - /** @private **/ - this._HI_STROKE_WIDTH = 1.2; - /** @private **/ this._canvas = canvas; - + /** @private **/ this._annotator = annotator; /** @private **/ this._g2d = canvas.getContext('2d'); - this._g2d.lineWidth = 1; - + /** @private **/ this._anchor; - + /** @private **/ this._opposite; @@ -70,49 +40,90 @@ annotorious.plugins.selection.RectDragSelector.prototype.init = function(annotat /** @private **/ this._mouseUpListener; + + /** @private **/ + this._properties = { + outline: '#000000', + outlineWidth: 1, + hiOutline: '#000000', + hiOutlineWidth: 1, + stroke: '#ffffff', + strokeWidth: 1, + hiStroke: '#fff000', + hiStrokeWidth: 1.2, + fill: undefined, + hiFill: undefined, + maskTransparency: 0.8, + maskBorder: true + }; + + /** @private **/ + this._defaultProperties = Object.assign({}, this._properties); + + /** @private **/ + this._useFancyBox = false; } /** * Attaches MOUSEUP and MOUSEMOVE listeners to the editing canvas. * @private */ -annotorious.plugins.selection.RectDragSelector.prototype._attachListeners = function(startPoint) { - var self = this; +annotorious.plugins.selection.RectDragSelector.prototype._attachListeners = function (startPoint, limitShape) { + var self = this; var canvas = this._canvas; - - this._mouseMoveListener = goog.events.listen(this._canvas, annotorious.events.ui.EventType.MOVE, function(event) { + + this._mouseMoveListener = goog.events.listen(this._canvas, annotorious.events.ui.EventType.MOVE, function (event) { var points = annotorious.events.ui.sanitizeCoordinates(event, canvas); - if (self._enabled) { - self._opposite = { x: points.x, y: points.y }; - - self._g2d.clearRect(0, 0, canvas.width, canvas.height); - - var width = self._opposite.x - self._anchor.x; - var height = self._opposite.y - self._anchor.y; - - self.drawShape(self._g2d, { - type: annotorious.shape.ShapeType.RECTANGLE, - geometry: { - x: width > 0 ? self._anchor.x : self._opposite.x, - y: height > 0 ? self._anchor.y : self._opposite.y, - width: Math.abs(width), - height: Math.abs(height) - }, - style: {} - }); + if (!self._enabled || (limitShape && !annotorious.shape.intersects(limitShape, points.x, points.y))) return; + self._opposite = { x: points.x, y: points.y }; + + self._g2d.clearRect(0, 0, canvas.width, canvas.height); + + var width = self._opposite.x - self._anchor.x; + var height = self._opposite.y - self._anchor.y; + + var pixCurs = self._annotator.toItemPixelCoordinates(points); + var pixBox = self._annotator.toItemPixelCoordinates({ x: self._anchor.x, y: self._anchor.y, width: width, height: height }); + self._annotator.fireEvent(annotorious.events.EventType.MOUSE_MOVE_ANNOTATABLE_ITEM, { "cursor": pixCurs, "box": pixBox }, event); + + if (self._useFancyBox) { + var vb = self.getViewportBounds(); + height = Math.abs(height); + width = Math.abs(width); + + self._g2d.lineWidth = self._g2d.lineWidth = self._properties.strokeWidth; + self._g2d.strokeStyle = self._properties.stroke; + self._g2d.fillStyle = 'rgba(0,0,0,0.45)'; + self._g2d.fillRect(0, 0, self._canvas.width, vb.top); + self._g2d.fillRect(vb.right, vb.top, (self._canvas.width - vb.right), height); + self._g2d.fillRect(0, vb.bottom, self._canvas.width, (self._canvas.height - vb.bottom)); + self._g2d.fillRect(0, vb.top, vb.left, height); + self._g2d.strokeRect(vb.left + 0.5, vb.top + 0.5, width, height); + return; } + + self.drawShape(self._g2d, { + type: annotorious.shape.ShapeType.RECTANGLE, + geometry: { + x: width > 0 ? self._anchor.x : self._opposite.x, + y: height > 0 ? self._anchor.y : self._opposite.y, + width: Math.abs(width), + height: Math.abs(height) + }, + style: {} + }); }); - this._mouseUpListener = goog.events.listen(canvas, annotorious.events.ui.EventType.UP, function(event) { + this._mouseUpListener = goog.events.listen(canvas, annotorious.events.ui.EventType.UP, function (event) { var points = annotorious.events.ui.sanitizeCoordinates(event, canvas); var shape = self.getShape(); event = (event.event_) ? event.event_ : event; - + self._enabled = false; if (shape) { self._detachListeners(); self._annotator.fireEvent(annotorious.events.EventType.SELECTION_COMPLETED, - { mouseEvent: event, shape: shape, viewportBounds: self.getViewportBounds() }); + { mouseEvent: event, shape: shape, viewportBounds: self.getViewportBounds() }); } else { self._annotator.fireEvent(annotorious.events.EventType.SELECTION_CANCELED); @@ -128,7 +139,7 @@ annotorious.plugins.selection.RectDragSelector.prototype._attachListeners = func * Detaches MOUSEUP and MOUSEMOVE listeners from the editing canvas. * @private */ -annotorious.plugins.selection.RectDragSelector.prototype._detachListeners = function() { +annotorious.plugins.selection.RectDragSelector.prototype._detachListeners = function () { if (this._mouseMoveListener) { goog.events.unlistenByKey(this._mouseMoveListener); delete this._mouseMoveListener; @@ -144,79 +155,99 @@ annotorious.plugins.selection.RectDragSelector.prototype._detachListeners = func * Selector API method: returns the selector name. * @returns the selector name */ -annotorious.plugins.selection.RectDragSelector.prototype.getName = function() { +annotorious.plugins.selection.RectDragSelector.prototype.getName = function () { return 'rect_drag'; } /** * Selector API method: returns the supported shape type. * - * TODO support for multiple shape types? - * * @return the supported shape type */ -annotorious.plugins.selection.RectDragSelector.prototype.getSupportedShapeType = function() { - return annotorious.shape.ShapeType.RECTANGLE; +annotorious.plugins.selection.RectDragSelector.prototype.getSupportedShapeType = function () { + return [annotorious.shape.ShapeType.RECTANGLE, annotorious.shape.ShapeType.POINT]; +} + +/** + * Set the Fancy Box Selector + * @param {Boolean} enabled true if enable the Fancy Box Selector + */ +annotorious.plugins.selection.RectDragSelector.prototype.setFancyBox = function (enabled) { + this._useFancyBox = enabled; } /** * Sets the properties on this selector. */ -annotorious.plugins.selection.RectDragSelector.prototype.setProperties = function(props) { +annotorious.plugins.selection.RectDragSelector.prototype.setProperties = function (props) { + if (!(props instanceof Object) || Object.keys(props).length === 0) { + this._properties = Object.assign({}, this._defaultProperties); + return; + } + if (props.hasOwnProperty('outline')) - this._OUTLINE = props['outline']; + this._properties.outline = props['outline'] || this._defaultProperties.outline; + + if (props.hasOwnProperty('outlineWidth')) + this._properties.outlineWidth = props['outlineWidth'] || this._defaultProperties.outlineWidth; + + if (props.hasOwnProperty('hiOutline')) + this._properties.hiOutline = props['hiOutline'] || this._defaultProperties.hiOutline; + + if (props.hasOwnProperty('hiOutlineWidth')) + this._properties.hiOutlineWidth = props['hiOutlineWidth'] || this._defaultProperties.hiOutlineWidth; if (props.hasOwnProperty('stroke')) - this._STROKE = props['stroke']; - - if (props.hasOwnProperty('fill')) - this._FILL = props['fill']; + this._properties.stroke = props['stroke'] || this._defaultProperties.stroke; - if (props.hasOwnProperty('hi_outline')) - this._HI_OUTLINE = props['hi_outline']; + if (props.hasOwnProperty('strokeWidth')) + this._properties.strokeWidth = props['strokeWidth'] || this._defaultProperties.strokeWidth; - if (props.hasOwnProperty('hi_stroke')) - this._HI_STROKE = props['hi_stroke']; + if (props.hasOwnProperty('hiStroke')) + this._properties.hiStroke = props['hiStroke'] || this._defaultProperties.hiStroke; - if (props.hasOwnProperty('hi_fill')) - this._HI_FILL = props['hi_fill']; + if (props.hasOwnProperty('hiStrokeWidth')) + this._properties.hiStrokeWidth = props['hiStrokeWidth'] || this._defaultProperties.hiStrokeWidth; + + if (props.hasOwnProperty('fill')) + this._properties.fill = props['fill'] || this._defaultProperties.fill; - if (props.hasOwnProperty('outline_width')) - this._OUTLINE_WIDTH = props['outline_width']; + if (props.hasOwnProperty('hiFill')) + this._properties.hiFill = props['hiFill'] || this._defaultProperties.hiFill; - if (props.hasOwnProperty('stroke_width')) - this._STROKE_WIDTH = props['stroke_width']; + if (props.hasOwnProperty('maskTransparency')) + this._properties.maskTransparency = props['maskTransparency'] || this._defaultProperties.maskTransparency; - if (props.hasOwnProperty('hi_outline_width')) - this._HI_OUTLINE_WIDTH = props['hi_outline_width']; + if (props.hasOwnProperty('maskBorder')) + this._properties.maskBorder = (typeof props['maskBorder'] === "boolean") ? props['maskBorder'] : this._defaultProperties.maskBorder; - if (props.hasOwnProperty('hi_stroke_width')) - this._HI_STROKE_WIDTH = props['hi_stroke_width']; } /** * Selector API method: starts the selection at the specified coordinates. * @param {number} x the X coordinate * @param {number} y the Y coordinate + * @param {annotorious.shape.Shape | undefined} limitShape the shape of limit draw */ -annotorious.plugins.selection.RectDragSelector.prototype.startSelection = function(x, y) { +annotorious.plugins.selection.RectDragSelector.prototype.startSelection = function (x, y, limitShape) { var startPoint = { x: x, y: y }; this._enabled = true; - this._attachListeners(startPoint); + this._attachListeners(startPoint, limitShape); this._anchor = new annotorious.shape.geom.Point(x, y); this._annotator.fireEvent(annotorious.events.EventType.SELECTION_STARTED, { - offsetX: x, offsetY: y}); - + offsetX: x, offsetY: y + }); + goog.style.setStyle(document.body, '-webkit-user-select', 'none'); } /** * Selector API method: stops the selection. */ -annotorious.plugins.selection.RectDragSelector.prototype.stopSelection = function() { +annotorious.plugins.selection.RectDragSelector.prototype.stopSelection = function () { this._detachListeners(); this._g2d.clearRect(0, 0, this._canvas.width, this._canvas.height); goog.style.setStyle(document.body, '-webkit-user-select', 'auto'); @@ -227,23 +258,13 @@ annotorious.plugins.selection.RectDragSelector.prototype.stopSelection = functio * Selector API method: returns the currently edited shape. * @return {annotorious.shape.Shape | undefined} the shape */ -annotorious.plugins.selection.RectDragSelector.prototype.getShape = function() { - if (this._opposite && - (Math.abs(this._opposite.x - this._anchor.x) > 3) && - (Math.abs(this._opposite.y - this._anchor.y) > 3)) { - +annotorious.plugins.selection.RectDragSelector.prototype.getShape = function () { + if (this._opposite && + (Math.abs(this._opposite.x - this._anchor.x) > 3) && + (Math.abs(this._opposite.y - this._anchor.y) > 3)) { + var viewportBounds = this.getViewportBounds(); - // var item_anchor = this._annotator.toItemCoordinates({x: viewportBounds.left, y: viewportBounds.top}); - // var item_opposite = this._annotator.toItemCoordinates({x: viewportBounds.right, y: viewportBounds.bottom}); - - /* - var rect = new annotorious.shape.geom.Rectangle( - item_anchor.x, - item_anchor.y, - item_opposite.x - item_anchor.x, - item_opposite.y - item_anchor.y - );*/ - var rect = this._annotator.toItemCoordinates({ + var rect = this._annotator.toItemCoordinates({ // conversion to fraction x: viewportBounds.left, y: viewportBounds.top, width: viewportBounds.right - viewportBounds.left, @@ -251,35 +272,34 @@ annotorious.plugins.selection.RectDragSelector.prototype.getShape = function() { }); return new annotorious.shape.Shape(annotorious.shape.ShapeType.RECTANGLE, rect); - } else { - return undefined; } + return undefined; } /** * Selector API method: returns the bounds of the selected shape, in viewport (= pixel) coordinates. * @returns {Object} the shape viewport bounds */ -annotorious.plugins.selection.RectDragSelector.prototype.getViewportBounds = function() { +annotorious.plugins.selection.RectDragSelector.prototype.getViewportBounds = function () { var right, left; if (this._opposite.x > this._anchor.x) { right = this._opposite.x; left = this._anchor.x; } else { right = this._anchor.x; - left = this._opposite.x; + left = this._opposite.x; } - + var top, bottom; if (this._opposite.y > this._anchor.y) { top = this._anchor.y; bottom = this._opposite.y; } else { top = this._opposite.y; - bottom = this._anchor.y; + bottom = this._anchor.y; } - - return {top: top, right: right, bottom: bottom, left: left}; + + return { top: top, right: right, bottom: bottom, left: left }; } /** @@ -288,65 +308,163 @@ annotorious.plugins.selection.RectDragSelector.prototype.getViewportBounds = fun * @param {annotorious.shape.Shape} shape the shape to draw * @param {boolean=} highlight if true, shape will be drawn highlighted */ -annotorious.plugins.selection.RectDragSelector.prototype.drawShape = function(g2d, shape, highlight) { - var geom, stroke, fill, outline, outline_width, stroke_width; +annotorious.plugins.selection.RectDragSelector.prototype.drawShape = function (g2d, shape, highlight) { + var geom = shape.geometry, stroke, fill, outline, outlineWidth, strokeWidth; if (!shape.style) shape.style = {}; - + if (shape.type == annotorious.shape.ShapeType.RECTANGLE) { if (highlight) { - fill = shape.style.hi_fill || this._HI_FILL; - stroke = shape.style.hi_stroke || this._HI_STROKE; - outline = shape.style.hi_outline || this._HI_OUTLINE; - outline_width = shape.style.hi_outline_width || this._HI_OUTLINE_WIDTH; - stroke_width = shape.style.hi_stroke_width || this._HI_STROKE_WIDTH; + fill = shape.style.hiFill || this._properties.hiFill; + stroke = shape.style.hiStroke || this._properties.hiStroke; + outline = shape.style.hiOutline || this._properties.hiOutline; + outlineWidth = shape.style.hiOutlineWidth || this._properties.hiOutlineWidth; + strokeWidth = shape.style.hiStrokeWidth || this._properties.hiStrokeWidth; } else { - fill = shape.style.fill || this._FILL; - stroke = shape.style.stroke || this._STROKE; - outline = shape.style.outline || this._OUTLINE; - outline_width = shape.style.outline_width || this._OUTLINE_WIDTH; - stroke_width = shape.style.stroke_width || this._STROKE_WIDTH; + fill = shape.style.fill || this._properties.fill; + stroke = shape.style.stroke || this._properties.stroke; + outline = shape.style.outline || this._properties.outline; + outlineWidth = shape.style.outlineWidth || this._properties.outlineWidth; + strokeWidth = shape.style.strokeWidth || this._properties.strokeWidth; } - geom = shape.geometry; - // Outline - if (outline) { - g2d.lineJoin = "round"; - g2d.lineWidth = outline_width; - g2d.strokeStyle = outline; - g2d.strokeRect( - geom.x + outline_width/2, - geom.y + outline_width/2, - geom.width - outline_width, - geom.height - outline_width - ); + var tempGeom = Object.assign({}, geom); + if (geom.rotation != undefined && (!shape.mask || (shape.mask && shape.hasOwnProperty("_loadedMask")))) { + g2d.save(); + g2d.beginPath(); + + g2d.translate((geom.x + (geom.width / 2)), (geom.y + (geom.height / 2))); + g2d.rotate(geom.rotation * Math.PI / 180); } - // Stroke - if (stroke) { - g2d.lineJoin = "miter"; - g2d.lineWidth = stroke_width; - g2d.strokeStyle = stroke; - g2d.strokeRect( - geom.x + outline_width + stroke_width/2, - geom.y + outline_width + stroke_width/2, - geom.width - outline_width*2 - stroke_width, - geom.height - outline_width*2 - stroke_width - ); + //annotation has a mask + if (shape.mask) { + if (!shape.hasOwnProperty("_loadedMask") || shape["_loadedMask"].url != shape.mask) { + Object.defineProperty(shape, "_loadedMask", { + enumerable: false, + writable: true + }); + var self = this; + shape["_loadedMask"] = { + image: new Image, + url: shape.mask + } + shape["_loadedMask"].image.onload = function () { + self.drawShape(g2d, shape, highlight); + } + shape["_loadedMask"].image.src = shape.mask; + } + if (shape["_loadedMask"].image) { + g2d.globalAlpha = shape.style.maskTransparency || this._properties.maskTransparency; + if (geom.rotation != undefined) g2d.drawImage(shape["_loadedMask"].image, -(geom.width / 2), -(geom.height / 2), geom.width, geom.height); + else g2d.drawImage(shape["_loadedMask"].image, geom.x, geom.y, geom.width, geom.height); + g2d.globalAlpha = 1; + + if ((shape.style.maskBorder != undefined && !shape.style.maskBorder) || (shape.style.maskBorder == undefined && !this._properties.maskBorder)) return; + + if (geom.rotation != undefined) geom = { x: (geom.width / 2) + strokeWidth + outlineWidth, y: (geom.height / 2) + strokeWidth + outlineWidth, width: geom.width + strokeWidth + outlineWidth, height: geom.height + strokeWidth + outlineWidth, rotation: geom.rotation }; + else geom = { x: geom.x - strokeWidth - outlineWidth, y: geom.y - strokeWidth - outlineWidth, width: geom.width + strokeWidth + outlineWidth, height: geom.height + strokeWidth + outlineWidth, rotation: geom.rotation }; + } } + if (geom.rotation != undefined && (!shape.mask || (shape.mask && shape.hasOwnProperty("_loadedMask")))) { + var tempGeom = Object.assign({}, geom); + geom.x *= -1; + geom.y *= -1; + } + + // Outline + g2d.lineJoin = "round"; + g2d.lineWidth = outlineWidth; + g2d.strokeStyle = outline; + g2d.strokeRect( + geom.x + outlineWidth / 2, + geom.y + outlineWidth / 2, + geom.width - outlineWidth, + geom.height - outlineWidth + ); + + // Stroke + g2d.lineJoin = "miter"; + g2d.lineWidth = strokeWidth; + g2d.strokeStyle = stroke; + g2d.strokeRect( + geom.x + outlineWidth + strokeWidth / 2, + geom.y + outlineWidth + strokeWidth / 2, + geom.width - outlineWidth * 2 - strokeWidth, + geom.height - outlineWidth * 2 - strokeWidth + ); + // Fill if (fill) { g2d.lineJoin = "miter"; - g2d.lineWidth = stroke_width; + g2d.lineWidth = strokeWidth; g2d.fillStyle = fill; g2d.fillRect( - geom.x + outline_width + stroke_width/2, - geom.y + outline_width + stroke_width/2, - geom.width - outline_width*2 - stroke_width, - geom.height - outline_width*2 - stroke_width + geom.x + outlineWidth + strokeWidth / 2, + geom.y + outlineWidth + strokeWidth / 2, + geom.width - outlineWidth * 2 - strokeWidth, + geom.height - outlineWidth * 2 - strokeWidth ); } + + if (geom.rotation != undefined) { + g2d.restore(); + geom.x = tempGeom.x; + geom.y = tempGeom.y; + } + + return; } + + if (shape.type == annotorious.shape.ShapeType.POINT) { + fill = shape.style.fill || this._properties.fill; + strokeWidth = shape.style.strokeWidth || this._properties.strokeWidth; + g2d.beginPath(); + g2d.fillStyle = fill; + g2d.arc(geom.x, geom.y, strokeWidth, 0, strokeWidth * Math.PI, false); + g2d.fill(); + } +} + +/** + * Move the rectangle to the new point as the center + * @param {Object} g2d graphics context + * @param {annotorious.shape.Shape} shape the shape to draw + * @param {number} x the X coordinate + * @param {number} y the Y coordinate + */ +annotorious.plugins.selection.RectDragSelector.prototype.moveShape = function (g2d, shape, x, y) { + if (shape.type != annotorious.shape.ShapeType.RECTANGLE) return; + + var point = new annotorious.shape.geom.Point(x, y); + var geo = shape.geometry; + geo.x = point.x - geo.width / 2; + geo.y = point.y - geo.height / 2; + + this.drawShape(g2d, shape); + return shape; +} + +/** + * Rotate the rectangle by calculating the angle based on the new point and the center of the rectangle + * @param {Object} g2d graphics context + * @param {annotorious.shape.Shape} shape the shape to draw + * @param {number} x the X coordinate + * @param {number} y the Y coordinate + */ +annotorious.plugins.selection.RectDragSelector.prototype.rotateShape = function (g2d, shape, x, y) { + if (shape.type != annotorious.shape.ShapeType.RECTANGLE) return; + + var coordinate = new annotorious.shape.geom.Point(x, y); + var geom = shape.geometry; + var center = new annotorious.shape.geom.Point((geom.x + (geom.width / 2)), (geom.y + (geom.height / 2))); + + var rotation = Math.atan2(coordinate.y - center.y, coordinate.x - center.x) * 180 / Math.PI; + geom.rotation = rotation; + + this.drawShape(g2d, shape); + + return shape; } diff --git a/src/shape/arrow.js b/src/shape/arrow.js new file mode 100644 index 0000000..42be7a6 --- /dev/null +++ b/src/shape/arrow.js @@ -0,0 +1,41 @@ +goog.provide('annotorious.shape.geom.Arrow'); + +/** + * A Arrow. + * @param {annotorious.shape.geom.Point} arrowTail the point of arrow tail + * @param {annotorious.shape.geom.Point} arrowHead the point of arrow head + * @constructor + */ +annotorious.shape.geom.Arrow = function (arrowTail, arrowHead) { + this.arrowTail = arrowTail; + this.arrowHead = arrowHead; +} + +/** Arrow-specific helper functions & geometry computation utilities **/ + +/** + * Returns the rectangle formed by the ends of the arrow + * @param {annotorious.shape.geom.Arrow} arrow the arrow + * @return {annotorious.shape.geom.Rectangle} the rectangle + */ +annotorious.shape.geom.Arrow.getRectangle = function (arrow) { + var right, left; + if (arrow.arrowHead.x > arrow.arrowTail.x) { + right = arrow.arrowHead.x; + left = arrow.arrowTail.x; + } else { + right = arrow.arrowTail.x; + left = arrow.arrowHead.x; + } + + var top, bottom; + if (arrow.arrowHead.y > arrow.arrowTail.y) { + top = arrow.arrowTail.y; + bottom = arrow.arrowHead.y; + } else { + top = arrow.arrowHead.y; + bottom = arrow.arrowTail.y; + } + + return new annotorious.shape.geom.Rectangle(left, top, right - left, (bottom + 5) - top) +} diff --git a/src/shape/rectangle.js b/src/shape/rectangle.js index 67d2786..6839c29 100644 --- a/src/shape/rectangle.js +++ b/src/shape/rectangle.js @@ -8,7 +8,7 @@ goog.provide('annotorious.shape.geom.Rectangle'); * @param {number} height the rectangle height * @constructor */ -annotorious.shape.geom.Rectangle = function(x, y, width, height) { +annotorious.shape.geom.Rectangle = function (x, y, width, height) { if (width > 0) { this.x = x; this.width = width; @@ -24,5 +24,7 @@ annotorious.shape.geom.Rectangle = function(x, y, width, height) { this.y = y + height; this.height = -height; } + + this.rotation = 0; } diff --git a/src/shape/shape.js b/src/shape/shape.js index bc9bdbf..5e6e7b6 100644 --- a/src/shape/shape.js +++ b/src/shape/shape.js @@ -8,12 +8,15 @@ goog.require('annotorious.shape.geom.Rectangle'); * @param {annotorious.shape.ShapeType} type the shape type * @param {annotorious.shape.geom.Point | annotorious.shape.geom.Rectangle | annotorious.shape.geom.Polygon} geometry the geometry * @param {annotorious.shape.Units=} units geometry measurement units - * @param {Object} drawing style of the shape (optional) + * @param {Object} style style of the shape (optional) + * @param {string} mask the The URL of the mask - only if type is 'rect' (optional) * @constructor */ -annotorious.shape.Shape = function(type, geometry, units, style) { +annotorious.shape.Shape = function (type, geometry, units, style, mask) { this.type = type this.geometry = geometry; + if (mask && type == annotorious.shape.ShapeType.RECTANGLE) + this.mask = mask; if (units) this.units = units; if (style) @@ -29,7 +32,8 @@ annotorious.shape.Shape = function(type, geometry, units, style) { annotorious.shape.ShapeType = { POINT: 'point', RECTANGLE: 'rect', - POLYGON: 'polygon' + POLYGON: 'polygon', + ARROW: 'arrow' } /** @@ -52,29 +56,31 @@ annotorious.shape.Units = { * @param {number} py the Y coordinate * @return {boolean} true if the point intersects the shape */ -annotorious.shape.intersects = function(shape, px, py) { - if (shape.type == annotorious.shape.ShapeType.RECTANGLE) { - if (px < shape.geometry.x) +annotorious.shape.intersects = function (shape, px, py) { + if (shape.type == annotorious.shape.ShapeType.RECTANGLE || shape.type == annotorious.shape.ShapeType.ARROW) { + var geometry = (shape.type == annotorious.shape.ShapeType.ARROW) ? annotorious.shape.geom.Arrow.getRectangle(shape.geometry) : shape.geometry; + + if (px < geometry.x) return false; - if (py < shape.geometry.y) + if (py < geometry.y) return false; - if (px > shape.geometry.x + shape.geometry.width) + if (px > geometry.x + geometry.width) return false; - if (py > shape.geometry.y + shape.geometry.height) + if (py > geometry.y + geometry.height) return false; - + return true; } else if (shape.type == annotorious.shape.ShapeType.POLYGON) { var points = shape.geometry.points; var inside = false; var j = points.length - 1; - for (var i=0; i py) != (points[j].y > py) && - (px < (points[j].x - points[i].x) * (py - points[i].y) / (points[j].y-points[i].y) + points[i].x)) { + for (var i = 0; i < points.length; i++) { + if ((points[i].y > py) != (points[j].y > py) && + (px < (points[j].x - points[i].x) * (py - points[i].y) / (points[j].y - points[i].y) + points[i].x)) { inside = !inside; } j = i; @@ -82,7 +88,7 @@ annotorious.shape.intersects = function(shape, px, py) { return inside; } - + return false; } @@ -91,9 +97,10 @@ annotorious.shape.intersects = function(shape, px, py) { * @param {annotorious.shape.Shape} shape the shape * @return {number} the size */ -annotorious.shape.getSize = function(shape) { - if (shape.type == annotorious.shape.ShapeType.RECTANGLE) { - return shape.geometry.width * shape.geometry.height; +annotorious.shape.getSize = function (shape) { + if (shape.type == annotorious.shape.ShapeType.RECTANGLE || shape.type == annotorious.shape.ShapeType.ARROW) { + var geometry = (shape.type == annotorious.shape.ShapeType.ARROW) ? annotorious.shape.geom.Arrow.getRectangle(shape.geometry) : shape.geometry; + return geometry.width * geometry.height; } else if (shape.type == annotorious.shape.ShapeType.POLYGON) { return Math.abs(annotorious.shape.geom.Polygon.computeArea(shape.geometry.points)); } @@ -105,7 +112,7 @@ annotorious.shape.getSize = function(shape) { * @param {annotorious.shape.Shape} shape the shape * @return {annotorious.shape.Shape | undefined} the bounding rectangle */ -annotorious.shape.getBoundingRect = function(shape) { +annotorious.shape.getBoundingRect = function (shape) { if (shape.type == annotorious.shape.ShapeType.RECTANGLE) { return shape; } else if (shape.type == annotorious.shape.ShapeType.POLYGON) { @@ -116,7 +123,7 @@ annotorious.shape.getBoundingRect = function(shape) { var top = points[0].y; var bottom = points[0].y; - for (var i=1; i right) right = points[i].x; @@ -129,13 +136,18 @@ annotorious.shape.getBoundingRect = function(shape) { if (points[i].y < top) top = points[i].y; } - - return new annotorious.shape.Shape(annotorious.shape.ShapeType.RECTANGLE, + + return new annotorious.shape.Shape(annotorious.shape.ShapeType.RECTANGLE, new annotorious.shape.geom.Rectangle(left, top, right - left, bottom - top), false, shape.style ); + } else if (shape.type == annotorious.shape.ShapeType.ARROW) { + return new annotorious.shape.Shape(annotorious.shape.ShapeType.RECTANGLE, + annotorious.shape.geom.Arrow.getRectangle(shape.geometry), + false, shape.style + ); } - + return undefined; } @@ -144,14 +156,14 @@ annotorious.shape.getBoundingRect = function(shape) { * @param {annotorious.shape.Shape} shape the shape * @returns {annotorious.shape.geom.Point | undefined} the centroid X/Y coordinate */ -annotorious.shape.getCentroid = function(shape) { - if (shape.type == annotorious.shape.ShapeType.RECTANGLE) { - var rect = shape.geometry; +annotorious.shape.getCentroid = function (shape) { + if (shape.type == annotorious.shape.ShapeType.RECTANGLE || shape.type == annotorious.shape.ShapeType.ARROW) { + var rect = (shape.type == annotorious.shape.ShapeType.ARROW) ? annotorious.shape.geom.Arrow.getRectangle(shape.geometry) : shape.geometry; return new annotorious.shape.geom.Point(rect.x + rect.width / 2, rect.y + rect.height / 2); } else if (shape.type == annotorious.shape.ShapeType.POLYGON) { - return annotorious.shape.geom.Polygon.computeCentroid( shape.geometry.points); + return annotorious.shape.geom.Polygon.computeCentroid(shape.geometry.points); } - + return undefined; } @@ -160,12 +172,12 @@ annotorious.shape.getCentroid = function(shape) { * @param {annotorious.shape.Shape} shape the shape * @param {number} delta the delta */ -annotorious.shape.expand = function(shape, delta) { +annotorious.shape.expand = function (shape, delta) { // TODO for the sake of completeness: implement for RECTANGLE return new annotorious.shape.Shape(annotorious.shape.ShapeType.POLYGON, new annotorious.shape.geom.Polygon(annotorious.shape.geom.Polygon.expandPolygon(shape.geometry.points, delta)), false, shape.style); - } +} /** * Transforms a shape from a source coordinate system to a destination coordinate @@ -175,21 +187,30 @@ annotorious.shape.expand = function(shape, delta) { * @param {Function} transformationFn the transformation function * @return {annotorious.shape.Shape | undefined} the transformed shape */ -annotorious.shape.transform = function(shape, transformationFn) { +annotorious.shape.transform = function (shape, transformationFn) { if (shape.type == annotorious.shape.ShapeType.RECTANGLE) { + var geom = shape.geometry; var transformed = transformationFn(geom); - return new annotorious.shape.Shape(annotorious.shape.ShapeType.RECTANGLE, transformed, false, shape.style); + transformed.rotation = geom.rotation; + + return new annotorious.shape.Shape(annotorious.shape.ShapeType.RECTANGLE, transformed, false, shape.style, shape.mask); } else if (shape.type == annotorious.shape.ShapeType.POLYGON) { var transformedPoints = []; - goog.array.forEach(shape.geometry.points, function(pt) { + goog.array.forEach(shape.geometry.points, function (pt) { transformedPoints.push(transformationFn(pt)); }); return new annotorious.shape.Shape(annotorious.shape.ShapeType.POLYGON, new annotorious.shape.geom.Polygon(transformedPoints), false, shape.style ); - } + } else if (shape.type == annotorious.shape.ShapeType.ARROW) return new annotorious.shape.Shape( + annotorious.shape.ShapeType.ARROW, + new annotorious.shape.geom.Arrow( + transformationFn(shape.geometry.arrowTail), + transformationFn(shape.geometry.arrowHead) + ), false, shape.style + ); return undefined; } @@ -201,6 +222,6 @@ annotorious.shape.transform = function(shape, transformationFn) { * @param {annotorious.shape.Shape} shape the shape * @return {string} a 'hashcode' for the shape */ -annotorious.shape.hashCode = function(shape) { +annotorious.shape.hashCode = function (shape) { return JSON.stringify(shape.geometry); } diff --git a/templates/core_elements.soy b/templates/core_elements.soy index e8f3796..bd00801 100644 --- a/templates/core_elements.soy +++ b/templates/core_elements.soy @@ -6,8 +6,14 @@ {template .popup}
- EDIT - DELETE +
+ EDIT + DELETE +
+
+ MOVE + ROTATE +
@@ -22,8 +28,8 @@
diff --git a/test/image/image-human-brain_99433-298.jpg b/test/image/image-human-brain_99433-298.jpg new file mode 100644 index 0000000..30c17b3 Binary files /dev/null and b/test/image/image-human-brain_99433-298.jpg differ diff --git a/test/image/index.html b/test/image/index.html index 77bce69..9958a44 100644 --- a/test/image/index.html +++ b/test/image/index.html @@ -1,145 +1,263 @@ - - - - - + + - - - - -
-

Annotation Test Page

-

- This page serves as a development environment for the Annotorious JavaScript - image annotation library. Hover over one of the annotatable images to get started. - NOTE: make sure the plovr build tool is running! Use -

-

- java -jar plovr/plovr.jar serve standalone.json -

- to start plovr. -

- - - -

Test Images

-
- -

- Hallstatt, Austria. By Nick Csakany, 2007. Public Domain. Source: - Wikimedia - Commons -

-
-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut varius - diam posuere quam molestie vestibulum. Vestibulum non volutpat elit. Integer - vitae felis eget magna rutrum sagittis. Nulla facilisi. Praesent a consectetur - velit. Cras eget nibh est, eu imperdiet mauris. Nulla quis justo urna. Sed eu - rutrum mauris. Integer aliquet nulla sit amet ante mollis pellentesque. - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut varius - diam posuere quam molestie vestibulum. Vestibulum non volutpat elit. Integer - vitae felis eget magna rutrum sagittis. Nulla facilisi. Praesent a consectetur - velit. Cras eget nibh est, eu imperdiet mauris. Nulla quis justo urna. Sed eu - rutrum mauris. Integer aliquet nulla sit amet ante mollis pellentesque. - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut varius - diam posuere quam molestie vestibulum. Vestibulum non volutpat elit. Integer - vitae felis eget magna rutrum sagittis. Nulla facilisi. Praesent a consectetur - velit. Cras eget nibh est, eu imperdiet mauris. Nulla quis justo urna. Sed eu - rutrum mauris. Integer aliquet nulla sit amet ante mollis pellentesque. + anno.addHandler('onAnnotationCreated', function (annotation, item) { + console.log("<--- Annotation create --->"); + //annotation.setMask("./image-human-brain_99433-298.jpg", 0, 0.5, false); + //anno.reload(); + console.log(annotation); + //console.log("IntersectedAnnotations", anno.getIntersectedAnnotations(annotation)); + console.log("<--- Annotation create --->"); + }); + anno.addHandler('onAnnotationRemoved', function (annotation) { + console.log("<--- Annotation removed --->"); + console.log(annotation); + console.log("<--- Annotation removed --->"); + }); + anno.addHandler('onAnnotationUpdated', function (annotation, old) { + console.log("<--- Annotation updated --->"); + console.log(annotation); + console.log(old); + console.log("<--- Annotation updated --->"); + }); + anno.addHandler('onMouseMoveOverItem', function (pixels, event) { + $("#showPixel").html("cursor: [x=" + pixels.cursor.x + ", y=" + pixels.cursor.y + "]" + ((pixels.box) ? " box: [x=" + pixels.box.x + ", y=" + pixels.box.y + ", width=" + pixels.box.width + ", height=" + pixels.box.height + "]" : "")) + }); + }); + + anno.addHandler('onAnnotationMoved', function (annotation) { + console.log("<--- Annotation moved --->"); + console.log(annotation); + console.log("<--- Annotation moved --->"); + }); + + anno.addHandler('onAnnotationRotated', function (annotation) { + console.log("<--- Annotation rotated --->"); + console.log(annotation); + console.log("<--- Annotation rotated --->"); + }); + + var properties = { + displayMessage: "Annotate", //the message to display as hint [OPTIONAL] + outputUnits: "pixel", // measurement units used for the output geometry ['pixel', 'fraction'] [pixels are relative to the original image size] [OPTIONAL] + shapeStyle: { //global style [OPTIONAL] + + outline: '#ff0000', //outline color for annotation and selection shapes [OPTIONAL] + outlineWidth: 2, //outline width for annotation and selection shapes [1-12] [OPTIONAL] + + hiOutline: '#ffff00', // outline color for highlighted annotation shapes [OPTIONAL] + hiOutlineWidth: 3, // outline width for highlighted annotation shapes [1-12] [OPTIONAL] + + stroke: '#ff3399', // stroke color for annotation and selection shapes [OPTIONAL] + strokeWidth: 4, // stroke width for annotation and selection shapes [1-12] [OPTIONAL] + + hiStroke: '#006600', // stroke color for highlighted annotation shapes [OPTIONAL] + hiStrokeWidth: 6.2, //stroke width for highlighted annotation shapes [1-12] [OPTIONAL] + + maskTransparency: 1, //transparency for annotation mask [0-1] [OPTIONAL] + maskBorder: true, //if false, not show the mask border [OPTIONAL] + + highlightTail: "rgba(0, 0,255,0.7)", //color to highlight the tail of the arrow shape [OPTIONAL] + hiTailRadius: 5, // arrow tail highlight radius [OPTIONAL] + highlightHead: "rgba(0, 0,255,0.7)", //color to highlight the head of the arrow shape [OPTIONAL] + hiHeadRadius: 5 // arrow head highlight radius [OPTIONAL] + }, + popup: { //properties for popup GUI widget + showMoveButton: false, //if false, not show the move button + showRotateButton: true //if false, not show the rotate button + }, + }; + + + + + +

+

Annotation Test Page

+

+ This page serves as a development environment for the Annotorious JavaScript + image annotation library. Hover over one of the annotatable images to get started. + NOTE: make sure the plovr build tool is running! Use +

+

+ java -jar plovr/plovr.jar serve standalone.json +

+ to start plovr. +

+ + + + +
+ + + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +

Test Images

+
+ +

cursor: [x=0, y=0]

+

+ Hallstatt, Austria. By Nick Csakany, 2007. Public Domain. Source: + Wikimedia + Commons

-
- -

- Medival Ideal Portrait of Ptolemy. Public Domain. Source: - Wikimedia +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut varius + diam posuere quam molestie vestibulum. Vestibulum non volutpat elit. Integer + vitae felis eget magna rutrum sagittis. Nulla facilisi. Praesent a consectetur + velit. Cras eget nibh est, eu imperdiet mauris. Nulla quis justo urna. Sed eu + rutrum mauris. Integer aliquet nulla sit amet ante mollis pellentesque. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut varius + diam posuere quam molestie vestibulum. Vestibulum non volutpat elit. Integer + vitae felis eget magna rutrum sagittis. Nulla facilisi. Praesent a consectetur + velit. Cras eget nibh est, eu imperdiet mauris. Nulla quis justo urna. Sed eu + rutrum mauris. Integer aliquet nulla sit amet ante mollis pellentesque. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut varius + diam posuere quam molestie vestibulum. Vestibulum non volutpat elit. Integer + vitae felis eget magna rutrum sagittis. Nulla facilisi. Praesent a consectetur + velit. Cras eget nibh est, eu imperdiet mauris. Nulla quis justo urna. Sed eu + rutrum mauris. Integer aliquet nulla sit amet ante mollis pellentesque. +

+
-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut varius - diam posuere quam molestie vestibulum. Vestibulum non volutpat elit. Integer - vitae felis eget magna rutrum sagittis. Nulla facilisi. Praesent a consectetur - velit. Cras eget nibh est, eu imperdiet mauris. Nulla quis justo urna. Sed eu - rutrum mauris. Integer aliquet nulla sit amet ante mollis pellentesque. - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut varius - diam posuere quam molestie vestibulum. Vestibulum non volutpat elit. Integer - vitae felis eget magna rutrum sagittis. Nulla facilisi. Praesent a consectetur - velit. Cras eget nibh est, eu imperdiet mauris. Nulla quis justo urna. Sed eu - rutrum mauris. Integer aliquet nulla sit amet ante mollis pellentesque. - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut varius - diam posuere quam molestie vestibulum. Vestibulum non volutpat elit. Integer - vitae felis eget magna rutrum sagittis. Nulla facilisi. Praesent a consectetur - velit. Cras eget nibh est, eu imperdiet mauris. Nulla quis justo urna. Sed eu - rutrum mauris. Integer aliquet nulla sit amet ante mollis pellentesque. - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut varius - diam posuere quam molestie vestibulum. Vestibulum non volutpat elit. Integer - vitae felis eget magna rutrum sagittis. Nulla facilisi. Praesent a consectetur - velit. Cras eget nibh est, eu imperdiet mauris. Nulla quis justo urna. Sed eu - rutrum mauris. Integer aliquet nulla sit amet ante mollis pellentesque.

-

-
- -

- Ptolemaic Map, 16th century. Public Domain. Source: - Wikimedia +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut varius + diam posuere quam molestie vestibulum. Vestibulum non volutpat elit. Integer + vitae felis eget magna rutrum sagittis. Nulla facilisi. Praesent a consectetur + velit. Cras eget nibh est, eu imperdiet mauris. Nulla quis justo urna. Sed eu + rutrum mauris. Integer aliquet nulla sit amet ante mollis pellentesque. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut varius + diam posuere quam molestie vestibulum. Vestibulum non volutpat elit. Integer + vitae felis eget magna rutrum sagittis. Nulla facilisi. Praesent a consectetur + velit. Cras eget nibh est, eu imperdiet mauris. Nulla quis justo urna. Sed eu + rutrum mauris. Integer aliquet nulla sit amet ante mollis pellentesque. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut varius + diam posuere quam molestie vestibulum. Vestibulum non volutpat elit. Integer + vitae felis eget magna rutrum sagittis. Nulla facilisi. Praesent a consectetur + velit. Cras eget nibh est, eu imperdiet mauris. Nulla quis justo urna. Sed eu + rutrum mauris. Integer aliquet nulla sit amet ante mollis pellentesque. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut varius + diam posuere quam molestie vestibulum. Vestibulum non volutpat elit. Integer + vitae felis eget magna rutrum sagittis. Nulla facilisi. Praesent a consectetur + velit. Cras eget nibh est, eu imperdiet mauris. Nulla quis justo urna. Sed eu + rutrum mauris. Integer aliquet nulla sit amet ante mollis pellentesque. +

+

+
-

- This image is not supposed to be annotatable!

-

- - +

+ This image is not supposed to be annotatable! +

+

+
+ + + \ No newline at end of file