diff --git a/css/toolbar/annotation-tool.css b/css/toolbar/annotation-tool.css
index ed708e29..3ab185d7 100644
--- a/css/toolbar/annotation-tool.css
+++ b/css/toolbar/annotation-tool.css
@@ -109,4 +109,108 @@
opacity: 1;
visibility: visible;
flex: 0 0 100%;
+}
+
+.textOptions {
+ display: flex;
+}
+
+.fontInput {
+ width: 6em;
+ background: #407ccb;
+ opacity: 1;
+ border: none;
+ color: white;
+ display: block;
+}
+
+.hideElement {
+ display: none !important;
+}
+
+.fontInput::placeholder {
+ color: white;
+}
+
+/* Font input */
+.fontSizeInput {
+ display: flex;
+ align-items: center;
+ width: 200px;
+}
+
+.fontInput {
+ padding: 8px;
+ width: 42px;
+ text-align: center;
+}
+
+.decreaseFontSize,
+.increaseFontSize {
+ padding: 8px;
+ background: #407ccb;
+ opacity: 1;
+ border: none;
+ color: white;
+ display: block;
+ cursor: pointer;
+}
+
+.decreaseFontSize:hover,
+.increaseFontSize:hover {
+ background-color: #18548d;
+}
+
+.decreaseFontSize:active,
+.increaseFontSize:active {
+ background-color: #18548d;
+ color: #fff;
+}
+
+.textOptions span {
+ padding: 9px;
+}
+
+.fontSelection {
+ display: flex;
+ flex-direction: column;
+ position: relative;
+}
+
+.fontSizeValue {
+ position: absolute;
+ top: 100%;
+}
+
+.fontSizeValue div {
+ background: #407ccb;
+ width: 42px;
+ text-align: center;
+ padding: 2px 0;
+ opacity: 1;
+ border: none;
+ color: white;
+ display: block;
+ cursor: pointer;
+}
+
+.fontSizeValue div:hover {
+ background-color: #18548d;
+
+}
+
+/* Chrome, Safari, Edge, Opera */
+.fontInput::-webkit-outer-spin-button,
+.fontInput::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+}
+
+/* Firefox */
+.fontInput[type=number] {
+ -moz-appearance: textfield;
+}
+
+.textOptions span {
+ padding: 9px !important;
}
\ No newline at end of file
diff --git a/css/viewer/annotationContainer.css b/css/viewer/annotationContainer.css
index 459585be..2d4a43e8 100644
--- a/css/viewer/annotationContainer.css
+++ b/css/viewer/annotationContainer.css
@@ -47,3 +47,58 @@
#channel-names-overlay-container .channel-label-overlay.visible {
display: inline-block;
}
+
+
+/*
+CSS for text annotation tool
+*/
+
+.text-foreign-object {
+ text-align: left;
+ overflow: visible;
+}
+
+.text-foreign-object-div {
+ display: inline-block;
+ border: 100px solid red;
+ padding: 500px;
+ cursor: move;
+ outline: none;
+}
+
+.insideforeign:hover {
+ cursor: move;
+}
+
+.text-foreign-object-input {
+ width: 100%;
+ height: 100%;
+ background: transparent;
+ outline: none;
+}
+
+.text-foreign-object-resizer-right {
+ background: red;
+ z-index: 10;
+ position: absolute !important;
+ transform: translate(-50%, -50%);
+ top: 50%;
+ left: 100%;
+ cursor: ew-resize;
+}
+
+.text-foreign-object-div * {
+ -webkit-touch-callout: none; /* iOS Safari */
+ -webkit-user-select: none; /* Safari */
+ -khtml-user-select: none; /* Konqueror HTML */
+ -moz-user-select: none; /* Firefox */
+ -ms-user-select: none; /* Internet Explorer/Edge */
+ user-select: none; /* Non-prefixed version, currently
+ supported by Chrome and Opera */
+}
+
+/* Text highlight */
+.text-hover:hover {
+ font-weight: 900;
+ cursor: pointer;
+}
\ No newline at end of file
diff --git a/docs/dev-docs/annotation-tool.md b/docs/dev-docs/annotation-tool.md
new file mode 100644
index 00000000..de22ac7e
--- /dev/null
+++ b/docs/dev-docs/annotation-tool.md
@@ -0,0 +1,145 @@
+# Annotation tool
+
+This document will summarize the implementation details related to annotation tools.
+
+## Table of contents
+
+- [Text Annotation](#text-annotation)
+ - [Creating a Text Annotation](#creating-a-text-annotation)
+ - [Annotation Toolbar Interaction](#annotation-toolbar-interaction)
+ - [OSD Canvas Interaction](#osd-canvas-interaction)
+ - [Text Input Interaction](#text-input-interaction)
+ - [Export/Import of Text Annotations](#exportimport-of-text-annotations)
+ - [Export Functionality](#export-functionality)
+ - [Import Functionality](#import-functionality)
+ - [Additional Resources](#additional-resources)
+- [Arrow Line Annotation](#arrow-line-annotation)
+ - [Core Changes](#core-changes)
+ - [`arrowline.js`](#arrowlinejs)
+ - [`base.js`](#basejs)
+ - [`mview.html`](#mviewhtml)
+ - [`annotation-svg.js`](#annotation-svgjs)
+ - [`annotation-group.js`](#annotation-groupjs)
+ - [`annotation-tool.js`](#annotation-tooljs)
+ - [Notable Updates](#notable-updates)
+ - [Fixes and Adjustments](#fixes-and-adjustments)
+ - [Additional Resources](#additional-resources-1)
+
+
+## Text Annotation
+
+This documentation is designed to guide you through the process of creating, saving, and loading text annotations with our application. By following the flow of creating a text annotation, we'll not only explore its feature set but also delve into the underlying code logic.
+
+### Creating a Text Annotation
+
+The creation of a text annotation involves user interactions with three critical components:
+
+1. Annotation Toolbar
+2. OSD (OpenSeadragon) Canvas
+3. Text Input
+
+#### Annotation Toolbar Interaction
+
+The user can initiate the text annotation creation process by selecting the text annotation option from the toolbar. This selection activates the drawing mode and the related text annotation tools. The toolbar facilitates the user to manipulate the text annotation in the following ways:
+
+1. **Font-size Input**: Users can specify the desired font size directly within the input field. This scaled font size feature allows users to set a size that aligns with other screen elements. Notably, the font size dynamically adjusts according to the image scale, which is elaborated in the code.
+
+2. **Size Adjustment**: The toolbar offers two buttons to either increase or decrease the font size by a predetermined step, typically one unit.
+
+**Code Logic:**
+
+We have event listeners attached to each of the three controls: font size input, increase, and decrease buttons. These listeners are defined in the `annotation-tool.js` file. User interaction with any of these controls triggers the corresponding event listener, which in turn raises an internal event to accomplish the desired action.
+
+#### OSD Canvas Interaction
+
+Users can place the annotation anywhere on the image with a simple mouse click. Each click positions the annotation at the mouse cursor's location. Further clicks either remove an empty annotation or convert a non-empty annotation into a text (`
` tag), additionally prompting the user with a new text input field.
+
+**Code Logic:**
+
+With the text annotation mode enabled, a user's click on the canvas checks for any previously drawn text annotation. If one exists, we transform the previous annotation into a text (`
` tag). This transformation involves removing the `div` containing the input textarea and appending the `p` tag with the same attributes and styling as the textarea to the foreign object. We then create a new text annotation object, and new mouse trackers are added to listen for the user's click event to place the annotation on the image. The `onMouseClickToDraw` function in `viewer.js` handles these functionalities.
+
+#### Text Input Interaction
+
+Once the annotation is positioned, the user can interact with it in several ways:
+
+1. **Text Entry**: Users can input their desired text into the text area.
+2. **Annotation Movement**: Users can relocate the annotation across the canvas by clicking and dragging the text area borders.
+3. **Text Box Resizing**: Users can adjust the horizontal dimensions of the text box by dragging the resize control.
+
+**Code Logic:**
+
+The `text.js` file contains all the functions that handle text interaction. For text entry, the textarea manages the input functionality. Event listeners on the input event adjust the textarea's height after each input change to accommodate the text as it shifts to a new line. The drag functionality, or annotation movement, is handled by the event handlers on the `mouseover` event of the `div` containing the textarea. The application performs basic arithmetic computation to accurately adjust the `div`'s position. The resize control handles the text box resizing, listening for the same event and adjusting the width of the `div` containing the textarea accordingly. These functionalities are elaborated in detail within the code comments.
+
+
+
+### Export/Import of Text Annotations
+
+#### Export Functionality
+Given that we utilize a foreign object and HTML elements to facilitate the text annotation functionality, our export process differs from other annotations that use the `exportToSVG` function found in the `base.js` file. Instead, we directly return the `outerHTML` of the foreign object. This HTML string is then appended to the SVG annotation string used for export.
+
+#### Import Functionality
+In the import process, we need to load the foreign object into the SVG as a text annotation. This involves cloning the node of the foreign object and creating a new `
` tag that retains the same attributes as the imported foreign object. Instead of generating a new foreign object from scratch, we repurpose the imported one. Thanks to the DOM parser treating the ForeignObject as an SVG element, we can directly attach it to the SVG. As of the current version, we do not have an edit functionality for imported annotations.
+
+### Additional Resources
+
+For more details and context regarding the Text Annotation feature, please refer to the following GitHub links:
+
+1. [OpenSeadragon Viewer Issue #102](https://github.com/informatics-isi-edu/openseadragon-viewer/issues/102)
+2. [OpenSeadragon Viewer Issue #103](https://github.com/informatics-isi-edu/openseadragon-viewer/issues/103)
+3. [OpenSeadragon Viewer Pull Request #105](https://github.com/informatics-isi-edu/openseadragon-viewer/pull/105)
+
+These links provide further discussions and insights into the design and implementation of the Text Annotation feature.
+
+
+
+## Arrow Line Annotation
+
+This section of the documentation details the Arrow Line Annotation functionality that has been added to the viewer application. This feature uses SVG line shapes to create arrow annotations, appending an arrowhead at the line's end using the `marker-end` attribute. The SVG marker definition for the arrowhead is added to the SVG definitions (`defs` tag) for the corresponding annotation SVG and is then referenced in the line's `marker-end` attribute.
+
+### Core Changes
+
+#### `arrowline.js`
+This new file contains the functionality for the Arrow Line Annotation tool.
+
+#### `base.js`
+Modifications were made to handle the `marker-end` attribute, which is only applicable to the Arrow Line Annotation type. Furthermore, changes were made to support the exporting of the required arrowhead marker definitions.
+
+#### `mview.html`
+The `arrowline.js` script is now being loaded.
+
+#### `annotation-svg.js`
+This file now includes code for:
+- Adding the marker definition to the annotation SVG.
+- Changing the arrowhead's color upon stroke color change.
+- Parsing saved arrow line annotations correctly and creating corresponding marker SVG definitions to match the stroke colors of the loaded arrow lines.
+
+#### `annotation-group.js`
+The Arrow Line Annotation type case has been added, handling the addition of marker definitions to the annotation group and managing the change of the arrowhead color upon drawing stroke color change.
+
+#### `annotation-tool.js`
+HTML code modifications were made to render the Arrow Line option in the tool, along with other case changes related to the Arrow Line Annotation.
+
+### Notable Updates
+
+Instead of the SVG element, the marker definition now gets added to the `g` (group) tag for each group. Marker definitions are exported and saved alongside the annotation SVG, allowing the SVGs to work with other viewers if needed. The saved annotations are loaded differently, and the SVG nodes are parsed in a new way.
+
+The Arrow Line Annotation supports multiple arrowhead styles, but for now, only the solid arrow style is visible. The code for additional styles is included in the pull request, but these are currently disabled at the rendering level. The HTML code for these styles is commented out in `annotation-tool.js`.
+
+### Fixes and Adjustments
+
+To ensure unique marker IDs for the marker definitions, unique marker definitions are now created for Arrow Line Annotations. This change addressed an issue where SVG definitions were added to the same namespace as the document.
+
+A bug that resulted in the marker definition function getting called while saving the SVG was fixed. This was achieved by removing `marker-end` and `data-markerid` attributes from the default attribute list in the `base.js` file, which previously added the attributes to all annotations.
+
+Lastly, the arrowhead marker definition was modified to reduce the dimensions of the arrowhead, ensuring a reasonable size when highlighted. This change involved adjusting the `d` values of the path SVG element in the marker head, as well as the marker dimensions and reference coordinates in the marker tag.
+
+Thank you for providing the additional link. Here's the updated section:
+
+### Additional Resources
+
+For a more comprehensive understanding and deeper insights into the Arrow Line Annotation feature, kindly refer to the following GitHub links:
+
+1. [OpenSeadragon Viewer Issue #92](https://github.com/informatics-isi-edu/openseadragon-viewer/issues/92)
+2. [OpenSeadragon Viewer Pull Request #100](https://github.com/informatics-isi-edu/openseadragon-viewer/pull/100)
+
+These links can provide further context and discussions that were crucial to the design and implementation of the Arrow Line Annotation feature.
\ No newline at end of file
diff --git a/js/app.js b/js/app.js
index 8a90250d..c457117d 100644
--- a/js/app.js
+++ b/js/app.js
@@ -71,6 +71,10 @@ var OSDViewer = (function (_config) {
case "zoomOut":
viewer.zoomOut();
break;
+ // Text annotation input font size change
+ case "changeTextSize":
+ viewer.dispatchSVGEvent(type, data);
+ break;
// [Events from Viewer]
// Send the updated annotation list to toolbar
case "updateAnnotationList":
diff --git a/js/toolbar/annotation-tool.js b/js/toolbar/annotation-tool.js
index 1e84eaa0..2f2967ad 100644
--- a/js/toolbar/annotation-tool.js
+++ b/js/toolbar/annotation-tool.js
@@ -28,6 +28,7 @@ function AnnotationTool(parent){
btnSubtype = "solid";
}
+
if (btnType == "HELP") {
_self.dispatchEvent('openDrawingHelpPage');
return;
@@ -95,6 +96,33 @@ function AnnotationTool(parent){
"",
"",
"",
+ "
",
+ "
",
+ "",
+ "",
+ "
",
+ "
",
"",
"",
"",
@@ -172,6 +200,14 @@ function AnnotationTool(parent){
_self.curSubtype = modeSubtype || '';
_self.elem.querySelector(".toolBtn[data-type='"+_self.curType+"']").className = "toolBtn selected";
+
+ if(_self.curType != "TEXT"){
+ _self.elem.querySelector(".fontSizeInput").classList.add("hideElement");
+ }
+ else {
+ // Function call to handle special functions for different annotation types
+ _self.handleTextAnnotationFunctions(mode, modeSubtype);
+ }
switch(_self.curType){
case "CURSOR":
@@ -206,6 +242,17 @@ function AnnotationTool(parent){
}
});
break;
+ case "TEXT":
+ _self.dispatchEvent("drawingStart", {
+ svgID : _self.curSVGID,
+ groupID : _self.curGroupID,
+ type : _self.curType,
+ attrs : {
+ "stroke" : _self.curStroke,
+ "fill" : _self.curStroke,
+ }
+ });
+ break;
case "ERASER":
_self.dispatchEvent("setMode", {
mode : "ERASE_ANNOTATIONS"
@@ -230,4 +277,103 @@ function AnnotationTool(parent){
stroke : this.curStroke
}
}
+
+ /**
+ * Function to handle the specific annotation behaviour/functions
+ * @param {*} type type of annotation
+ * @param {*} subtype subtype of annotation
+ */
+ this.handleTextAnnotationFunctions = function(type, subtype){
+
+ if(type === "TEXT"){
+
+ var textSizeDiv = document.querySelector(".fontSizeInput");
+ textSizeDiv.classList.remove("hideElement");
+ var textOptionsDiv = document.querySelector(".fontSizeValue");
+ textOptionsDiv.classList.add("hideElement");
+
+ var textSizeInput = document.querySelector(".fontInput");
+
+ // Functions to handle the font size input functionality in the toolbar
+ if(textSizeInput != null){
+
+ textSizeInput.addEventListener('focus', function (e) {
+ textOptionsDiv = document.querySelector(".fontSizeValue");
+ textOptionsDiv.classList.remove("hideElement");
+
+ var fontSizes = document.querySelectorAll(".fontSizeValue div");
+ for(var i = 0; i < fontSizes.length; i++){
+ fontSizes[i].addEventListener('click', function(e){
+ textSizeInput.value = this.innerHTML;
+ textOptionsDiv.classList.add("hideElement");
+ // Trigger an input event on the input element
+ var event = new Event('input', {
+ bubbles: true,
+ cancelable: true,
+ });
+ textSizeInput.dispatchEvent(event);
+ });
+ }
+ });
+
+ textSizeInput.addEventListener('input', function (e) {
+ if((this.value != null) && (this.value.trim() != "")){
+ fontSize = this.value.trim();
+ _self.dispatchEvent("changeTextSize", {
+ svgID: _self.curSVGID,
+ groupID: _self.curGroupID,
+ type: _self.curType,
+ fontSize: fontSize
+ });
+ }
+ });
+
+ var decreaseFontSize = document.querySelector(".decreaseFontSize");
+ decreaseFontSize.addEventListener('click', function (e) {
+ if((textSizeInput.value != null) && (textSizeInput.value.trim() != "")){
+ fontSize = textSizeInput.value.trim();
+ fontSize = parseInt(fontSize) - 1;
+ if(fontSize > 0){
+ textSizeInput.value = fontSize;
+ _self.dispatchEvent("changeTextSize", {
+ svgID: _self.curSVGID,
+ groupID: _self.curGroupID,
+ type: _self.curType,
+ fontSize: fontSize
+ });
+ }
+ }
+ });
+
+ var increaseFontSize = document.querySelector(".increaseFontSize");
+ increaseFontSize.addEventListener('click', function (e) {
+ if((textSizeInput.value != null) && (textSizeInput.value.trim() != "")){
+ fontSize = textSizeInput.value.trim();
+ fontSize = parseInt(fontSize) + 1;
+ if(fontSize > 0){
+ textSizeInput.value = fontSize;
+ _self.dispatchEvent("changeTextSize", {
+ svgID: _self.curSVGID,
+ groupID: _self.curGroupID,
+ type: _self.curType,
+ fontSize: fontSize
+ });
+ }
+ }
+ });
+ }
+ }
+ else {
+ // Remove any event listeners on the fontsize input if present
+ var textSizeInput = document.querySelector(".fontInput");
+ if(textSizeInput != null){
+
+ // Get all event listeners for textSizeInput and remove them
+ var elClone = textSizeInput.cloneNode(true);
+ textSizeInput.parentNode.replaceChild(elClone, textSizeInput);
+ textSizeInput = elClone;
+ }
+ }
+
+ }
}
diff --git a/js/viewer/annotation/annotation-group.js b/js/viewer/annotation/annotation-group.js
index ccf737b6..08175faf 100644
--- a/js/viewer/annotation/annotation-group.js
+++ b/js/viewer/annotation/annotation-group.js
@@ -23,11 +23,13 @@ function AnnotationGroup(id, anatomy, description, parent){
/**
* This function adds the given type of annotation to the annotation group.
* @param {string} type - Type of the annotation.
- * @param {string} subtype - Subtype of the annotation, for example the type of arrowhead for arrowline annotation.
- * @param {object} annotAttrs - Attributes to be added to the annotation.
+ * @param {string?} subtype - Subtype of the annotation, for example the type of arrowhead for arrowline annotation.
+ * @param {object?} annotAttrs - Attributes to be added to the annotation.
+ * @param {object?} origSVGNode - a copy of svg node that is drawn (useful for foreignObject)
* @returns
*/
- this.addAnnotation = function(type, subtype, annotAttrs){
+ this.addAnnotation = function(type, subtype, annotAttrs, origSVGNode){
+
var annotation = null;
var graphID = Date.parse(new Date()) + parseInt(Math.random() * 10000);
var attrs = {
@@ -53,9 +55,21 @@ function AnnotationGroup(id, anatomy, description, parent){
case "LINE":
annotation = new Line(attrs);
break;
+ case "TEXT":
+ // Add the image width, height and stroke color to the text annotation attributes
+ attrs["x"] = this.parent.imgWidth;
+ attrs["y"] = this.parent.imgHeight;
+ attrs["stroke"] = annotAttrs.stroke;
+ annotation = new Text(attrs);
+ break;
+ // Added to handle the import of text annotations
+ case "FOREIGNOBJECT":
+ annotation = new Text(attrs);
+ annotation.addTextBox(this.id, origSVGNode);
+ break;
case "ARROWLINE":
// Create the marker definition and append it to the annotation group
- group = document.getElementById(this.id);
+ var group = document.getElementById(this.id);
// Check if marker ID already exists which would be the case while importing saved annotation or create a new one
markerID = annotAttrs["data-markerid"] || "arrowmarker-" + annotAttrs.stroke.slice(1) + parseInt(Math.random() * 100000000);
if(group != null) {
@@ -83,6 +97,7 @@ function AnnotationGroup(id, anatomy, description, parent){
return annotation;
}
+
/**
* This function creates the marker definition needed for the arrowline annotation tool.
* The returned definition gets added to the annotation SVG.
@@ -237,6 +252,7 @@ function AnnotationGroup(id, anatomy, description, parent){
if (rst.length === 0) {
return "";
}
+
return "" + rst.join("") + "";
}
@@ -294,7 +310,6 @@ function AnnotationGroup(id, anatomy, description, parent){
return true;
}
})
-
if(!annotation){
return
}
@@ -302,6 +317,12 @@ function AnnotationGroup(id, anatomy, description, parent){
// remove annotation object from the collection
this.annotations.splice(index, 1);
+ // Text annotation requires different methods to remove annotation
+ if(annotation._tag == "text"){
+ annotation.removeText();
+ return;
+ }
+
// remove event handlers for the annotation
annotation.unbind();
@@ -312,6 +333,13 @@ function AnnotationGroup(id, anatomy, description, parent){
// Remove all annotations
this.removeAllAnnotations = function(){
this.annotations.forEach(function (annotation) {
+
+ // Text annotation requires different methods to remove annotation
+ if(annotation._tag == "text"){
+ annotation.removeText();
+ return;
+ }
+
// remove event handlers for the annotation
annotation.unbind();
@@ -420,6 +448,11 @@ function AnnotationGroup(id, anatomy, description, parent){
if(annotation._tag == "line" && annotation._attrs["data-subtype"] != null){
_self.changeArrowStroke(groupID, stroke)
}
+
+ // If the annotation is a textbox
+ if(annotation._tag == "text"){
+ annotation.updateTextColor(stroke);
+ }
// render the SVG after changing the color so that the new color is reflected in the viewer
annotation.renderSVG();
});
diff --git a/js/viewer/annotation/annotation-svg.js b/js/viewer/annotation/annotation-svg.js
index c40870ce..f8e85ab9 100644
--- a/js/viewer/annotation/annotation-svg.js
+++ b/js/viewer/annotation/annotation-svg.js
@@ -52,11 +52,11 @@ function AnnotationSVG(parent, id, imgWidth, imgHeight, scale, ignoreReferencePo
// Find corresponding group
group = this.groups[groupID];
-
+
annotation = group.addAnnotation(type, subtype, attrs);
annotation.renderSVG(group);
annotation.setupDrawingAttrs(attrs);
-
+ this.prevAnnotation = annotation;
this.dispatchEvent("onDrawingBegin", {
svgID : this.id,
groupID : groupID,
@@ -69,6 +69,7 @@ function AnnotationSVG(parent, id, imgWidth, imgHeight, scale, ignoreReferencePo
subtype: subtype,
attrs : attrs
})
+
}
}
@@ -202,6 +203,17 @@ function AnnotationSVG(parent, id, imgWidth, imgHeight, scale, ignoreReferencePo
}
}
+ /**
+ * Change the size of the text annotation input.
+ * @param {data} Object that contains the changed font-size
+ * @param {textAnnotation} The text annotation that is being edited
+ */
+ this.changeTextSize = function (data, textAnnotation) {
+ if(textAnnotation){
+ textAnnotation.changeFontSize(data.fontSize);
+ }
+ }
+
this.dispatchEvent = function(type, data){
switch(type){
// Change the selecting annotation
@@ -340,7 +352,6 @@ function AnnotationSVG(parent, id, imgWidth, imgHeight, scale, ignoreReferencePo
// Parsing child nodes in SVG
var svgElems = svgFile.childNodes || [];
-
for (var i = 0; i < svgElems.length; i++) {
if (!svgElems[i].getAttribute) { continue; }
@@ -363,6 +374,8 @@ function AnnotationSVG(parent, id, imgWidth, imgHeight, scale, ignoreReferencePo
case "rect":
case "line":
case "arrowline":
+ // To handle the parsing of the text annotation when imported from the svg file
+ case "foreignObject":
this.parseSVGNodes([node], styleSheet, node);
break;
// Added the defs case to handle the definition of markers. We just skip the definition as
@@ -478,11 +491,9 @@ function AnnotationSVG(parent, id, imgWidth, imgHeight, scale, ignoreReferencePo
parentNode = {};
}
-
for (var i = 0; i < nodes.length; i++) {
if (!nodes[i].getAttribute) { continue; }
-
var node = nodes[i];
var id = this.getNodeID(node, parentNode);
node.setAttribute("id", id);
@@ -501,7 +512,6 @@ function AnnotationSVG(parent, id, imgWidth, imgHeight, scale, ignoreReferencePo
node.setAttribute(attr, attrs[attr]);
}
// var attrs = styleSheet[className] ? JSON.parse(JSON.stringify(styleSheet[className])) : {};
-
switch (node.nodeName) {
case "g":
this.parseSVGNodes(node.childNodes, styleSheet, node);
@@ -511,10 +521,15 @@ function AnnotationSVG(parent, id, imgWidth, imgHeight, scale, ignoreReferencePo
case "polyline":
case "polygon":
case "rect":
+ case "foreignObject":
if(id !== "undefined"){
group = this.groups.hasOwnProperty(id) ? this.groups[id] : this.createAnnotationGroup(id, anatomy);
- annotation = group.addAnnotation(node.nodeName);
- annotation.setAttributesByJSON(this.getNodeAttributes(node));
+ // Create a new clone of the node and add it to the group as an annotation
+ // Cloning the node helps in keeping the original node intact and adding the text annotation
+ // as a foreignObject
+ var newNode = node.cloneNode(true);
+ annotation = group.addAnnotation(node.nodeName, null, null, newNode);
+ annotation.setAttributesByJSON(this.getNodeAttributes(newNode));
annotation.renderSVG(this);
}
break;
@@ -588,11 +603,11 @@ function AnnotationSVG(parent, id, imgWidth, imgHeight, scale, ignoreReferencePo
}
// Remove annotation from a group
- this.removeAnnotationByGraphID = function(groupID, graphID){
+ this.removeAnnotationByGraphID = function(groupID, graphID, userData){
+
if(this.groups.hasOwnProperty(groupID)){
var group = this.groups[groupID];
-
- group.removeAnnotationByID(graphID);
+ group.removeAnnotationByID(graphID, userData);
}
}
diff --git a/js/viewer/annotation/base.js b/js/viewer/annotation/base.js
index 9b6459a3..e15cd033 100644
--- a/js/viewer/annotation/base.js
+++ b/js/viewer/annotation/base.js
@@ -62,10 +62,35 @@ function Base(attrs){
this.exportToSVG = function(){
+ // For text annotation we just need to export the foreign object as it is
+ if(this._tag == "text"){
+ var foreignObj = this.getForeignObj();
+
+ // Check if foreign object exists
+ if(foreignObj == null){
+ return "";
+ }
+ // Check if the foreign object has a child node
+ if(foreignObj.childNodes.length == 0){
+ return "";
+ }
+ // Check if the child node is a p tag
+ if(foreignObj.childNodes[0].tagName != "P"){
+ return "";
+ }
+ // Check if the p tag has any text content
+ if(foreignObj.childNodes[0].innerHTML.trim() == ""){
+ return "";
+ }
+
+ return (foreignObj.outerHTML);
+ }
+
// Check to see if there are necessary dimensions needed to construct the component. This makes sure that no empty components are added to the final SVG output file.
if (!this.hasDimensions(this._attrs, this._tag)) {
return "";
}
+
var tag = this._tag;
var rst = "<" + tag + " ";
var attr;
@@ -266,4 +291,4 @@ Base.prototype.unbind = function(){
.on("mouseout", null)
.on('click', null);
}
-}
+}
\ No newline at end of file
diff --git a/js/viewer/annotation/text.js b/js/viewer/annotation/text.js
new file mode 100644
index 00000000..5d78818f
--- /dev/null
+++ b/js/viewer/annotation/text.js
@@ -0,0 +1,539 @@
+function Text(attrs) {
+
+ attrs = attrs ? attrs : {};
+ Base.call(this, attrs);
+
+ this._tag = "text";
+ this._attrs["x"] = (attrs["x"] / 2);
+ this._attrs["y"] = (attrs["y"] / 2);
+ this._attrs["stroke"] = attrs["stroke"] || "red";
+ this.annotationUtils = new AnnotationUtils();
+
+
+ /* Calculate the ratio for the mapping from image to screen size
+ * We use the OSD viewer container size and the actual image size to calculate the ratio
+ * This ratio will be used to obtain the font size of the text annotation that appears similar
+ * to the font size on user's screen.
+ * Formula: mappingRatio = max(imageWidth/containerWidth, imageHeight/containerHeight)
+ */
+ var viewerParent = this.parent.parent.parent;
+ var containerSize = viewerParent.osd.viewport.containerSize;
+ this._ratio = Math.max(attrs["x"]/containerSize.x, attrs["y"]/containerSize.y);
+
+ this._attrs["font-size"] = attrs["font-size"] || this._ratio * 14;
+ this._attrs["font-weight"] = attrs["font-weight"] || 400;
+ this.svg = null;
+
+ // Foreign object that contains the annotation input and transformed text p tag
+ this.foreignObj = null;
+}
+
+Text.prototype = Object.create(Base.prototype);
+Text.prototype.constructor = Text;
+
+Text.prototype.setForeignObj = function(obj) {
+ this.foreignObj = obj;
+}
+
+Text.prototype.getForeignObj = function() {
+ return this.foreignObj;
+}
+
+Text.prototype.renderSVG = function () {
+ // We do not need to render the text tag for the text annotation
+ // since we are using the foreign object to render the text input
+ // and the transformed text p tag
+ return;
+}
+
+
+/**
+ * This function is used to place the annotation at the desired position when user clicks on the OSD canvas
+ * @param {Array} point - The x and y coordinates of the point are used to position the annotation
+ */
+Text.prototype.positionAnnotation = function (point) {
+ var _self = this;
+ var updateAttrs = {
+ x: point.x,
+ y: point.y,
+ };
+
+ _self.setAttributesByJSON(updateAttrs);
+ _self.renderSVG();
+};
+
+
+
+/**
+ * Function to transform the