diff --git a/__tests__/fixtures/version-2/annotationMiradorDual.json b/__tests__/fixtures/version-2/annotationMiradorDual.json
new file mode 100644
index 0000000000000000000000000000000000000000..4eced583f30a3c00262c36e0e58f6d1a8f139a40
--- /dev/null
+++ b/__tests__/fixtures/version-2/annotationMiradorDual.json
@@ -0,0 +1,34 @@
+{
+  "@context": "http://iiif.io/api/presentation/2/context.json",
+  "@type": "oa:Annotation",
+  "motivation": [
+    "oa:commenting"
+  ],
+  "resource": [
+    {
+      "@type": "dctypes:Text",
+      "format": "text/html",
+      "chars": "<p>something</p>"
+    }
+  ],
+  "on": {
+    "@type": "oa:SpecificResource",
+    "full": "https://oculus-dev.harvardx.harvard.edu/manifests/huam:320567/canvas/canvas-10466656.json",
+    "selector": {
+      "@type": "oa:Choice",
+      "default": {
+        "@type": "oa:FragmentSelector",
+        "value": "xywh=1000,219,198,148"
+      },
+      "item": {
+        "@type": "oa:SvgSelector",
+        "value": "<svg xmlns='http://www.w3.org/2000/svg'><path xmlns=\"http://www.w3.org/2000/svg\" d=\"M1000.24213,219.15375l98.78935,0l0,0l98.78935,0l0,74.09201l0,74.09201l-98.78935,0l-98.78935,0l0,-74.09201z\" data-paper-data=\"{&quot;defaultStrokeValue&quot;:1,&quot;editStrokeValue&quot;:5,&quot;currentStrokeValue&quot;:5,&quot;rotation&quot;:0,&quot;annotation&quot;:null,&quot;editable&quot;:true}\" id=\"rectangle_7e2b56fa-b18b-4d09-a575-0bb19f560b56\" fill-opacity=\"0\" fill=\"#00bfff\" fill-rule=\"nonzero\" stroke=\"#00bfff\" stroke-width=\"30.87167\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\" stroke-dasharray=\"\" stroke-dashoffset=\"0\" font-family=\"sans-serif\" font-weight=\"normal\" font-size=\"12\" text-anchor=\"start\" style=\"mix-blend-mode: normal\"/></svg>"
+      }
+    },
+    "within": {
+      "@id": "https://oculus-dev.harvardx.harvard.edu/manifests/huam:320567",
+      "@type": "sc:Manifest"
+    }
+  },
+  "@id": "d2eda2e2-951a-4f88-b4ec-03a7b25a5d07"
+}
diff --git a/__tests__/integration/mirador/svg_annos.html b/__tests__/integration/mirador/svg_annos.html
new file mode 100644
index 0000000000000000000000000000000000000000..201cb24afa65e0d1f5d63e0a8ea6bae70291d51f
--- /dev/null
+++ b/__tests__/integration/mirador/svg_annos.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+    <meta name="theme-color" content="#000000">
+    <title>Mirador</title>
+    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
+  </head>
+  <body>
+    <div id="mirador" style="position: absolute; top: 0; bottom: 0; left: 0; right: 0;"></div>
+    <script>document.write("<script type='text/javascript' src='../../../dist/mirador.min.js?v=" + Date.now() + "'><\/script>");</script>
+    <script type="text/javascript">
+     var miradorInstance = Mirador.viewer({
+       id: 'mirador',
+       windows: [
+         {
+           manifestId: 'https://api.myjson.com/bins/ahd5y',
+         },
+         {
+           manifestId: 'https://iiif.bodleian.ox.ac.uk/iiif/manifest/748a9d50-5a3a-440e-ab9d-567dd68b6abb.json',
+           canvasId: 'https://iiif.bodleian.ox.ac.uk/iiif/canvas/4a4d7347-0c32-4e3d-b517-5042eba06c25.json',
+         }
+       ],
+       window: {
+         sideBarOpenByDefault: true,
+         defaultSideBarPanel: 'annotations'
+       },
+     });
+    </script>
+  </body>
+</html>
diff --git a/__tests__/src/components/OpenSeadragonViewer.test.js b/__tests__/src/components/OpenSeadragonViewer.test.js
index b63427b1723e0c4f96f2aed31dedecc63a285cc6..4822d4178d4d7b25e4c7aa296cd83788ec3b3e00 100644
--- a/__tests__/src/components/OpenSeadragonViewer.test.js
+++ b/__tests__/src/components/OpenSeadragonViewer.test.js
@@ -355,7 +355,7 @@ describe('OpenSeadragonViewer', () => {
       wrapper.instance().annotationsToContext(annotations);
       const context = wrapper.instance().osdCanvasOverlay.context2d;
       expect(context.strokeStyle).toEqual('yellow');
-      expect(context.lineWidth).toEqual(4);
+      expect(context.lineWidth).toEqual(20);
       expect(strokeRect).toHaveBeenCalledWith(10, 10, 100, 200);
     });
   });
