/* eslint no-alert: 0 */ import * as cornerstoneTools from 'cornerstone-tools' const external = cornerstoneTools.external const EVENTS = cornerstoneTools.EVENTS const toolStyle = cornerstoneTools.toolStyle const textStyle = cornerstoneTools.textStyle const toolColors = cornerstoneTools.toolColors const pointInsideBoundingBox = cornerstoneTools.import('util/pointInsideBoundingBox') const lineSegDistance = cornerstoneTools.import('util/lineSegDistance') const triggerEvent = cornerstoneTools.import('util/triggerEvent') const moveNewHandle = cornerstoneTools.import('manipulators/moveNewHandle') const addToolState = cornerstoneTools.addToolState const getToolState = cornerstoneTools.getToolState const removeToolState = cornerstoneTools.removeToolState // Drawing const drawLinkedTextBox = cornerstoneTools.import('drawing/drawLinkedTextBox') const getNewContext = cornerstoneTools.import('drawing/getNewContext') const draw = cornerstoneTools.import('drawing/draw') // import textBoxWidth from './textBoxWidth' const setShadow = cornerstoneTools.import('drawing/setShadow') const drawArrow = cornerstoneTools.import('drawing/drawArrow') const drawHandles = cornerstoneTools.import('drawing/drawHandles') const { arrowAnnotateCursor } = cornerstoneTools.import('tools/cursors') const getModule = cornerstoneTools.getModule /** * @public * @class ArrowAnnotateTool * @memberof Tools.Annotation * @classdesc Create and position an arrow and label * @extends Tools.Base.BaseAnnotationTool */ export default class ArrowAnnotateTool extends cornerstoneTools.ArrowAnnotateTool { constructor(props = {}) { const defaultProps = { name: 'ArrowAnnotate', supportedInteractionTypes: ['Mouse', 'Touch'], configuration: { getTextCallback, changeTextCallback, drawHandles: true, drawHandlesOnHover: false, hideHandlesIfMoving: false, arrowFirst: true, renderDashed: false, allowEmptyLabel: false }, svgCursor: arrowAnnotateCursor } super(props, defaultProps) this.preventNewMeasurement = false } textBoxWidth(context, text, padding) { const font = textStyle.getFont() const origFont = context.font if (font && font !== origFont) { context.font = font } const width = context.measureText(text).width if (font && font !== origFont) { context.font = origFont } return width + 2 * padding } createNewMeasurement(evt) { // Create the measurement data for this tool with the end handle activated return { visible: true, active: true, color: undefined, text: '', handles: { start: { x: evt.detail.currentPoints.image.x, y: evt.detail.currentPoints.image.y, highlight: true, active: false }, end: { x: evt.detail.currentPoints.image.x, y: evt.detail.currentPoints.image.y, highlight: true, active: false }, textBox: { active: false, hasMoved: false, movesIndependently: false, drawnIndependently: true, allowedOutsideImage: true, hasBoundingBox: true } } } } pointNearTool(element, data, coords) { if (data.visible === false) { return false } return ( lineSegDistance(element, data.handles.start, data.handles.end, coords) < 25 ) } updateCachedStats() { // Implementing to satisfy BaseAnnotationTool } renderToolData(evt) { const { element, enabledElement } = evt.detail const { handleRadius, drawHandlesOnHover, hideHandlesIfMoving, renderDashed } = this.configuration // If we have no toolData for this element, return immediately as there is nothing to do const toolData = getToolState(element, this.name) if (!toolData) { return } // We have tool data for this element - iterate over each one and draw it const canvas = evt.detail.canvasContext.canvas const context = getNewContext(canvas) const lineWidth = toolStyle.getToolWidth() let lineDash if (renderDashed) { lineDash = getModule('globalConfiguration').configuration.lineDash } for (let i = 0; i < toolData.data.length; i++) { const data = toolData.data[i] if (data.visible === false) { continue } draw(context, context => { setShadow(context, this.configuration) const color = toolColors.getColorIfActive(data) // Draw the arrow const handleStartCanvas = external.cornerstone.pixelToCanvas( element, data.handles.start ) const handleEndCanvas = external.cornerstone.pixelToCanvas( element, data.handles.end ) // Config.arrowFirst = false; if (this.configuration.arrowFirst) { drawArrow( context, handleEndCanvas, handleStartCanvas, color, lineWidth, lineDash ) } else { drawArrow( context, handleStartCanvas, handleEndCanvas, color, lineWidth, lineDash ) } const handleOptions = { color, handleRadius, drawHandlesIfActive: drawHandlesOnHover, hideHandlesIfMoving } if (this.configuration.drawHandles) { drawHandles(context, evt.detail, data.handles, handleOptions) } const text = [] if (data.hasOwnProperty('remark')) { text.push(data.remark) } text.push(textBoxText(data)) // Draw the text if (text && text !== '') { // Calculate the text coordinates. const padding = 5 const textWidth = this.textBoxWidth(context, text, padding) const textHeight = textStyle.getFontSize() + 10 let distance = Math.max(textWidth, textHeight) / 2 + 5 if (handleEndCanvas.x < handleStartCanvas.x) { distance = -distance } if (!data.handles.textBox.hasMoved) { let textCoords if (this.configuration.arrowFirst) { textCoords = { x: handleEndCanvas.x - textWidth / 2 + distance, y: handleEndCanvas.y - textHeight / 2 } } else { // If the arrow is at the End position, the text should // Be placed near the Start position textCoords = { x: handleStartCanvas.x - textWidth / 2 - distance, y: handleStartCanvas.y - textHeight / 2 } } const transform = external.cornerstone.internal.getTransform( enabledElement ) transform.invert() const coords = transform.transformPoint(textCoords.x, textCoords.y) data.handles.textBox.x = coords.x data.handles.textBox.y = coords.y } drawLinkedTextBox( context, element, data.handles.textBox, text, data.handles, textBoxAnchorPoints, color, lineWidth, 0, false ) } }) } function textBoxText(data) { return data.text } function textBoxAnchorPoints(handles) { const midpoint = { x: (handles.start.x + handles.end.x) / 2, y: (handles.start.y + handles.end.y) / 2 } return [handles.start, midpoint, handles.end] } } addNewMeasurement(evt, interactionType) { const element = evt.detail.element const measurementData = this.createNewMeasurement(evt) const { allowEmptyLabel } = this.configuration // Associate this data with this imageId so we can render it and manipulate it addToolState(element, this.name, measurementData) external.cornerstone.updateImage(element) moveNewHandle( evt.detail, this.name, measurementData, measurementData.handles.end, this.options, interactionType, success => { if (success) { if (!allowEmptyLabel && measurementData.text === undefined) { this.configuration.getTextCallback(text => { if (text || allowEmptyLabel) { measurementData.text = text measurementData.active = false const modifiedEventData = { toolName: this.name, toolType: this.name, // Deprecation notice: toolType will be replaced by toolName element, measurementData } external.cornerstone.updateImage(element) triggerEvent( element, EVENTS.MEASUREMENT_COMPLETED, modifiedEventData ) } else { removeToolState(element, this.name, measurementData) } }, evt.detail) } } else { removeToolState(element, this.name, measurementData) } external.cornerstone.updateImage(element) } ) } doubleClickCallback(evt) { return this._updateTextForNearbyAnnotation(evt) } touchPressCallback(evt) { return this._updateTextForNearbyAnnotation(evt) } _updateTextForNearbyAnnotation(evt) { const element = evt.detail.element const coords = evt.detail.currentPoints.canvas const toolState = getToolState(element, this.name) if (!toolState) { return false } for (let i = 0; i < toolState.data.length; i++) { const data = toolState.data[i] if ( this.pointNearTool(element, data, coords) || pointInsideBoundingBox(data.handles.textBox, coords) ) { data.active = true external.cornerstone.updateImage(element) // Allow relabelling via a callback this.configuration.changeTextCallback( data, evt.detail, this._doneChangingTextCallback.bind(this, element, data) ) evt.stopImmediatePropagation() evt.preventDefault() evt.stopPropagation() return true } } } _doneChangingTextCallback(element, measurementData, updatedText, deleteTool) { if (deleteTool === true) { removeToolState(element, this.name, measurementData) } else { measurementData.text = updatedText } measurementData.active = false external.cornerstone.updateImage(element) triggerEvent(element, EVENTS.MEASUREMENT_MODIFIED, { toolName: this.name, toolType: this.name, // Deprecation notice: toolType will be replaced by toolName element, measurementData }) } } function getTextCallback(doneChangingTextCallback) { doneChangingTextCallback(prompt('Enter your annotation:')) } function changeTextCallback(data, eventData, doneChangingTextCallback) { doneChangingTextCallback(prompt('Change your annotation:')) }