From 1d06e46a5d1ddae4da256da43c01ce72a150ab71 Mon Sep 17 00:00:00 2001
From: Jack Reed <phillipjreed@gmail.com>
Date: Wed, 22 Apr 2020 14:14:38 -0600
Subject: [PATCH] Support Annotations in bookview for right-to-left viewing
 fixes #2956

---
 __tests__/integration/mirador/svg_annos.html  |  2 +-
 .../src/lib/CanvasAnnotationDisplay.test.js   |  6 +++
 __tests__/src/lib/CanvasWorld.test.js         | 27 +++++++------
 src/components/OpenSeadragonViewer.js         |  4 +-
 src/lib/CanvasAnnotationDisplay.js            |  4 ++
 src/lib/CanvasWorld.js                        | 39 ++++++++++++-------
 6 files changed, 55 insertions(+), 27 deletions(-)

diff --git a/__tests__/integration/mirador/svg_annos.html b/__tests__/integration/mirador/svg_annos.html
index 201cb24af..71d87cfba 100644
--- a/__tests__/integration/mirador/svg_annos.html
+++ b/__tests__/integration/mirador/svg_annos.html
@@ -19,7 +19,7 @@
          },
          {
            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',
+           canvasIndex: 6,
          }
        ],
        window: {
diff --git a/__tests__/src/lib/CanvasAnnotationDisplay.test.js b/__tests__/src/lib/CanvasAnnotationDisplay.test.js
index e122100e8..16914ca66 100644
--- a/__tests__/src/lib/CanvasAnnotationDisplay.test.js
+++ b/__tests__/src/lib/CanvasAnnotationDisplay.test.js
@@ -55,13 +55,19 @@ describe('CanvasAnnotationDisplay', () => {
   describe('svgContext', () => {
     it('draws the paths with selected arguments', () => {
       const context = {
+        restore: jest.fn(),
+        save: jest.fn(),
         stroke: jest.fn(),
+        translate: jest.fn(),
       };
       const subject = createSubject({
         resource: new AnnotationResource(dualStrategyAnno),
       });
       subject.svgContext(context);
       expect(context.stroke).toHaveBeenCalledWith({});
+      expect(context.save).toHaveBeenCalledWith();
+      expect(context.restore).toHaveBeenCalledWith();
+      expect(context.translate).toHaveBeenCalledWith(-100, 0);
       expect(context.strokeStyle).toEqual('blue');
       expect(context.lineWidth).toEqual(20);
     });
diff --git a/__tests__/src/lib/CanvasWorld.test.js b/__tests__/src/lib/CanvasWorld.test.js
index 263d66fb6..10a2719bc 100644
--- a/__tests__/src/lib/CanvasWorld.test.js
+++ b/__tests__/src/lib/CanvasWorld.test.js
@@ -17,15 +17,27 @@ describe('CanvasWorld', () => {
       expect(new CanvasWorld(canvasSubset).worldBounds()).toEqual([0, 0, 9153, 4288]);
     });
   });
+  describe('contentResourceToWorldCoordinates', () => {
+    it('converts canvas coordinates to world offset by location', () => {
+      expect(new CanvasWorld([canvases[1]]).contentResourceToWorldCoordinates({ id: 'https://stacks.stanford.edu/image/iiif/fr426cg9537%2FSC1094_s3_b14_f17_Cats_1976_0005/full/full/0/default.jpg' }))
+        .toEqual([0, 0, 6501, 4421]);
+      expect(new CanvasWorld(canvasSubset).contentResourceToWorldCoordinates({ id: 'https://stacks.stanford.edu/image/iiif/rz176rt6531%2FPC0170_s3_Tree_Calendar_20081101_152516_0410/full/full/0/default.jpg' }))
+        .toEqual([6305, 0, 2848, 4288]);
+    });
+    it('supports RTL orientations', () => {
+      expect(new CanvasWorld(canvasSubset, null, 'right-to-left').contentResourceToWorldCoordinates({ id: 'https://stacks.stanford.edu/image/iiif/rz176rt6531%2FPC0170_s3_Tree_Calendar_20081101_152516_0410/full/full/0/default.jpg' }))
+        .toEqual([0, 0, 2848, 4288]);
+    });
+  });
   describe('canvasToWorldCoordinates', () => {
     it('converts canvas coordinates to world offset by location', () => {
-      expect(new CanvasWorld([canvases[1]]).canvasToWorldCoordinates({ id: 'https://stacks.stanford.edu/image/iiif/fr426cg9537%2FSC1094_s3_b14_f17_Cats_1976_0005/full/full/0/default.jpg' }))
+      expect(new CanvasWorld([canvases[1]]).canvasToWorldCoordinates('https://purl.stanford.edu/fr426cg9537/iiif/canvas/fr426cg9537_1'))
         .toEqual([0, 0, 6501, 4421]);
-      expect(new CanvasWorld(canvasSubset).canvasToWorldCoordinates({ id: 'https://stacks.stanford.edu/image/iiif/rz176rt6531%2FPC0170_s3_Tree_Calendar_20081101_152516_0410/full/full/0/default.jpg' }))
+      expect(new CanvasWorld(canvasSubset).canvasToWorldCoordinates('https://purl.stanford.edu/rz176rt6531/iiif/canvas/rz176rt6531_1'))
         .toEqual([6305, 0, 2848, 4288]);
     });
     it('supports RTL orientations', () => {
-      expect(new CanvasWorld(canvasSubset, null, 'right-to-left').canvasToWorldCoordinates({ id: 'https://stacks.stanford.edu/image/iiif/rz176rt6531%2FPC0170_s3_Tree_Calendar_20081101_152516_0410/full/full/0/default.jpg' }))
+      expect(new CanvasWorld(canvasSubset, null, 'right-to-left').canvasToWorldCoordinates('https://purl.stanford.edu/rz176rt6531/iiif/canvas/rz176rt6531_1'))
         .toEqual([0, 0, 2848, 4288]);
     });
   });
@@ -36,14 +48,7 @@ describe('CanvasWorld', () => {
       ).toEqual({ x: 0, y: 0 });
       expect(
         new CanvasWorld(canvasSubset).offsetByCanvas('https://purl.stanford.edu/rz176rt6531/iiif/canvas/rz176rt6531_1'),
-      ).toEqual({ x: 6501, y: 0 });
-    });
-  });
-  describe('indexOfTarget', () => {
-    it('returns the index of a target in canvases', () => {
-      expect(
-        new CanvasWorld(canvasSubset).indexOfTarget('https://purl.stanford.edu/rz176rt6531/iiif/canvas/rz176rt6531_1'),
-      ).toEqual(1);
+      ).toEqual({ x: 6305, y: 0 });
     });
   });
 