diff --git a/__tests__/src/lib/AnnotationResource.test.js b/__tests__/src/lib/AnnotationResource.test.js
index f9df4d7ab066810b0a4b64dd0fff27071c823b81..08dc0a0d79b9eced4cc3fc0ddb3b7493c896e3d6 100644
--- a/__tests__/src/lib/AnnotationResource.test.js
+++ b/__tests__/src/lib/AnnotationResource.test.js
@@ -122,4 +122,20 @@ describe('AnnotationResource', () => {
         .fragmentSelector).toEqual([10, 10, 100, 200]);
     });
   });
+  describe('svgSelector', () => {
+    it('simple string', () => {
+      expect(new AnnotationResource({ on: 'www.example.com/#xywh=10,10,100,200' })
+        .svgSelector).toEqual(null);
+    });
+
+    it('array of selectors', () => {
+      expect(new AnnotationResource({ on: [{ selector: { item: { '@type': 'oa:SvgSelector' } } }] })
+        .svgSelector).toEqual({ '@type': 'oa:SvgSelector' });
+    });
+
+    it('without specified type', () => {
+      expect(new AnnotationResource({ on: [{ selector: { item: {} } }] })
+        .svgSelector).toEqual(null);
+    });
+  });
 });
diff --git a/__tests__/src/lib/CanvasAnnotationDisplay.test.js b/__tests__/src/lib/CanvasAnnotationDisplay.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..e122100e8aad9a5584cbf160e949ca37e568329a
--- /dev/null
+++ b/__tests__/src/lib/CanvasAnnotationDisplay.test.js
@@ -0,0 +1,83 @@
+import CanvasAnnotationDisplay from '../../../src/lib/CanvasAnnotationDisplay';
+import AnnotationResource from '../../../src/lib/AnnotationResource';
+import dualStrategyAnno from '../../fixtures/version-2/annotationMiradorDual.json';
+
+/** */
+function createSubject(args) {
+  return new CanvasAnnotationDisplay({
+    color: 'blue',
+    offset: {
+      x: -100,
+      y: 0,
+    },
+    zoom: 0.0005,
+    ...args,
+  });
+}
+
+describe('CanvasAnnotationDisplay', () => {
+  describe('toContext', () => {
+    it('selects svgSelector if present in a dual anno', () => {
+      const context = {
+        stroke: jest.fn(),
+      };
+      const subject = createSubject({
+        resource: new AnnotationResource(dualStrategyAnno),
+      });
+      subject.svgContext = jest.fn();
+      subject.fragmentContext = jest.fn();
+      subject.toContext(context);
+      expect(subject.svgContext).toHaveBeenCalled();
+      expect(subject.fragmentContext).not.toHaveBeenCalled();
+    });
+    it('selects fragmentSelector if no svg present', () => {
+      const context = {
+        stroke: jest.fn(),
+      };
+      const subject = createSubject({
+        resource: new AnnotationResource({ on: 'www.example.com/#xywh=10,10,100,200' }),
+      });
+      subject.svgContext = jest.fn();
+      subject.fragmentContext = jest.fn();
+      subject.toContext(context);
+      expect(subject.svgContext).not.toHaveBeenCalled();
+      expect(subject.fragmentContext).toHaveBeenCalled();
+    });
+  });
+  describe('svgString', () => {
+    it('selects the svg selector string value', () => {
+      const subject = createSubject({
+        resource: new AnnotationResource(dualStrategyAnno),
+      });
+      expect(subject.svgString).toMatch(/<svg/);
+    });
+  });
+  describe('svgContext', () => {
+    it('draws the paths with selected arguments', () => {
+      const context = {
+        stroke: jest.fn(),
+      };
+      const subject = createSubject({
+        resource: new AnnotationResource(dualStrategyAnno),
+      });
+      subject.svgContext(context);
+      expect(context.stroke).toHaveBeenCalledWith({});
+      expect(context.strokeStyle).toEqual('blue');
+      expect(context.lineWidth).toEqual(20);
+    });
+  });
+  describe('fragmentContext', () => {
+    it('draws the fragment with selected arguments', () => {
+      const context = {
+        strokeRect: jest.fn(),
+      };
+      const subject = createSubject({
+        resource: new AnnotationResource({ on: 'www.example.com/#xywh=10,10,100,200' }),
+      });
+      subject.fragmentContext(context);
+      expect(context.strokeRect).toHaveBeenCalledWith(-90, 10, 100, 200);
+      expect(context.strokeStyle).toEqual('blue');
+      expect(context.lineWidth).toEqual(20);
+    });
+  });
+});
diff --git a/setupJest.js b/setupJest.js
index 6ccce82a5733d3d111de160410d7c706a129da0a..b88cdf880517460401c82c05aff889f86af7d3cf 100644
--- a/setupJest.js
+++ b/setupJest.js
@@ -32,6 +32,11 @@ class IntersectionObserverPolyfill {
 
 global.IntersectionObserver = IntersectionObserverPolyfill;
 
+/** */
+function Path2D() {
+}
+
+global.Path2D = Path2D;
 /**
  * copy object property descriptors from `src` to `target`
  * @param {*} src
diff --git a/src/components/OpenSeadragonViewer.js b/src/components/OpenSeadragonViewer.js
index 487f5ca0fdd2f477b0064e3a78bc901264cec87f..94538fe2ce6303ff263ba71e1aba948cd1bd3a3a 100644
--- a/src/components/OpenSeadragonViewer.js
+++ b/src/components/OpenSeadragonViewer.js
@@ -6,6 +6,7 @@ import classNames from 'classnames';
 import ns from '../config/css-ns';
 import OpenSeadragonCanvasOverlay from '../lib/OpenSeadragonCanvasOverlay';
 import CanvasWorld from '../lib/CanvasWorld';
+import CanvasAnnotationDisplay from '../lib/CanvasAnnotationDisplay';
 
 /**
  * Represents a OpenSeadragonViewer in the mirador workspace. Responsible for mounting
@@ -183,16 +184,14 @@ export class OpenSeadragonViewer extends Component {
     const { canvasWorld } = this.props;
     const context = this.osdCanvasOverlay.context2d;
     const zoom = this.viewer.viewport.getZoom(true);
-    const width = canvasWorld.worldBounds()[2];
     annotations.forEach((annotation) => {
       annotation.resources.forEach((resource) => {
         if (!canvasWorld.canvasIds.includes(resource.targetId)) return;
         const offset = canvasWorld.offsetByCanvas(resource.targetId);
-        const fragment = resource.fragmentSelector;
-        fragment[0] += offset.x;
-        context.strokeStyle = color;
-        context.lineWidth = Math.ceil(10 / (zoom * width));
-        context.strokeRect(...fragment);
+        const canvasAnnotationDisplay = new CanvasAnnotationDisplay({
+          color, offset, resource, zoom,
+        });
+        canvasAnnotationDisplay.toContext(context);
       });
     });
   }
diff --git a/src/lib/AnnotationResource.js b/src/lib/AnnotationResource.js
index 3ed87bc1ef0313ef21841fdfc716ebbb70195b7b..d2bb62a644d26b33f34d2d3b0e4d9ab6f4354d33 100644
--- a/src/lib/AnnotationResource.js
+++ b/src/lib/AnnotationResource.js
@@ -68,6 +68,23 @@ export default class AnnotationResource {
     }
   }
 
+  /** */
+  get svgSelector() {
+    const on = this.on[0];
+
+    switch (typeof on) {
+      case 'string':
+        return null;
+      case 'object':
+        if (on.selector && on.selector.item && on.selector.item['@type'] === 'oa:SvgSelector') {
+          return on.selector.item;
+        }
+        return null;
+      default:
+        return null;
+    }
+  }
+
   /** */
   get fragmentSelector() {
     const { selector } = this;
diff --git a/src/lib/CanvasAnnotationDisplay.js b/src/lib/CanvasAnnotationDisplay.js
new file mode 100644
index 0000000000000000000000000000000000000000..2d5fa7995c010c433c13a13ff297a01b528d1f9b
--- /dev/null
+++ b/src/lib/CanvasAnnotationDisplay.js
@@ -0,0 +1,72 @@
+/**
+ * CanvasAnnotationDisplay - class used to display a SVG and fragment based
+ * annotations.
+ */
+export default class CanvasAnnotationDisplay {
+  /** */
+  constructor({
+    resource, color, zoom, offset,
+  }) {
+    this.resource = resource;
+    this.color = color;
+    this.zoom = zoom;
+    this.offset = offset;
+  }
+
+  /** */
+  toContext(context) {
+    if (this.resource.svgSelector) {
+      this.svgContext(context);
+    } else {
+      this.fragmentContext(context);
+    }
+  }
+
+  /** */
+  get svgString() {
+    return this.resource.svgSelector.value;
+  }
+
+  /** */
+  svgContext(context) {
+    [...this.svgPaths].forEach((element) => {
+      /**
+       *  Note: Path2D is not supported in IE11.
+       *  TODO: Support multi canvas offset
+       *  One example: https://developer.mozilla.org/en-US/docs/Web/API/Path2D/addPath
+       */
+      const p = new Path2D(element.attributes.d.nodeValue);
+      /**
+       * Note: we could do something to return the svg styling attributes as
+       * some have encoded information in these values. However, how should we
+       * handle highlighting and other complications?
+       *  context.strokeStyle = element.attributes.stroke.nodeValue;
+       *  context.lineWidth = element.attributes['stroke-width'].nodeValue;
+       */
+      context.strokeStyle = this.color; // eslint-disable-line no-param-reassign
+      context.lineWidth = this.lineWidth(); // eslint-disable-line no-param-reassign
+      context.stroke(p);
+    });
+  }
+
+  /** */
+  fragmentContext(context) {
+    const fragment = this.resource.fragmentSelector;
+    fragment[0] += this.offset.x;
+    context.strokeStyle = this.color; // eslint-disable-line no-param-reassign
+    context.lineWidth = this.lineWidth(); // eslint-disable-line no-param-reassign
+    context.strokeRect(...fragment);
+  }
+
+  /** */
+  lineWidth() {
+    return Math.ceil(1 / (this.zoom * 100));
+  }
+
+  /** */
+  get svgPaths() {
+    const parser = new DOMParser();
+    const xmlDoc = parser.parseFromString(this.svgString, 'text/xml');
+    return xmlDoc.getElementsByTagName('path');
+  }
+}