<commit_before>
function readAspectRatio(opts) {
  opts = opts || {};
  if (opts.hasOwnProperty('aspectRatio')) {
    return opts.aspectRatio;
  }
  if (!opts.height || !opts.width) {
    throw new Error("Please provide either aspect ratio or container dimensions (width, height)");
  }
  return opts.width / opts.height;
}

function readHotspot(hotspot, imageAspect) {

  if (hotspot.size) {
    hotspot.width = hotspot.size;
    hotspot.height = imageAspect;
    console.log("hotspot.size is deprecated. Should be hotspot.height and hotspot.width")
  }
  return hotspot;
}

function round(v, decimals) {
  var _decimals = typeof(decimals === 'undefined') ? 2 : decimals;
  const multiplier = Math.pow(10, _decimals);
  return Math.round(v*multiplier) / multiplier;
}

function calculateHotSpotCrop(sourceAspect, descriptor, spec) {
  var crop = descriptor.crop;
  var viewportAspect = spec.aspect;
  var alignment = spec.align;

  // The rational aspect of the cropped image
  var netWidth = 1.0 - crop.left - crop.right;
  var netHeight = 1.0 - crop.top - crop.bottom;

  // Places the image inside the crop box
  var outImg = {
    top: -crop.top / netHeight,
    left: -crop.left / netWidth,
    width: 1/netWidth,
    height: 1/netHeight
  };

  // The rational aspect is the aspect ration of the crop in ratios of the image size meaning the image
  // is always considered having the size 1.0*1.0
  var cropRationalAspect = netWidth / netHeight;

  // cropAspect is the real aspect ratio of the crop box in pixel-space
  var cropAspect = cropRationalAspect * sourceAspect;

  // Now we transform the hotspot to be expressed in ratios of the cropped area, not the
  // full image:
  var hotspot = {
    x: (descriptor.hotspot.x-crop.left) / netWidth,
    y: (descriptor.hotspot.y-crop.top) / netHeight,
    height: descriptor.hotspot.height / netHeight,
    width: descriptor.hotspot.width / netWidth
  };

  // Lets calculate the maximum scale the image may be presented at without cropping the hotspot. A scale of
  // 1.0 means the cropped image exactly fill the width of the viewport.

  // The scale at which the hotspot would fill the viewport exactly in the X direction
  var maxHotspotXScale = 1.0/hotspot.width;
  // The scale at which the hotspot would fill the veiwport exactly in the Y direction
  var maxHotspotYScale = (1.0/hotspot.height) * cropAspect / viewportAspect;
  // This is the largest scale the image can have while still not cropping the hotspot:
  var maxScale = Math.min(maxHotspotXScale, maxHotspotYScale);

  // Now lets find the minimum scale we can have while maintaining full bleed (no letterboxing)
  var minFullBleedScale;
  var cropIsTaller = (cropAspect <= viewportAspect);
  if (cropIsTaller) { // Crop is taller than viewport
    minFullBleedScale = 1.0; // By definition 1.0 fills the width of the viewport exactly with the viewport cutting away from the height of the cropbox
  } else { // Image is wider than viewport
    minFullBleedScale = cropAspect / viewportAspect; // At this scale the viewport is filled exactly in the height while cutting away from the sides
  }

  var method;
  var outCrop;

  // Do we have to letterbox this image in order to leave the hotspot area uncropped?
  if (minFullBleedScale > maxScale) {

    // Yes :-( There is no way to protect the hot spot and still have full bleed, so we are letterboxing it
    method = "letterbox";
    var letterboxScale;
    var diff = minFullBleedScale - maxScale;

    // Determine a scale where the image fills one dimension of the container
    if (cropIsTaller) {
      letterboxScale = 1.0 - diff;
    } else {
      letterboxScale = maxScale;
    }

    outCrop = {
      width: letterboxScale,
      height: letterboxScale / cropAspect * viewportAspect
    };

    var hotspotLeft = (hotspot.x * outCrop.width) - (hotspot.width * outCrop.width)/2;
    switch(alignment.x){
      case "left":
        outCrop.left = cropIsTaller ? 0 : -hotspotLeft;
        break;
      case "right":
        // todo: broken atm
        outCrop.left = cropIsTaller ? 1 - outCrop.width : hotspotLeft;
        break;
      case "center":
        outCrop.left = (1 - outCrop.width) / 2;
        break;
    }
    var hotspotTop = (hotspot.y * outCrop.height) - (hotspot.height * outCrop.height)/2;
    switch(alignment.y) {
      case "top":
        outCrop.top = cropIsTaller ? -hotspotTop : 0;
        break;
      case "bottom":
          // todo: broken atm
        outCrop.top = hotspotTop;
        break;
      case "center":
        outCrop.top = (1 - outCrop.height) / 2;
        break;
    }
  }<commit_msg>Fix an edge case causing hotspot area not being in center<commit_after>
function readAspectRatio(opts) {
  opts = opts || {};
  if (opts.hasOwnProperty('aspectRatio')) {
    return opts.aspectRatio;
  }
  if (!opts.height || !opts.width) {
    throw new Error("Please provide either aspect ratio or container dimensions (width, height)");
  }
  return opts.width / opts.height;
}

function readHotspot(hotspot, imageAspect) {

  if (hotspot.size) {
    hotspot.width = hotspot.size;
    hotspot.height = imageAspect;
    console.log("hotspot.size is deprecated. Should be hotspot.height and hotspot.width")
  }
  return hotspot;
}

function round(v, decimals) {
  var _decimals = typeof(decimals === 'undefined') ? 2 : decimals;
  const multiplier = Math.pow(10, _decimals);
  return Math.round(v*multiplier) / multiplier;
}

function calculateHotSpotCrop(sourceAspect, descriptor, spec) {
  var crop = descriptor.crop;
  var viewportAspect = spec.aspect;
  var alignment = spec.align;

  // The rational aspect of the cropped image
  var netWidth = 1.0 - crop.left - crop.right;
  var netHeight = 1.0 - crop.top - crop.bottom;

  // Places the image inside the crop box
  var outImg = {
    top: -crop.top / netHeight,
    left: -crop.left / netWidth,
    width: 1/netWidth,
    height: 1/netHeight
  };

  // The rational aspect is the aspect ration of the crop in ratios of the image size meaning the image
  // is always considered having the size 1.0*1.0
  var cropRationalAspect = netWidth / netHeight;

  // cropAspect is the real aspect ratio of the crop box in pixel-space
  var cropAspect = cropRationalAspect * sourceAspect;

  // Now we transform the hotspot to be expressed in ratios of the cropped area, not the
  // full image:
  var hotspot = {
    x: (descriptor.hotspot.x-crop.left) / netWidth,
    y: (descriptor.hotspot.y-crop.top) / netHeight,
    height: descriptor.hotspot.height / netHeight,
    width: descriptor.hotspot.width / netWidth
  };

  // Lets calculate the maximum scale the image may be presented at without cropping the hotspot. A scale of
  // 1.0 means the cropped image exactly fill the width of the viewport.

  // The scale at which the hotspot would fill the viewport exactly in the X direction
  var maxHotspotXScale = 1.0/hotspot.width;
  // The scale at which the hotspot would fill the veiwport exactly in the Y direction
  var maxHotspotYScale = (1.0/hotspot.height) * cropAspect / viewportAspect;
  // This is the largest scale the image can have while still not cropping the hotspot:
  var maxScale = Math.min(maxHotspotXScale, maxHotspotYScale);

  // Now lets find the minimum scale we can have while maintaining full bleed (no letterboxing)
  var minFullBleedScale;
  var cropIsTaller = (cropAspect <= viewportAspect);
  if (cropIsTaller) { // Crop is taller than viewport
    minFullBleedScale = 1.0; // By definition 1.0 fills the width of the viewport exactly with the viewport cutting away from the height of the cropbox
  } else { // Image is wider than viewport
    minFullBleedScale = cropAspect / viewportAspect; // At this scale the viewport is filled exactly in the height while cutting away from the sides
  }

  var method;
  var outCrop;

  // Do we have to letterbox this image in order to leave the hotspot area uncropped?
  if (minFullBleedScale > maxScale) {

    // Yes :-( There is no way to protect the hot spot and still have full bleed, so we are letterboxing it
    method = "letterbox";
    var letterboxScale;
    var diff = minFullBleedScale - maxScale;

    // Determine a scale where the image fills one dimension of the container
    if (cropIsTaller) {
      letterboxScale = 1.0 - diff;
    } else {
      letterboxScale = maxScale;
    }

    outCrop = {
      width: letterboxScale,
      height: letterboxScale / cropAspect * viewportAspect
    };

    var hotspotLeft = (hotspot.x * outCrop.width) - (hotspot.width * outCrop.width)/2;
    switch(alignment.x){
      case "left":
        outCrop.left = cropIsTaller ? 0 : -hotspotLeft;
        break;
      case "right":
        // todo: broken atm
        outCrop.left = cropIsTaller ? 1 - outCrop.width : hotspotLeft;
        break;
      case "center":
        outCrop.left = cropIsTaller ? -hotspotLeft : (1 - outCrop.width) / 2;
        break;
    }
    var hotspotTop = (hotspot.y * outCrop.height) - (hotspot.height * outCrop.height)/2;
    switch(alignment.y) {
      case "top":
        outCrop.top = cropIsTaller ? -hotspotTop : 0;
        break;
      case "bottom":
          // todo: broken atm
        outCrop.top = hotspotTop;
        break;
      case "center":
        outCrop.top = cropIsTaller ? -hotspotTop : (1 - outCrop.height) / 2;
        break;
    }
  }
{'fraction_non_alphanumeric': 0.09152288072018004, 'fraction_numerical': 0.007287536169756725, 'mean_word_length': 2.88347898460258, 'pattern_counts': {'":': 12, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2549689', 'n_tokens_mistral': 2986, 'n_tokens_neox': 2902, 'n_words': 996}