diff --git a/src/components/OpenSeadragonViewer.js b/src/components/OpenSeadragonViewer.js
index 08e522071..3ab53dbbf 100644
--- a/src/components/OpenSeadragonViewer.js
+++ b/src/components/OpenSeadragonViewer.js
@@ -226,7 +226,7 @@ export class OpenSeadragonViewer extends Component {
       this.viewer.addSimpleImage({
         error: event => reject(event),
         fitBounds: new OpenSeadragon.Rect(
-          ...canvasWorld.canvasToWorldCoordinates(contentResource),
+          ...canvasWorld.contentResourceToWorldCoordinates(contentResource),
         ),
         index: canvasWorld.layerIndexOfImageResource(contentResource),
         opacity: canvasWorld.layerOpacityOfImageResource(contentResource),
@@ -253,7 +253,7 @@ export class OpenSeadragonViewer extends Component {
       this.viewer.addTiledImage({
         error: event => reject(event),
         fitBounds: new OpenSeadragon.Rect(
-          ...canvasWorld.canvasToWorldCoordinates(contentResource),
+          ...canvasWorld.contentResourceToWorldCoordinates(contentResource),
         ),
         index: canvasWorld.layerIndexOfImageResource(contentResource),
         opacity: canvasWorld.layerOpacityOfImageResource(contentResource),
diff --git a/src/lib/CanvasAnnotationDisplay.js b/src/lib/CanvasAnnotationDisplay.js
index 72ccbcd5a..454c91cb5 100644
--- a/src/lib/CanvasAnnotationDisplay.js
+++ b/src/lib/CanvasAnnotationDisplay.js
@@ -36,6 +36,8 @@ export default class CanvasAnnotationDisplay {
        *  TODO: Support multi canvas offset
        *  One example: https://developer.mozilla.org/en-US/docs/Web/API/Path2D/addPath
        */
+      context.save();
+      context.translate(this.offset.x, this.offset.y);
       const p = new Path2D(element.attributes.d.nodeValue);
       /**
        * Note: we could do something to return the svg styling attributes as
@@ -47,6 +49,7 @@ export default class CanvasAnnotationDisplay {
       context.strokeStyle = this.color; // eslint-disable-line no-param-reassign
       context.lineWidth = this.lineWidth(); // eslint-disable-line no-param-reassign
       context.stroke(p);
+      context.restore();
     });
   }
 
@@ -54,6 +57,7 @@ export default class CanvasAnnotationDisplay {
   fragmentContext(context) {
     const fragment = this.resource.fragmentSelector;
     fragment[0] += this.offset.x;
+    fragment[1] += this.offset.y;
     context.strokeStyle = this.color; // eslint-disable-line no-param-reassign
     context.lineWidth = this.lineWidth(); // eslint-disable-line no-param-reassign
     context.strokeRect(...fragment);
diff --git a/src/lib/CanvasWorld.js b/src/lib/CanvasWorld.js
index ab44caade..845c79296 100644
--- a/src/lib/CanvasWorld.js
+++ b/src/lib/CanvasWorld.js
@@ -21,10 +21,10 @@ export default class CanvasWorld {
   }
 
   /**
-   * canvasToWorldCoordinates - calculates the canvas coordinates respective to
-   * the world.
+   * contentResourceToWorldCoordinates - calculates the contentResource coordinates
+   * respective to the world.
    */
-  canvasToWorldCoordinates(contentResource) {
+  contentResourceToWorldCoordinates(contentResource) {
     const wholeBounds = this.worldBounds();
     const manifestoCanvasIndex = this.canvases.findIndex(c => (
       c.imageResources.find(r => r.id === contentResource.id)
@@ -43,6 +43,24 @@ export default class CanvasWorld {
     ];
   }
 
+  /** */
+  canvasToWorldCoordinates(canvasId) {
+    const wholeBounds = this.worldBounds();
+    const manifestoCanvasIndex = this.canvases.findIndex(c => (c.id === canvasId));
+    const { aspectRatio } = this.canvases[manifestoCanvasIndex];
+    const scaledWidth = Math.floor(wholeBounds[3] * aspectRatio);
+    let x = 0;
+    if (manifestoCanvasIndex === this.secondCanvasIndex) {
+      x = wholeBounds[2] - scaledWidth;
+    }
+    return [
+      x,
+      0,
+      scaledWidth,
+      wholeBounds[3],
+    ];
+  }
+
   /**
    * secondCanvasIndex - index of the second canvas used for determining which
    * is first
@@ -51,10 +69,6 @@ export default class CanvasWorld {
     return this.viewingDirection === 'right-to-left' ? 0 : 1;
   }
 
-  /** */
-  indexOfTarget(canvasTarget) {
-    return this.canvases.map(canvas => canvas.id).indexOf(canvasTarget);
-  }
 
   /** Get the IIIF content resource for an image */
   contentResource(infoResponseId) {
@@ -115,12 +129,11 @@ export default class CanvasWorld {
    * assumes a horrizontal only layout.
    */
   offsetByCanvas(canvasTarget) {
-    const offset = { x: 0, y: 0 };
-    let i;
-    for (i = 0; i < this.indexOfTarget(canvasTarget); i += 1) {
-      offset.x += this.canvases[i].getWidth();
-    }
-    return offset;
+    const coordinates = this.canvasToWorldCoordinates(canvasTarget);
+    return {
+      x: coordinates[0],
+      y: coordinates[1],
+    };
   }
 
   /**
-- 
GitLab