text: /* global QUnit, Util, $, console, testLog */
  
  (function() {
      var viewer;
      // jQuery.position can give results quite different than what set in style.left
      var epsilon = 1;
  
      QUnit.module("Overlays", {
          beforeEach: function() {
              var example = $('<div id="example-overlays"></div>').appendTo("#qunit-fixture");
              var fixedOverlay = $('<div id="fixed-overlay"></div>').appendTo(example);
              fixedOverlay.width(70);
              fixedOverlay.height(60);
  
              testLog.reset();
          },
          afterEach: function() {
              resetTestVariables();
          }
      });
  
      var resetTestVariables = function() {
          if (viewer) {
              viewer.close();
          }
      };
  
      function waitForViewer(handler, count) {
          if (typeof count !== "number") {
              count = 0;
          }
          var ready = viewer.isOpen() &&
              viewer.drawer !== null &&
              !viewer.world.needsDraw() &&
              Util.equalsWithVariance(viewer.viewport.getBounds(true).x,
                  viewer.viewport.getBounds().x, 0.000) &&
              Util.equalsWithVariance(viewer.viewport.getBounds(true).y,
                  viewer.viewport.getBounds().y, 0.000) &&
              Util.equalsWithVariance(viewer.viewport.getBounds(true).width,
                  viewer.viewport.getBounds().width, 0.000);
  
          if (ready) {
              handler();
          } else if (count < 50) {
              count++;
              setTimeout(function() {
                  waitForViewer(handler, count);
              }, 100);
          } else {
              console.log("waitForViewer:" + viewer.isOpen( ) + ":" + viewer.drawer +
                  ":" + viewer.world.needsDraw());
              handler();
          }
      }
  
      QUnit.test('Overlays via viewer options', function(assert) {
          var done = assert.async();
          viewer = OpenSeadragon({
              id: 'example-overlays',
              prefixUrl: '/build/openseadragon/images/',
              tileSources: ['/test/data/testpattern.dzi', '/test/data/testpattern.dzi'],
              springStiffness: 100, // Faster animation = faster tests
              overlays: [{
                      x: 0.1,
                      y: 0.4,
                      width: 0.09,
                      height: 0.09,
                      id: "overlay"
                  }]
          });
          viewer.addHandler('open', openHandler);
  
          function openHandler() {
              viewer.removeHandler('open', openHandler);
  
              assert.equal(viewer.overlays.length, 1, "Global overlay should be added.");
              assert.equal(viewer.currentOverlays.length, 1, "Global overlay should be open.");
  
              viewer.addHandler('open', openPageHandler);
              viewer.goToPage(1);
          }
  
          function openPageHandler() {
              viewer.removeHandler('open', openPageHandler);
  
              assert.equal(viewer.overlays.length, 1, "Global overlay should stay after page switch.");
              assert.equal(viewer.currentOverlays.length, 1, "Global overlay should re-open after page switch.");
  
              viewer.addHandler('close', closeHandler);
              viewer.close();
          }
  
          function closeHandler() {
              viewer.removeHandler('close', closeHandler);
  
              assert.equal(viewer.overlays.length, 1, "Global overlay should not be removed on close.");
              assert.equal(viewer.currentOverlays.length, 0, "Global overlay should be closed on close.");
  
              done();
          }
      });
  
      QUnit.test('Page Overlays via viewer options', function(assert) {
          var done = assert.async();
          viewer = OpenSeadragon({
              id: 'example-overlays',
              prefixUrl: '/build/openseadragon/images/',
              tileSources: [{
                      Image: {
                          xmlns: "http://schemas.microsoft.com/deepzoom/2008",
                          Url: "/test/data/testpattern_files/",
                          Format: "jpg",
                          Overlap: "1",
                          TileSize: "254",
                          Size: {
                              Width: 1000,
                              Height: 1000
                          }
                      },
                      overlays: [{
                              x: 0.1,
                              y: 0.4,
                              width: 0.09,
                              height: 0.09,
                              id: "overlay"
                          }]
                  }, {
                      Image: {
                          xmlns: "http://schemas.microsoft.com/deepzoom/2008",
                          Url: "/test/data/testpattern_files/",
                          Format: "jpg",
                          Overlap: "1",
                          TileSize: "254",
                          Size: {
                              Width: 1000,
                              Height: 1000
                          }
                      }
                  }],
              springStiffness: 100 // Faster animation = faster tests
          });
          viewer.addHandler('open', openHandler);
  
          function openHandler() {
              viewer.removeHandler('open', openHandler);
  
              assert.equal(viewer.overlays.length, 0, "No global overlay should be added.");
              assert.equal(viewer.currentOverlays.length, 1, "Page overlay should be open.");
  
              viewer.addHandler('open', openPageHandler);
              viewer.goToPage(1);
          }
  
          function openPageHandler() {
              viewer.removeHandler('open', openPageHandler);
  
              assert.equal(viewer.overlays.length, 0, "No global overlay should be added after page switch.");
              assert.equal(viewer.currentOverlays.length, 0, "No page overlay should be opened after page switch.");
  
              viewer.addHandler('close', closeHandler);
              viewer.close();
          }
  
          function closeHandler() {
              viewer.removeHandler('close', closeHandler);
  
              assert.equal(viewer.overlays.length, 0, "No global overlay should be added on close.");
              assert.equal(viewer.currentOverlays.length, 0, "Page overlay should be closed on close.");
  
              done();
          }
      });
  
      QUnit.test('Overlays via addOverlay method', function(assert) {
          var done = assert.async();
          viewer = OpenSeadragon({
              id: 'example-overlays',
              prefixUrl: '/build/openseadragon/images/',
              tileSources: ['/test/data/testpattern.dzi', '/test/data/testpattern.dzi'],
              springStiffness: 100 // Faster animation = faster tests
          });
          viewer.addHandler('open', openHandler);
  
          function openHandler() {
              viewer.removeHandler('open', openHandler);
  
              assert.equal(viewer.overlays.length, 0, "No global overlay should be added.");
              assert.equal(viewer.currentOverlays.length, 0, "No overlay should be open.");
  
              var rect = new OpenSeadragon.Rect(0.1, 0.1, 0.1, 0.1);
              var overlay = $("<div/>").prop("id", "overlay").get(0);
              viewer.addOverlay(overlay, rect);
              assert.equal(viewer.overlays.length, 0, "No manual overlay should be added as global overlay.");
              assert.equal(viewer.currentOverlays.length, 1, "A manual overlay should be open.");
  
              viewer.addHandler('open', openPageHandler);
              viewer.goToPage(1);
          }
  
          function openPageHandler() {
              viewer.removeHandler('open', openPageHandler);
  
              assert.equal(viewer.overlays.length, 0, "No global overlay should be added after page switch.");
              assert.equal(viewer.currentOverlays.length, 0, "Manual overlay should be removed after page switch.");
  
              viewer.addHandler('close', closeHandler);
              viewer.close();
          }
  
          function closeHandler() {
              viewer.removeHandler('close', closeHandler);
  
              assert.equal(viewer.overlays.length, 0, "No global overlay should be added on close.");
              assert.equal(viewer.currentOverlays.length, 0, "Manual overlay should be removed on close.");
  
              done();
          }
  
      });
  
      QUnit.test('Overlays size in pixels', function(assert) {
          var done = assert.async();
          viewer = OpenSeadragon({
              id: 'example-overlays',
              prefixUrl: '/build/openseadragon/images/',
              tileSources: '/test/data/testpattern.dzi',
              springStiffness: 100, // Faster animation = faster tests
              overlays: [{
                      px: 13,
                      py: 120,
                      width: 124,
                      height: 132,
                      id: "overlay"
                  }, {
                      px: 400,
                      py: 500,
                      id: "fixed-overlay",
                      placement: "TOP_LEFT"
                  }]
          });
  
          function checkOverlayPosition(contextMessage) {
              var viewport = viewer.viewport;
  
              var expPosition = viewport.imageToViewerElementCoordinates(
                  new OpenSeadragon.Point(13, 120));
              var actPosition = $("#overlay").position();
              Util.assessNumericValue(assert, actPosition.left, expPosition.x, epsilon,
                  "X position mismatch " + contextMessage);
              Util.assessNumericValue(assert, actPosition.top, expPosition.y, epsilon,
                  "Y position mismatch " + contextMessage);
  
              var zoom = viewport.viewportToImageZoom(viewport.getZoom(true));
              var expectedWidth = 124 * zoom;
              var expectedHeight = 132 * zoom;
              Util.assessNumericValue(assert, $("#overlay").width(), expectedWidth, epsilon,
                  "Width mismatch " + contextMessage);
              Util.assessNumericValue(assert, $("#overlay").height(), expectedHeight, epsilon,
                  "Height mismatch " + contextMessage);
  
  
              expPosition = viewport.imageToViewerElementCoordinates(
                  new OpenSeadragon.Point(400, 500));
              actPosition = $("#fixed-overlay").position();
              Util.assessNumericValue(assert, actPosition.left, expPosition.x, epsilon,
                  "Fixed overlay X position mismatch " + contextMessage);
              Util.assessNumericValue(assert, actPosition.top, expPosition.y, epsilon,
                  "Fixed overlay Y position mismatch " + contextMessage);
  
              Util.assessNumericValue(assert, $("#fixed-overlay").width(), 70, epsilon,
                  "Fixed overlay width mismatch " + contextMessage);
              Util.assessNumericValue(assert, $("#fixed-overlay").height(), 60, epsilon,
                  "Fixed overlay height mismatch " + contextMessage);
          }
  
          waitForViewer(function() {
              checkOverlayPosition("after opening using image coordinates");
  
              viewer.viewport.zoomBy(1.1).panBy(new OpenSeadragon.Point(0.1, 0.2));
              waitForViewer(function() {
                  checkOverlayPosition("after zoom and pan using image coordinates");
  
                  viewer.viewport.goHome();
                  waitForViewer(function() {
                      checkOverlayPosition("after goHome using image coordinates");
                      done();
                  });
              });
  
          });
      });
  
      QUnit.test('Overlays size in points', function(assert) {
          var done = assert.async();
          viewer = OpenSeadragon({
              id: 'example-overlays',
              prefixUrl: '/build/openseadragon/images/',
              tileSources: '/test/data/testpattern.dzi',
              springStiffness: 100, // Faster animation = faster tests
              overlays: [{
                      x: 0.2,
                      y: 0.1,
                      width: 0.5,
                      height: 0.1,
                      id: "overlay"
                  }, {
                      x: 0.5,
                      y: 0.6,
                      id: "fixed-overlay",
                      placement: "TOP_LEFT"
                  }]
          });
  
          function checkOverlayPosition(contextMessage) {
              var viewport = viewer.viewport;
  
              var expPosition = viewport.viewportToViewerElementCoordinates(
                  new OpenSeadragon.Point(0.2, 0.1));
              var actPosition = $("#overlay").position();
              Util.assessNumericValue(assert, actPosition.left, expPosition.x, epsilon,
                  "X position mismatch " + contextMessage);
              Util.assessNumericValue(assert, actPosition.top, expPosition.y, epsilon,
                  "Y position mismatch " + contextMessage);
  
              var expectedSize = viewport.deltaPixelsFromPoints(
                  new OpenSeadragon.Point(0.5, 0.1));
              Util.assessNumericValue(assert, $("#overlay").width(), expectedSize.x, epsilon,
                  "Width mismatch " + contextMessage);
              Util.assessNumericValue(assert, $("#overlay").height(), expectedSize.y, epsilon,
                  "Height mismatch " + contextMessage);
  
  
              expPosition = viewport.viewportToViewerElementCoordinates(
                  new OpenSeadragon.Point(0.5, 0.6));
              actPosition = $("#fixed-overlay").position();
              Util.assessNumericValue(assert, actPosition.left, expPosition.x, epsilon,
                  "Fixed overlay X position mismatch " + contextMessage);
              Util.assessNumericValue(assert, actPosition.top, expPosition.y, epsilon,
                  "Fixed overlay Y position mismatch " + contextMessage);
  
              Util.assessNumericValue(assert, $("#fixed-overlay").width(), 70, epsilon,
                  "Fixed overlay width mismatch " + contextMessage);
              Util.assessNumericValue(assert, $("#fixed-overlay").height(), 60, epsilon,
                  "Fixed overlay height mismatch " + contextMessage);
          }
  
          waitForViewer(function() {
              checkOverlayPosition("after opening using viewport coordinates");
  
              viewer.viewport.zoomBy(1.1).panBy(new OpenSeadragon.Point(0.1, 0.2));
              waitForViewer(function() {
                  checkOverlayPosition("after zoom and pan using viewport coordinates");
  
                  viewer.viewport.goHome();
                  waitForViewer(function() {
                      checkOverlayPosition("after goHome using viewport coordinates");
                      done();
                  });
              });
  
          });
      });
  
      QUnit.test('Overlays placement', function(assert) {
          var done = assert.async();
          var scalableOverlayLocation = new OpenSeadragon.Rect(0.2, 0.1, 0.5, 0.1);
          var fixedOverlayLocation = new OpenSeadragon.Point(0.5, 0.6);
  
          viewer = OpenSeadragon({
              id: 'example-overlays',
              prefixUrl: '/build/openseadragon/images/',
              tileSources: '/test/data/testpattern.dzi',
              springStiffness: 100, // Faster animation = faster tests
              overlays: [{
                      x: scalableOverlayLocation.x,
                      y: scalableOverlayLocation.y,
                      width: scalableOverlayLocation.width,
                      height: scalableOverlayLocation.height,
                      id: "overlay",
                      placement: "TOP_LEFT"
                  }, {
                      x: fixedOverlayLocation.x,
                      y: fixedOverlayLocation.y,
                      id: "fixed-overlay",
                      placement: "TOP_LEFT"
                  }]
          });
  
          // Scalable overlays are always TOP_LEFT
          function checkScalableOverlayPosition(contextMessage) {
              var viewport = viewer.viewport;
  
              var expPosition = viewport.viewportToViewerElementCoordinates(
                  new OpenSeadragon.Point(0.2, 0.1));
              var actPosition = $("#overlay").position();
              Util.assessNumericValue(assert, actPosition.left, expPosition.x, epsilon,
                  "X position mismatch " + contextMessage);
              Util.assessNumericValue(assert, actPosition.top, expPosition.y, epsilon,
                  "Y position mismatch " + contextMessage);
          }
  
          function checkFixedOverlayPosition(expectedOffset, contextMessage) {
              var viewport = viewer.viewport;
  
              var expPosition = viewport.viewportToViewerElementCoordinates(
                  new OpenSeadragon.Point(0.5, 0.6))
                  .plus(expectedOffset);
              var actPosition = $("#fixed-overlay").position();
              Util.assessNumericValue(assert, actPosition.left, expPosition.x, epsilon,
                  "Fixed overlay X position mismatch " + contextMessage);
              Util.assessNumericValue(assert, actPosition.top, expPosition.y, epsilon,
                  "Fixed overlay Y position mismatch " + contextMessage);
          }
  
          waitForViewer(function() {
  
              checkScalableOverlayPosition("with TOP_LEFT placement.");
              checkFixedOverlayPosition(new OpenSeadragon.Point(0, 0),
                  "with TOP_LEFT placement.");
  
              // Check that legacy OpenSeadragon.OverlayPlacement is still working
              viewer.updateOverlay("overlay", scalableOverlayLocation,
                  OpenSeadragon.OverlayPlacement.CENTER);
              viewer.updateOverlay("fixed-overlay", fixedOverlayLocation,
                  OpenSeadragon.OverlayPlacement.CENTER);
  
              setTimeout(function() {
                  checkScalableOverlayPosition("with CENTER placement.");
                  checkFixedOverlayPosition(new OpenSeadragon.Point(-35, -30),
                      "with CENTER placement.");
  
                  // Check that new OpenSeadragon.Placement is working
                  viewer.updateOverlay("overlay", scalableOverlayLocation,
                      OpenSeadragon.Placement.BOTTOM_RIGHT);
                  viewer.updateOverlay("fixed-overlay", fixedOverlayLocation,
                      OpenSeadragon.Placement.BOTTOM_RIGHT);
                  setTimeout(function() {
                      checkScalableOverlayPosition("with BOTTOM_RIGHT placement.");
                      checkFixedOverlayPosition(new OpenSeadragon.Point(-70, -60),
                          "with BOTTOM_RIGHT placement.");
  
                      done();
                  }, 100);
  
              }, 100);
  
          });
      });
  
      QUnit.test('Overlays placement and resizing check', function(assert) {
          var done = assert.async();
          var fixedOverlayLocation = new OpenSeadragon.Point(0.5, 0.6);
  
          viewer = OpenSeadragon({
              id: 'example-overlays',
              prefixUrl: '/build/openseadragon/images/',
              tileSources: '/test/data/testpattern.dzi',
              springStiffness: 100, // Faster animation = faster tests
              overlays: [{
                      x: fixedOverlayLocation.x,
                      y: fixedOverlayLocation.y,
                      id: "fixed-overlay",
                      placement: "CENTER",
                      checkResize: true
                  }]
          });
  
          function checkFixedOverlayPosition(expectedOffset, contextMessage) {
              var viewport = viewer.viewport;
  
              var expPosition = viewport.viewportToViewerElementCoordinates(
                  new OpenSeadragon.Point(0.5, 0.6))
                  .plus(expectedOffset);
              var actPosition = $("#fixed-overlay").position();
              Util.assessNumericValue(assert, actPosition.left, expPosition.x, epsilon,
                  "Fixed overlay X position mismatch " + contextMessage);
              Util.assessNumericValue(assert, actPosition.top, expPosition.y, epsilon,
                  "Fixed overlay Y position mismatch " + contextMessage);
          }
  
          waitForViewer(function() {
              checkFixedOverlayPosition(new OpenSeadragon.Point(-35, -30),
                  "with overlay of size 70,60.");
  
              $("#fixed-overlay").width(50);
              $("#fixed-overlay").height(40);
  
              // The resizing of the overlays is not detected by the viewer's loop.
              viewer.forceRedraw();
  
              setTimeout(function() {
                  checkFixedOverlayPosition(new OpenSeadragon.Point(-25, -20),
                      "with overlay of size 50,40.");
  
                  // Restore original size
                  $("#fixed-overlay").width(70);
                  $("#fixed-overlay").height(60);
  
                  done();
              }, 100);
          });
  
      });
  
      QUnit.test('Overlays placement and no resizing check', function(assert) {
          var done = assert.async();
          var fixedOverlayLocation = new OpenSeadragon.Point(0.5, 0.6);
  
          viewer = OpenSeadragon({
              id: 'example-overlays',
              prefixUrl: '/build/openseadragon/images/',
              tileSources: '/test/data/testpattern.dzi',
              springStiffness: 100, // Faster animation = faster tests
              overlays: [{
                      x: fixedOverlayLocation.x,
                      y: fixedOverlayLocation.y,
                      id: "fixed-overlay",
                      placement: "CENTER",
                      checkResize: false
                  }]
          });
  
          function checkFixedOverlayPosition(expectedOffset, contextMessage) {
              var viewport = viewer.viewport;
  
              var expPosition = viewport.viewportToViewerElementCoordinates(
                  new OpenSeadragon.Point(0.5, 0.6))
                  .plus(expectedOffset);
              var actPosition = $("#fixed-overlay").position();
              Util.assessNumericValue(assert, actPosition.left, expPosition.x, epsilon,
                  "Fixed overlay X position mismatch " + contextMessage);
              Util.assessNumericValue(assert, actPosition.top, expPosition.y, epsilon,
                  "Fixed overlay Y position mismatch " + contextMessage);
          }
  
          waitForViewer(function() {
              checkFixedOverlayPosition(new OpenSeadragon.Point(-35, -30),
                  "with overlay of size 70,60.");
  
              $("#fixed-overlay").width(50);
              $("#fixed-overlay").height(40);
  
              // The resizing of the overlays is not detected by the viewer's loop.
              viewer.forceRedraw();
  
              setTimeout(function() {
                  checkFixedOverlayPosition(new OpenSeadragon.Point(-35, -30),
                      "with overlay of size 50,40.");
  
                  // Restore original size
                  $("#fixed-overlay").width(70);
                  $("#fixed-overlay").height(60);
  
                  done();
              }, 100);
          });
  
      });
  
      // ----------
      QUnit.test('overlays appear immediately', function(assert) {
          var done = assert.async();
          assert.equal($('#immediate-overlay0').length, 0, 'overlay 0 does not exist');
          assert.equal($('#immediate-overlay1').length, 0, 'overlay 1 does not exist');
  
          viewer = OpenSeadragon({
              id: 'example-overlays',
              prefixUrl: '/build/openseadragon/images/',
              tileSources: '/test/data/testpattern.dzi',
              springStiffness: 100, // Faster animation = faster tests
              overlays: [{
                      x: 0,
                      y: 0,
                      id: "immediate-overlay0"
                  }]
          });
  
          viewer.addHandler('open', function() {
              assert.equal($('#immediate-overlay0').length, 1, 'overlay 0 exists');
  
              viewer.addOverlay({
                  x: 0,
                  y: 0,
                  id: "immediate-overlay1"
              });
  
              assert.equal($('#immediate-overlay1').length, 1, 'overlay 1 exists');
              done();
          });
      });
  
      // ----------
      QUnit.test('Overlay scaled horizontally only', function(assert) {
          var done = assert.async();
          viewer = OpenSeadragon({
              id: 'example-overlays',
              prefixUrl: '/build/openseadragon/images/',
              tileSources: '/test/data/testpattern.dzi',
              springStiffness: 100 // Faster animation = faster tests
          });
  
          viewer.addHandler('open', function() {
              viewer.addOverlay({
                  id: "horizontally-scaled-overlay",
                  x: 0,
                  y: 0,
                  width: 1
              });
  
              var width = $("#horizontally-scaled-overlay").width();
              var height = 100;
              var zoom = 1.1;
              $("#horizontally-scaled-overlay").get(0).style.height = height + "px";
  
              viewer.viewport.zoomBy(zoom);
  
              waitForViewer(function() {
                  var newWidth = $("#horizontally-scaled-overlay").width();
                  var newHeight = $("#horizontally-scaled-overlay").height();
                  assert.equal(newWidth, width * zoom, "Width should be scaled.");
                  assert.equal(newHeight, height, "Height should not be scaled.");
  
                  done();
              });
          });
      });
  
      // ----------
      QUnit.test('Overlay scaled vertically only', function(assert) {
          var done = assert.async();
          viewer = OpenSeadragon({
              id: 'example-overlays',
              prefixUrl: '/build/openseadragon/images/',
              tileSources: '/test/data/testpattern.dzi',
              springStiffness: 100 // Faster animation = faster tests
          });
  
          viewer.addHandler('open', function() {
              viewer.addOverlay({
                  id: "vertically-scaled-overlay",
                  x: 0,
                  y: 0,
                  height: 1
              });
  
              var width = 100;
              var height = $("#vertically-scaled-overlay").height();
              var zoom = 1.1;
              $("#vertically-scaled-overlay").get(0).style.width = width + "px";
  
              viewer.viewport.zoomBy(zoom);
  
              waitForViewer(function() {
                  var newWidth = $("#vertically-scaled-overlay").width();
                  var newHeight = $("#vertically-scaled-overlay").height();
                  assert.equal(newWidth, width, "Width should not be scaled.");
                  assert.equal(newHeight, height * zoom, "Height should be scaled.");
  
                  done();
              });
          });
      });
  
      // ----------
      QUnit.test('Overlay.getBounds', function(assert) {
          var done = assert.async();
          viewer = OpenSeadragon({
              id: 'example-overlays',
              prefixUrl: '/build/openseadragon/images/',
              tileSources: '/test/data/testpattern.dzi',
              springStiffness: 100 // Faster animation = faster tests
          });
  
          viewer.addHandler('open', function() {
              viewer.addOverlay({
                  id: "fully-scaled-overlay",
                  x: 1,
                  y: 1,
                  width: 1,
                  height: 1,
                  placement: OpenSeadragon.Placement.BOTTOM_RIGHT
              });
              viewer.addOverlay({
                  id: "horizontally-scaled-overlay",
                  x: 0.5,
                  y: 0.5,
                  width: 1,
                  placement: OpenSeadragon.Placement.CENTER
              });
              viewer.addOverlay({
                  id: "vertically-scaled-overlay",
                  x: 0,
                  y: 0.5,
                  height: 1,
                  placement: OpenSeadragon.Placement.LEFT
              });
              viewer.addOverlay({
                  id: "not-scaled-overlay",
                  x: 1,
                  y: 0,
                  placement: OpenSeadragon.Placement.TOP_RIGHT
              });
  
              var notScaledWidth = 100;
              var notScaledHeight = 100;
              $("#horizontally-scaled-overlay").get(0).style.height = notScaledHeight + "px";
              $("#vertically-scaled-overlay").get(0).style.width = notScaledWidth + "px";
              $("#not-scaled-overlay").get(0).style.width = notScaledWidth + "px";
              $("#not-scaled-overlay").get(0).style.height = notScaledHeight + "px";
  
              var notScaledSize = viewer.viewport.deltaPointsFromPixelsNoRotate(
                  new OpenSeadragon.Point(notScaledWidth, notScaledHeight));
  
              // Force refresh to takes new dimensions into account.
              viewer._drawOverlays();
  
              var actualBounds = viewer.getOverlayById("fully-scaled-overlay")
                  .getBounds(viewer.viewport);
              var expectedBounds = new OpenSeadragon.Rect(0, 0, 1, 1);
              assert.ok(expectedBounds.equals(actualBounds),
                  "The fully scaled overlay should have bounds " +
                  expectedBounds + " but found " + actualBounds);
  
  
              actualBounds = viewer.getOverlayById("horizontally-scaled-overlay")
                  .getBounds(viewer.viewport);
              expectedBounds = new OpenSeadragon.Rect(
                  0, 0.5 - notScaledSize.y / 2, 1, notScaledSize.y);
              assert.ok(expectedBounds.equals(actualBounds),
                  "The horizontally scaled overlay should have bounds " +
                  expectedBounds + " but found " + actualBounds);
  
              actualBounds = viewer.getOverlayById("vertically-scaled-overlay")
                  .getBounds(viewer.viewport);
              expectedBounds = new OpenSeadragon.Rect(
                  0, 0, notScaledSize.x, 1);
              assert.ok(expectedBounds.equals(actualBounds),
                  "The vertically scaled overlay should have bounds " +
                  expectedBounds + " but found " + actualBounds);
  
              actualBounds = viewer.getOverlayById("not-scaled-overlay")
                  .getBounds(viewer.viewport);
              expectedBounds = new OpenSeadragon.Rect(
                  1 - notScaledSize.x, 0, notScaledSize.x, notScaledSize.y);
              assert.ok(expectedBounds.equals(actualBounds),
                  "The not scaled overlay should have bounds " +
                  expectedBounds + " but found " + actualBounds);
  
              done();
          });
      });
  
      // ----------
      QUnit.test('Fully scaled overlay rotation mode NO_ROTATION', function(assert) {
          var done = assert.async();
          viewer = OpenSeadragon({
              id: 'example-overlays',
              prefixUrl: '/build/openseadragon/images/',
              tileSources: '/test/data/testpattern.dzi',
              springStiffness: 100, // Faster animation = faster tests
              degrees: 45,
              overlays: [{
                      id: "fully-scaled-overlay",
                      x: 1,
                      y: 1,
                      width: 1,
                      height: 1,
                      placement: OpenSeadragon.Placement.BOTTOM_RIGHT,
                      rotationMode: OpenSeadragon.OverlayRotationMode.NO_ROTATION
                  }]
          });
  
          viewer.addOnceHandler('open', function() {
              var viewport = viewer.viewport;
  
              var $overlay = $("#fully-scaled-overlay");
              var expectedSize = viewport.deltaPixelsFromPointsNoRotate(
                  new OpenSeadragon.Point(1, 1));
              var expectedPosition = viewport.viewportToViewerElementCoordinates(
                  new OpenSeadragon.Point(1, 1))
                  .minus(expectedSize);
              var actualPosition = $overlay.position();
              Util.assessNumericValue(assert, actualPosition.left, expectedPosition.x, epsilon,
                  "Scaled overlay position.x should adjust to rotation.");
              Util.assessNumericValue(assert, actualPosition.top, expectedPosition.y, epsilon,
                  "Scaled overlay position.y should adjust to rotation.");
  
              var actualWidth = $overlay.width();
              var actualHeight = $overlay.height();
              Util.assessNumericValue(assert, actualWidth, expectedSize.x, epsilon,
                  "Scaled overlay width should not adjust to rotation.");
              Util.assessNumericValue(assert, actualHeight, expectedSize.y, epsilon,
                  "Scaled overlay height should not adjust to rotation.");
  
              var actualBounds = viewer.getOverlayById("fully-scaled-overlay")
                  .getBounds(viewport);
              var expectedBounds = new OpenSeadragon.Rect(0, 0, 1, 1)
                  .rotate(-45, new OpenSeadragon.Point(1, 1));
              assert.ok(expectedBounds.equals(actualBounds),
                  "The fully scaled overlay should have bounds " +
                  expectedBounds + " but found " + actualBounds);
  
              done();
          });
      });
  
      // ----------
      QUnit.test('Horizontally scaled overlay rotation mode NO_ROTATION', function(assert) {
          var done = assert.async();
          viewer = OpenSeadragon({
              id: 'example-overlays',
              prefixUrl: '/build/openseadragon/images/',
              tileSources: '/test/data/testpattern.dzi',
              springStiffness: 100, // Faster animation = faster tests
              degrees: 45,
              overlays: [{
                      id: "horizontally-scaled-overlay",
                      x: 0.5,
                      y: 0.5,
                      width: 1,
                      placement: OpenSeadragon.Placement.CENTER,
                      rotationMode: OpenSeadragon.OverlayRotationMode.NO_ROTATION
                  }]
          });
  
          viewer.addOnceHandler('open', function() {
              var $overlay = $("#horizontally-scaled-overlay");
              var notScaledWidth = 100;
              var notScaledHeight = 100;
              $overlay.get(0).style.height = notScaledHeight + "px";
  
              var viewport = viewer.viewport;
              var notScaledSize = viewport.deltaPointsFromPixelsNoRotate(
                  new OpenSeadragon.Point(notScaledWidth, notScaledHeight));
  
              // Force refresh to takes new dimensions into account.
              viewer._drawOverlays();
  
              var expectedWidth = viewport.deltaPixelsFromPointsNoRotate(
                  new OpenSeadragon.Point(1, 1)).x;
              var expectedPosition = viewport.viewportToViewerElementCoordinates(
                  new OpenSeadragon.Point(0.5, 0.5))
                  .minus(new OpenSeadragon.Point(expectedWidth / 2, notScaledHeight / 2));
              var actualPosition = $overlay.position();
              Util.assessNumericValue(assert, actualPosition.left, expectedPosition.x, epsilon,
                  "Horizontally scaled overlay position.x should adjust to rotation.");
              Util.assessNumericValue(assert, actualPosition.top, expectedPosition.y, epsilon,
                  "Horizontally scaled overlay position.y should adjust to rotation.");
  
              var actualWidth = $overlay.width();
              var actualHeight = $overlay.height();
              Util.assessNumericValue(assert, actualWidth, expectedWidth, epsilon,
                  "Horizontally scaled overlay width should not adjust to rotation.");
              Util.assessNumericValue(assert, actualHeight, notScaledHeight, epsilon,
                  "Horizontally scaled overlay height should not adjust to rotation.");
  
              var actualBounds = viewer.getOverlayById("horizontally-scaled-overlay")
                  .getBounds(viewport);
              var expectedBounds = new OpenSeadragon.Rect(
                  0, 0.5 - notScaledSize.y / 2, 1, notScaledSize.y)
                  .rotate(-45, new OpenSeadragon.Point(0.5, 0.5));
              assert.ok(expectedBounds.equals(actualBounds),
                  "The horizontally scaled overlay should have bounds " +
                  expectedBounds + " but found " + actualBounds);
  
              done();
          });
      });
  
      // ----------
      QUnit.test('Vertically scaled overlay rotation mode NO_ROTATION', function(assert) {
          var done = assert.async();
          viewer = OpenSeadragon({
              id: 'example-overlays',
              prefixUrl: '/build/openseadragon/images/',
              tileSources: '/test/data/testpattern.dzi',
              springStiffness: 100, // Faster animation = faster tests
              degrees: 45,
              overlays: [{
                      id: "vertically-scaled-overlay",
                      x: 0,
                      y: 0.5,
                      height: 1,
                      placement: OpenSeadragon.Placement.LEFT,
                      rotationMode: OpenSeadragon.OverlayRotationMode.NO_ROTATION
                  }]
          });
  
          viewer.addOnceHandler('open', function() {
              var $overlay = $("#vertically-scaled-overlay");
              var notScaledWidth = 100;
              var notScaledHeight = 100;
              $overlay.get(0).style.width = notScaledWidth + "px";
  
              var viewport = viewer.viewport;
              var notScaledSize = viewport.deltaPointsFromPixelsNoRotate(
                  new OpenSeadragon.Point(notScaledWidth, notScaledHeight));
  
              // Force refresh to takes new dimensions into account.
              viewer._drawOverlays();
  
              var expectedHeight = viewport.deltaPixelsFromPointsNoRotate(
                  new OpenSeadragon.Point(1, 1)).y;
              var expectedPosition = viewport.viewportToViewerElementCoordinates(
                  new OpenSeadragon.Point(0, 0.5))
                  .minus(new OpenSeadragon.Point(0, expectedHeight / 2));
              var actualPosition = $overlay.position();
              Util.assessNumericValue(assert, actualPosition.left, expectedPosition.x, epsilon,
                  "Vertically scaled overlay position.x should adjust to rotation.");
              Util.assessNumericValue(assert, actualPosition.top, expectedPosition.y, epsilon,
                  "Vertically scaled overlay position.y should adjust to rotation.");
  
              var actualWidth = $overlay.width();
              var actualHeight = $overlay.height();
              Util.assessNumericValue(assert, actualWidth, notScaledWidth, epsilon,
                  "Vertically scaled overlay width should not adjust to rotation.");
              Util.assessNumericValue(assert, actualHeight, expectedHeight, epsilon,
                  "Vertically scaled overlay height should not adjust to rotation.");
  
              var actualBounds = viewer.getOverlayById("vertically-scaled-overlay")
                  .getBounds(viewport);
              var expectedBounds = new OpenSeadragon.Rect(
                  0, 0, notScaledSize.x, 1)
                  .rotate(-45, new OpenSeadragon.Point(0, 0.5));
              assert.ok(expectedBounds.equals(actualBounds),
                  "The vertically scaled overlay should have bounds " +
                  expectedBounds + " but found " + actualBounds);
  
              done();
          });
      });
  
      // ----------
      QUnit.test('Not scaled overlay rotation mode NO_ROTATION', function(assert) {
          var done = assert.async();
          viewer = OpenSeadragon({
              id: 'example-overlays',
              prefixUrl: '/build/openseadragon/images/',
              tileSources: '/test/data/testpattern.dzi',
              springStiffness: 100, // Faster animation = faster tests
              degrees: 45,
              overlays: [{
                      id: "not-scaled-overlay",
                      x: 1,
                      y: 0,
                      placement: OpenSeadragon.Placement.TOP_RIGHT,
                      rotationMode: OpenSeadragon.OverlayRotationMode.NO_ROTATION
                  }]
          });
  
          viewer.addOnceHandler('open', function() {
              var $overlay = $("#not-scaled-overlay");
              var notScaledWidth = 100;
              var notScaledHeight = 100;
              $overlay.get(0).style.width = notScaledWidth + "px";
              $overlay.get(0).style.height = notScaledHeight + "px";
  
              var viewport = viewer.viewport;
              var notScaledSize = viewport.deltaPointsFromPixelsNoRotate(
                  new OpenSeadragon.Point(notScaledWidth, notScaledHeight));
  
              // Force refresh to takes new dimensions into account.
              viewer._drawOverlays();
  
              var expectedPosition = viewport.viewportToViewerElementCoordinates(
                  new OpenSeadragon.Point(1, 0))
                  .minus(new OpenSeadragon.Point(notScaledWidth, 0));
              var actualPosition = $overlay.position();
              Util.assessNumericValue(assert, actualPosition.left, expectedPosition.x, epsilon,
                  "Not scaled overlay position.x should adjust to rotation.");
              Util.assessNumericValue(assert, actualPosition.top, expectedPosition.y, epsilon,
                  "Not scaled overlay position.y should adjust to rotation.");
  
              var actualWidth = $overlay.width();
              var actualHeight = $overlay.height();
              Util.assessNumericValue(assert, actualWidth, notScaledWidth, epsilon,
                  "Not scaled overlay width should not adjust to rotation.");
              Util.assessNumericValue(assert, actualHeight, notScaledHeight, epsilon,
                  "Not scaled overlay height should not adjust to rotation.");
  
              var actualBounds = viewer.getOverlayById("not-scaled-overlay")
                  .getBounds(viewport);
              var expectedBounds = new OpenSeadragon.Rect(
                  1 - notScaledSize.x, 0, notScaledSize.x, notScaledSize.y)
                  .rotate(-45, new OpenSeadragon.Point(1, 0));
              assert.ok(expectedBounds.equals(actualBounds),
                  "Not scaled overlay should have bounds " +
                  expectedBounds + " but found " + actualBounds);
  
              done();
          });
      });
  
      // ----------
      QUnit.test('Fully scaled overlay rotation mode BOUNDING_BOX', function(assert) {
          var done = assert.async();
          viewer = OpenSeadragon({
              id: 'example-overlays',
              prefixUrl: '/build/openseadragon/images/',
              tileSources: '/test/data/testpattern.dzi',
              springStiffness: 100, // Faster animation = faster tests
              degrees: 45,
              overlays: [{
                      id: "fully-scaled-overlay",
                      x: 1,
                      y: 1,
                      width: 1,
                      height: 1,
                      placement: OpenSeadragon.Placement.BOTTOM_RIGHT,
                      rotationMode: OpenSeadragon.OverlayRotationMode.BOUNDING_BOX
                  }]
          });
  
          viewer.addOnceHandler('open', function() {
              var viewport = viewer.viewport;
  
              var $overlay = $("#fully-scaled-overlay");
              var expectedRect = viewport.viewportToViewerElementRectangle(
                  new OpenSeadragon.Rect(0, 0, 1, 1)).getBoundingBox();
              var actualPosition = $overlay.position();
              Util.assessNumericValue(assert, actualPosition.left, expectedRect.x, epsilon,
                  "Scaled overlay position.x should adjust to rotation.");
              Util.assessNumericValue(assert, actualPosition.top, expectedRect.y, epsilon,
                  "Scaled overlay position.y should adjust to rotation.");
  
              var actualWidth = $overlay.width();
              var actualHeight = $overlay.height();
              Util.assessNumericValue(assert, actualWidth, expectedRect.width, epsilon,
                  "Scaled overlay width should not adjust to rotation.");
              Util.assessNumericValue(assert, actualHeight, expectedRect.height, epsilon,
                  "Scaled overlay height should not adjust to rotation.");
  
              var actualBounds = viewer.getOverlayById("fully-scaled-overlay")
                  .getBounds(viewport);
              var expectedBounds = new OpenSeadragon.Rect(
                      0.5, -0.5, Math.sqrt(2), Math.sqrt(2), 45);
              var boundsEpsilon = 0.000001;
              Util.assessNumericValue(assert, actualBounds.x, expectedBounds.x, boundsEpsilon,
                  "The fully scaled overlay should have adjusted bounds.x");
              Util.assessNumericValue(assert, actualBounds.y, expectedBounds.y, boundsEpsilon,
                  "The fully scaled overlay should have adjusted bounds.y");
              Util.assessNumericValue(assert, actualBounds.width, expectedBounds.width, boundsEpsilon,
                  "The fully scaled overlay should have adjusted bounds.width");
              Util.assessNumericValue(assert, actualBounds.height, expectedBounds.height, boundsEpsilon,
                  "The fully scaled overlay should have adjusted bounds.height");
              Util.assessNumericValue(assert, actualBounds.degrees, expectedBounds.degrees, boundsEpsilon,
                  "The fully scaled overlay should have adjusted bounds.degrees");
  
              done();
          });
      });
  
      // ----------
      QUnit.test('Fully scaled overlay rotation mode EXACT', function(assert) {
          var done = assert.async();
          viewer = OpenSeadragon({
              id: 'example-overlays',
              prefixUrl: '/build/openseadragon/images/',
              tileSources: '/test/data/testpattern.dzi',
              springStiffness: 100, // Faster animation = faster tests
              degrees: 45,
              overlays: [{
                      id: "fully-scaled-overlay",
                      x: 1,
                      y: 1,
                      width: 1,
                      height: 1,
                      placement: OpenSeadragon.Placement.BOTTOM_RIGHT,
                      rotationMode: OpenSeadragon.OverlayRotationMode.EXACT
                  }]
          });
  
          viewer.addOnceHandler('open', function() {
              var viewport = viewer.viewport;
  
              var $overlay = $("#fully-scaled-overlay");
              var expectedSize = viewport.deltaPixelsFromPointsNoRotate(
                  new OpenSeadragon.Point(1, 1));
              var expectedPosition = viewport.pixelFromPoint(
                  new OpenSeadragon.Point(1, 1))
                  .minus(expectedSize);
              // We can't rely on jQuery.position with transforms.
              var actualStyle = $overlay.get(0).style;
              var left = Number(actualStyle.left.replace("px", ""));
              var top = Number(actualStyle.top.replace("px", ""));
              Util.assessNumericValue(assert, left, expectedPosition.x, epsilon,
                  "Scaled overlay position.x should adjust to rotation.");
              Util.assessNumericValue(assert, top, expectedPosition.y, epsilon,
                  "Scaled overlay position.y should adjust to rotation.");
  
              var actualWidth = $overlay.width();
              var actualHeight = $overlay.height();
              Util.assessNumericValue(assert, actualWidth, expectedSize.x, epsilon,
                  "Scaled overlay width should not adjust to rotation.");
              Util.assessNumericValue(assert, actualHeight, expectedSize.y, epsilon,
                  "Scaled overlay height should not adjust to rotation.");
  
              var transformOriginProp = OpenSeadragon.getCssPropertyWithVendorPrefix(
                  'transformOrigin');
              var transformProp = OpenSeadragon.getCssPropertyWithVendorPrefix(
                  'transform');
              var transformOrigin = actualStyle[transformOriginProp];
              // Some browsers replace "right bottom" by "100% 100%"
              assert.ok(transformOrigin.match(/(100% 100%)|(right bottom)/),
                  "Transform origin should be right bottom. Got: " + transformOrigin);
              assert.equal(actualStyle[transformProp], "rotate(45deg)",
                  "Transform should be rotate(45deg).");
  
              var actualBounds = viewer.getOverlayById("fully-scaled-overlay")
                  .getBounds(viewport);
              var expectedBounds = new OpenSeadragon.Rect(0, 0, 1, 1);
              assert.ok(expectedBounds.equals(actualBounds),
                  "The fully scaled overlay should have bounds " +
                  expectedBounds + " but found " + actualBounds);
  
              done();
          });
      });
  })();
  