diff --git a/src/core/annotation.js b/src/core/annotation.js index 840c5a9a4..89e39ceeb 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -81,6 +81,12 @@ class AnnotationFactory { case 'Line': return new LineAnnotation(parameters); + case 'Square': + return new SquareAnnotation(parameters); + + case 'Circle': + return new CircleAnnotation(parameters); + case 'Highlight': return new HighlightAnnotation(parameters); @@ -886,6 +892,24 @@ class LineAnnotation extends Annotation { } } +class SquareAnnotation extends Annotation { + constructor(parameters) { + super(parameters); + + this.data.annotationType = AnnotationType.SQUARE; + this._preparePopup(parameters.dict); + } +} + +class CircleAnnotation extends Annotation { + constructor(parameters) { + super(parameters); + + this.data.annotationType = AnnotationType.CIRCLE; + this._preparePopup(parameters.dict); + } +} + class HighlightAnnotation extends Annotation { constructor(parameters) { super(parameters); diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index d218ee2dc..250c5b59c 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -14,8 +14,8 @@ */ import { - addLinkAttributes, CustomStyle, getDefaultSetting, getFilenameFromUrl, - LinkTarget + addLinkAttributes, CustomStyle, DOMSVGFactory, getDefaultSetting, + getFilenameFromUrl, LinkTarget } from './dom_utils'; import { AnnotationBorderStyleType, AnnotationType, stringToPDFString, Util, warn @@ -31,6 +31,7 @@ import { * @property {DownloadManager} downloadManager * @property {string} imageResourcesPath * @property {boolean} renderInteractiveForms + * @property {Object} svgFactory */ class AnnotationElementFactory { @@ -73,6 +74,12 @@ class AnnotationElementFactory { case AnnotationType.LINE: return new LineAnnotationElement(parameters); + case AnnotationType.SQUARE: + return new SquareAnnotationElement(parameters); + + case AnnotationType.CIRCLE: + return new CircleAnnotationElement(parameters); + case AnnotationType.HIGHLIGHT: return new HighlightAnnotationElement(parameters); @@ -105,6 +112,7 @@ class AnnotationElement { this.downloadManager = parameters.downloadManager; this.imageResourcesPath = parameters.imageResourcesPath; this.renderInteractiveForms = parameters.renderInteractiveForms; + this.svgFactory = parameters.svgFactory; if (isRenderable) { this.container = this._createContainer(ignoreBorder); @@ -590,7 +598,7 @@ class PopupAnnotationElement extends AnnotationElement { render() { // Do not render popup annotations for parent elements with these types as // they create the popups themselves (because of custom trigger divs). - const IGNORE_TYPES = ['Line']; + const IGNORE_TYPES = ['Line', 'Square', 'Circle']; this.container.className = 'popupAnnotation'; @@ -771,8 +779,6 @@ class LineAnnotationElement extends AnnotationElement { * @returns {HTMLSectionElement} */ render() { - const SVG_NS = 'http://www.w3.org/2000/svg'; - this.container.className = 'lineAnnotation'; // Create an invisible line with the same starting and ending coordinates @@ -781,30 +787,122 @@ class LineAnnotationElement extends AnnotationElement { let data = this.data; let width = data.rect[2] - data.rect[0]; let height = data.rect[3] - data.rect[1]; - - let svg = document.createElementNS(SVG_NS, 'svg:svg'); - svg.setAttributeNS(null, 'version', '1.1'); - svg.setAttributeNS(null, 'width', width + 'px'); - svg.setAttributeNS(null, 'height', height + 'px'); - svg.setAttributeNS(null, 'preserveAspectRatio', 'none'); - svg.setAttributeNS(null, 'viewBox', '0 0 ' + width + ' ' + height); + let svg = this.svgFactory.create(width, height); // PDF coordinates are calculated from a bottom left origin, so transform // the line coordinates to a top left origin for the SVG element. - let line = document.createElementNS(SVG_NS, 'svg:line'); - line.setAttributeNS(null, 'x1', data.rect[2] - data.lineCoordinates[0]); - line.setAttributeNS(null, 'y1', data.rect[3] - data.lineCoordinates[1]); - line.setAttributeNS(null, 'x2', data.rect[2] - data.lineCoordinates[2]); - line.setAttributeNS(null, 'y2', data.rect[3] - data.lineCoordinates[3]); - line.setAttributeNS(null, 'stroke-width', data.borderStyle.width); - line.setAttributeNS(null, 'stroke', 'transparent'); + let line = this.svgFactory.createElement('svg:line'); + line.setAttribute('x1', data.rect[2] - data.lineCoordinates[0]); + line.setAttribute('y1', data.rect[3] - data.lineCoordinates[1]); + line.setAttribute('x2', data.rect[2] - data.lineCoordinates[2]); + line.setAttribute('y2', data.rect[3] - data.lineCoordinates[3]); + line.setAttribute('stroke-width', data.borderStyle.width); + line.setAttribute('stroke', 'transparent'); svg.appendChild(line); this.container.append(svg); // Create the popup ourselves so that we can bind it to the line instead // of to the entire container (which is the default). - this._createPopup(this.container, line, this.data); + this._createPopup(this.container, line, data); + + return this.container; + } +} + +class SquareAnnotationElement extends AnnotationElement { + constructor(parameters) { + let isRenderable = !!(parameters.data.hasPopup || + parameters.data.title || parameters.data.contents); + super(parameters, isRenderable, /* ignoreBorder = */ true); + } + + /** + * Render the square annotation's HTML element in the empty container. + * + * @public + * @memberof SquareAnnotationElement + * @returns {HTMLSectionElement} + */ + render() { + this.container.className = 'squareAnnotation'; + + // Create an invisible square with the same rectangle that acts as the + // trigger for the popup. Only the square itself should trigger the + // popup, not the entire container. + let data = this.data; + let width = data.rect[2] - data.rect[0]; + let height = data.rect[3] - data.rect[1]; + let svg = this.svgFactory.create(width, height); + + // The browser draws half of the borders inside the square and half of + // the borders outside the square by default. This behavior cannot be + // changed programmatically, so correct for that here. + let borderWidth = data.borderStyle.width; + let square = this.svgFactory.createElement('svg:rect'); + square.setAttribute('x', borderWidth / 2); + square.setAttribute('y', borderWidth / 2); + square.setAttribute('width', width - borderWidth); + square.setAttribute('height', height - borderWidth); + square.setAttribute('stroke-width', borderWidth); + square.setAttribute('stroke', 'transparent'); + square.setAttribute('fill', 'none'); + + svg.appendChild(square); + this.container.append(svg); + + // Create the popup ourselves so that we can bind it to the square instead + // of to the entire container (which is the default). + this._createPopup(this.container, square, data); + + return this.container; + } +} + +class CircleAnnotationElement extends AnnotationElement { + constructor(parameters) { + let isRenderable = !!(parameters.data.hasPopup || + parameters.data.title || parameters.data.contents); + super(parameters, isRenderable, /* ignoreBorder = */ true); + } + + /** + * Render the circle annotation's HTML element in the empty container. + * + * @public + * @memberof CircleAnnotationElement + * @returns {HTMLSectionElement} + */ + render() { + this.container.className = 'circleAnnotation'; + + // Create an invisible circle with the same ellipse that acts as the + // trigger for the popup. Only the circle itself should trigger the + // popup, not the entire container. + let data = this.data; + let width = data.rect[2] - data.rect[0]; + let height = data.rect[3] - data.rect[1]; + let svg = this.svgFactory.create(width, height); + + // The browser draws half of the borders inside the circle and half of + // the borders outside the circle by default. This behavior cannot be + // changed programmatically, so correct for that here. + let borderWidth = data.borderStyle.width; + let circle = this.svgFactory.createElement('svg:ellipse'); + circle.setAttribute('cx', width / 2); + circle.setAttribute('cy', height / 2); + circle.setAttribute('rx', (width / 2) - (borderWidth / 2)); + circle.setAttribute('ry', (height / 2) - (borderWidth / 2)); + circle.setAttribute('stroke-width', borderWidth); + circle.setAttribute('stroke', 'transparent'); + circle.setAttribute('fill', 'none'); + + svg.appendChild(circle); + this.container.append(svg); + + // Create the popup ourselves so that we can bind it to the circle instead + // of to the entire container (which is the default). + this._createPopup(this.container, circle, data); return this.container; } @@ -995,6 +1093,7 @@ class AnnotationLayer { imageResourcesPath: parameters.imageResourcesPath || getDefaultSetting('imageResourcesPath'), renderInteractiveForms: parameters.renderInteractiveForms || false, + svgFactory: new DOMSVGFactory(), }); if (element.isRenderable) { parameters.div.appendChild(element.render()); diff --git a/src/display/dom_utils.js b/src/display/dom_utils.js index 6fca2ae2e..4d0116849 100644 --- a/src/display/dom_utils.js +++ b/src/display/dom_utils.js @@ -14,12 +14,13 @@ */ import { - CMapCompressionType, createValidAbsoluteUrl, deprecated, + assert, CMapCompressionType, createValidAbsoluteUrl, deprecated, removeNullCharacters, stringToBytes, warn } from '../shared/util'; import globalScope from '../shared/global_scope'; -var DEFAULT_LINK_REL = 'noopener noreferrer nofollow'; +const DEFAULT_LINK_REL = 'noopener noreferrer nofollow'; +const SVG_NS = 'http://www.w3.org/2000/svg'; class DOMCanvasFactory { create(width, height) { @@ -109,6 +110,27 @@ class DOMCMapReaderFactory { } } +class DOMSVGFactory { + create(width, height) { + assert(width > 0 && height > 0, 'Invalid SVG dimensions'); + + let svg = document.createElementNS(SVG_NS, 'svg:svg'); + svg.setAttribute('version', '1.1'); + svg.setAttribute('width', width + 'px'); + svg.setAttribute('height', height + 'px'); + svg.setAttribute('preserveAspectRatio', 'none'); + svg.setAttribute('viewBox', '0 0 ' + width + ' ' + height); + + return svg; + } + + createElement(type) { + assert(typeof type === 'string', 'Invalid SVG element type'); + + return document.createElementNS(SVG_NS, type); + } +} + /** * Optimised CSS custom property getter/setter. * @class @@ -330,4 +352,5 @@ export { DEFAULT_LINK_REL, DOMCanvasFactory, DOMCMapReaderFactory, + DOMSVGFactory, }; diff --git a/src/display/svg.js b/src/display/svg.js index 29ffbcaeb..5fb2167a9 100644 --- a/src/display/svg.js +++ b/src/display/svg.js @@ -18,6 +18,7 @@ import { createObjectURL, FONT_IDENTITY_MATRIX, IDENTITY_MATRIX, ImageKind, isNodeJS, isNum, OPS, Util, warn } from '../shared/util'; +import { DOMSVGFactory } from './dom_utils'; var SVGGraphics = function() { throw new Error('Not implemented: SVGGraphics'); @@ -403,6 +404,8 @@ SVGGraphics = (function SVGGraphicsClosure() { } function SVGGraphics(commonObjs, objs, forceDataSchema) { + this.svgFactory = new DOMSVGFactory(); + this.current = new SVGExtraState(); this.transformMatrix = IDENTITY_MATRIX; // Graphics state matrix this.transformStack = []; @@ -418,7 +421,6 @@ SVGGraphics = (function SVGGraphicsClosure() { this.forceDataSchema = !!forceDataSchema; } - var NS = 'http://www.w3.org/2000/svg'; var XML_NS = 'http://www.w3.org/XML/1998/namespace'; var XLINK_NS = 'http://www.w3.org/1999/xlink'; var LINE_CAP_STYLES = ['butt', 'round', 'square']; @@ -683,13 +685,13 @@ SVGGraphics = (function SVGGraphicsClosure() { this.current.y = this.current.lineY = 0; current.xcoords = []; - current.tspan = document.createElementNS(NS, 'svg:tspan'); + current.tspan = this.svgFactory.createElement('svg:tspan'); current.tspan.setAttributeNS(null, 'font-family', current.fontFamily); current.tspan.setAttributeNS(null, 'font-size', pf(current.fontSize) + 'px'); current.tspan.setAttributeNS(null, 'y', pf(-current.y)); - current.txtElement = document.createElementNS(NS, 'svg:text'); + current.txtElement = this.svgFactory.createElement('svg:text'); current.txtElement.appendChild(current.tspan); }, @@ -698,9 +700,9 @@ SVGGraphics = (function SVGGraphicsClosure() { this.current.y = this.current.lineY = 0; this.current.textMatrix = IDENTITY_MATRIX; this.current.lineMatrix = IDENTITY_MATRIX; - this.current.tspan = document.createElementNS(NS, 'svg:tspan'); - this.current.txtElement = document.createElementNS(NS, 'svg:text'); - this.current.txtgrp = document.createElementNS(NS, 'svg:g'); + this.current.tspan = this.svgFactory.createElement('svg:tspan'); + this.current.txtElement = this.svgFactory.createElement('svg:text'); + this.current.txtgrp = this.svgFactory.createElement('svg:g'); this.current.xcoords = []; }, @@ -710,7 +712,7 @@ SVGGraphics = (function SVGGraphicsClosure() { this.current.y = this.current.lineY += y; current.xcoords = []; - current.tspan = document.createElementNS(NS, 'svg:tspan'); + current.tspan = this.svgFactory.createElement('svg:tspan'); current.tspan.setAttributeNS(null, 'font-family', current.fontFamily); current.tspan.setAttributeNS(null, 'font-size', pf(current.fontSize) + 'px'); @@ -810,7 +812,7 @@ SVGGraphics = (function SVGGraphicsClosure() { addFontStyle: function SVGGraphics_addFontStyle(fontObj) { if (!this.cssStyle) { - this.cssStyle = document.createElementNS(NS, 'svg:style'); + this.cssStyle = this.svgFactory.createElement('svg:style'); this.cssStyle.setAttributeNS(null, 'type', 'text/css'); this.defs.appendChild(this.cssStyle); } @@ -852,7 +854,7 @@ SVGGraphics = (function SVGGraphicsClosure() { current.fontWeight = bold; current.fontStyle = italic; - current.tspan = document.createElementNS(NS, 'svg:tspan'); + current.tspan = this.svgFactory.createElement('svg:tspan'); current.tspan.setAttributeNS(null, 'y', pf(-current.y)); current.xcoords = []; }, @@ -885,7 +887,7 @@ SVGGraphics = (function SVGGraphicsClosure() { setFillRGBColor: function SVGGraphics_setFillRGBColor(r, g, b) { var color = Util.makeCssRgb(r, g, b); this.current.fillColor = color; - this.current.tspan = document.createElementNS(NS, 'svg:tspan'); + this.current.tspan = this.svgFactory.createElement('svg:tspan'); this.current.xcoords = []; }, setDash: function SVGGraphics_setDash(dashArray, dashPhase) { @@ -896,7 +898,7 @@ SVGGraphics = (function SVGGraphicsClosure() { constructPath: function SVGGraphics_constructPath(ops, args) { var current = this.current; var x = current.x, y = current.y; - current.path = document.createElementNS(NS, 'svg:path'); + current.path = this.svgFactory.createElement('svg:path'); var d = []; var opLength = ops.length; @@ -967,7 +969,7 @@ SVGGraphics = (function SVGGraphicsClosure() { // Add current path to clipping path var clipId = 'clippath' + clipCount; clipCount++; - var clipPath = document.createElementNS(NS, 'svg:clipPath'); + var clipPath = this.svgFactory.createElement('svg:clipPath'); clipPath.setAttributeNS(null, 'id', clipId); clipPath.setAttributeNS(null, 'transform', pm(this.transformMatrix)); var clipElement = current.element.cloneNode(); @@ -1110,7 +1112,7 @@ SVGGraphics = (function SVGGraphicsClosure() { paintSolidColorImageMask: function SVGGraphics_paintSolidColorImageMask() { var current = this.current; - var rect = document.createElementNS(NS, 'svg:rect'); + var rect = this.svgFactory.createElement('svg:rect'); rect.setAttributeNS(null, 'x', '0'); rect.setAttributeNS(null, 'y', '0'); rect.setAttributeNS(null, 'width', '1px'); @@ -1122,7 +1124,7 @@ SVGGraphics = (function SVGGraphicsClosure() { paintJpegXObject: function SVGGraphics_paintJpegXObject(objId, w, h) { var imgObj = this.objs.get(objId); - var imgEl = document.createElementNS(NS, 'svg:image'); + var imgEl = this.svgFactory.createElement('svg:image'); imgEl.setAttributeNS(XLINK_NS, 'xlink:href', imgObj.src); imgEl.setAttributeNS(null, 'width', pf(w)); imgEl.setAttributeNS(null, 'height', pf(h)); @@ -1149,14 +1151,14 @@ SVGGraphics = (function SVGGraphicsClosure() { var height = imgData.height; var imgSrc = convertImgDataToPng(imgData, this.forceDataSchema); - var cliprect = document.createElementNS(NS, 'svg:rect'); + var cliprect = this.svgFactory.createElement('svg:rect'); cliprect.setAttributeNS(null, 'x', '0'); cliprect.setAttributeNS(null, 'y', '0'); cliprect.setAttributeNS(null, 'width', pf(width)); cliprect.setAttributeNS(null, 'height', pf(height)); this.current.element = cliprect; this.clip('nonzero'); - var imgEl = document.createElementNS(NS, 'svg:image'); + var imgEl = this.svgFactory.createElement('svg:image'); imgEl.setAttributeNS(XLINK_NS, 'xlink:href', imgSrc); imgEl.setAttributeNS(null, 'x', '0'); imgEl.setAttributeNS(null, 'y', pf(-height)); @@ -1180,10 +1182,10 @@ SVGGraphics = (function SVGGraphicsClosure() { var fillColor = current.fillColor; current.maskId = 'mask' + maskCount++; - var mask = document.createElementNS(NS, 'svg:mask'); + var mask = this.svgFactory.createElement('svg:mask'); mask.setAttributeNS(null, 'id', current.maskId); - var rect = document.createElementNS(NS, 'svg:rect'); + var rect = this.svgFactory.createElement('svg:rect'); rect.setAttributeNS(null, 'x', '0'); rect.setAttributeNS(null, 'y', '0'); rect.setAttributeNS(null, 'width', pf(width)); @@ -1208,7 +1210,7 @@ SVGGraphics = (function SVGGraphicsClosure() { var width = bbox[2] - bbox[0]; var height = bbox[3] - bbox[1]; - var cliprect = document.createElementNS(NS, 'svg:rect'); + var cliprect = this.svgFactory.createElement('svg:rect'); cliprect.setAttributeNS(null, 'x', bbox[0]); cliprect.setAttributeNS(null, 'y', bbox[1]); cliprect.setAttributeNS(null, 'width', pf(width)); @@ -1225,24 +1227,17 @@ SVGGraphics = (function SVGGraphicsClosure() { /** * @private */ - _initialize: function SVGGraphics_initialize(viewport) { - // Create the SVG element. - var svg = document.createElementNS(NS, 'svg:svg'); - svg.setAttributeNS(null, 'version', '1.1'); - svg.setAttributeNS(null, 'width', viewport.width + 'px'); - svg.setAttributeNS(null, 'height', viewport.height + 'px'); - svg.setAttributeNS(null, 'preserveAspectRatio', 'none'); - svg.setAttributeNS(null, 'viewBox', '0 0 ' + viewport.width + - ' ' + viewport.height); + _initialize(viewport) { + let svg = this.svgFactory.create(viewport.width, viewport.height); // Create the definitions element. - var definitions = document.createElementNS(NS, 'svg:defs'); + let definitions = this.svgFactory.createElement('svg:defs'); svg.appendChild(definitions); this.defs = definitions; // Create the root group element, which acts a container for all other // groups and applies the viewport transform. - var rootGroup = document.createElementNS(NS, 'svg:g'); + let rootGroup = this.svgFactory.createElement('svg:g'); rootGroup.setAttributeNS(null, 'transform', pm(viewport.transform)); svg.appendChild(rootGroup); @@ -1259,7 +1254,7 @@ SVGGraphics = (function SVGGraphicsClosure() { */ _ensureClipGroup: function SVGGraphics_ensureClipGroup() { if (!this.current.clipGroup) { - var clipGroup = document.createElementNS(NS, 'svg:g'); + var clipGroup = this.svgFactory.createElement('svg:g'); clipGroup.setAttributeNS(null, 'clip-path', this.current.activeClipUrl); this.svg.appendChild(clipGroup); @@ -1273,7 +1268,7 @@ SVGGraphics = (function SVGGraphicsClosure() { */ _ensureTransformGroup: function SVGGraphics_ensureTransformGroup() { if (!this.tgrp) { - this.tgrp = document.createElementNS(NS, 'svg:g'); + this.tgrp = this.svgFactory.createElement('svg:g'); this.tgrp.setAttributeNS(null, 'transform', pm(this.transformMatrix)); if (this.current.activeClipUrl) { this._ensureClipGroup().appendChild(this.tgrp); diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 2fbd783bf..56fb6c713 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -284,6 +284,7 @@ !annotation-squiggly.pdf !annotation-highlight.pdf !annotation-line.pdf +!annotation-square-circle.pdf !annotation-fileattachment.pdf !annotation-text-widget.pdf !annotation-choice-widget.pdf diff --git a/test/pdfs/annotation-square-circle.pdf b/test/pdfs/annotation-square-circle.pdf new file mode 100755 index 000000000..4aff07fe0 Binary files /dev/null and b/test/pdfs/annotation-square-circle.pdf differ diff --git a/test/test_manifest.json b/test/test_manifest.json index 471c052f1..48863cd42 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -3560,6 +3560,13 @@ "type": "eq", "annotations": true }, + { "id": "annotation-square-circle", + "file": "pdfs/annotation-square-circle.pdf", + "md5": "cfd3c302f68d61e1d55ed9c7896046c3", + "rounds": 1, + "type": "eq", + "annotations": true + }, { "id": "annotation-fileattachment", "file": "pdfs/annotation-fileattachment.pdf", "md5": "d20ecee4b53c81b2dd44c8715a1b4a83", diff --git a/test/unit/dom_utils_spec.js b/test/unit/dom_utils_spec.js index 6457131c4..ab1f82d4f 100644 --- a/test/unit/dom_utils_spec.js +++ b/test/unit/dom_utils_spec.js @@ -14,11 +14,72 @@ */ import { - getFilenameFromUrl, isExternalLinkTargetSet, LinkTarget + DOMSVGFactory, getFilenameFromUrl, isExternalLinkTargetSet, LinkTarget } from '../../src/display/dom_utils'; +import { isNodeJS } from '../../src/shared/util'; import { PDFJS } from '../../src/display/global'; describe('dom_utils', function() { + describe('DOMSVGFactory', function() { + let svgFactory; + + beforeAll(function (done) { + svgFactory = new DOMSVGFactory(); + done(); + }); + + afterAll(function () { + svgFactory = null; + }); + + it('`create` should throw an error if the dimensions are invalid', + function() { + // Invalid width. + expect(function() { + return svgFactory.create(-1, 0); + }).toThrow(new Error('Invalid SVG dimensions')); + + // Invalid height. + expect(function() { + return svgFactory.create(0, -1); + }).toThrow(new Error('Invalid SVG dimensions')); + }); + + it('`create` should return an SVG element if the dimensions are valid', + function() { + if (isNodeJS()) { + pending('Document is not supported in Node.js.'); + } + + let svg = svgFactory.create(20, 40); + + expect(svg instanceof SVGSVGElement).toBe(true); + expect(svg.getAttribute('version')).toBe('1.1'); + expect(svg.getAttribute('width')).toBe('20px'); + expect(svg.getAttribute('height')).toBe('40px'); + expect(svg.getAttribute('preserveAspectRatio')).toBe('none'); + expect(svg.getAttribute('viewBox')).toBe('0 0 20 40'); + }); + + it('`createElement` should throw an error if the type is not a string', + function() { + expect(function() { + return svgFactory.createElement(true); + }).toThrow(new Error('Invalid SVG element type')); + }); + + it('`createElement` should return an SVG element if the type is valid', + function() { + if (isNodeJS()) { + pending('Document is not supported in Node.js.'); + } + + let svg = svgFactory.createElement('svg:rect'); + + expect(svg instanceof SVGRectElement).toBe(true); + }); + }); + describe('getFilenameFromUrl', function() { it('should get the filename from an absolute URL', function() { var url = 'http://server.org/filename.pdf'; diff --git a/web/annotation_layer_builder.css b/web/annotation_layer_builder.css index d04b20fc9..695e2282a 100644 --- a/web/annotation_layer_builder.css +++ b/web/annotation_layer_builder.css @@ -181,6 +181,8 @@ .annotationLayer .squigglyAnnotation, .annotationLayer .strikeoutAnnotation, .annotationLayer .lineAnnotation svg line, +.annotationLayer .squareAnnotation svg rect, +.annotationLayer .circleAnnotation svg ellipse, .annotationLayer .fileAttachmentAnnotation { cursor: pointer; }