<commit_before>/**
 * @module ol/source/IIIF
 */

import {DEFAULT_TILE_SIZE} from '../tilegrid/common.js';
import {getTopLeft} from '../extent.js';
import {CustomTile} from './Zoomify.js';
import {Versions} from '../format/IIIFInfo.js';
import {assert} from '../asserts.js';
import TileGrid from '../tilegrid/TileGrid.js';
import TileImage from './TileImage.js';

/**
 * @typedef {Object} Options
 * @property {import("./Source.js").AttributionLike} [attributions] Attributions.
 * @property {boolean} [attributionsCollapsible=true] Attributions are collapsible.
 * @property {number} [cacheSize]
 * @property {null|string} [crossOrigin]
 * @property {import("../proj.js").ProjectionLike} [projection]
 * @property {number} [tilePixelRatio]
 * @property {number} [reprojectionErrorThreshold=0.5]
 * @property {string} [url] Base URL of the IIIF Image service.
 * This shoulf be the same as the IIIF Image @id.
 * @property {import("../size.js").Size} [size] Size of the image [width, height].
 * @property {import("../size.js").Size[]} [sizes] Supported scaled image sizes.
 * Content of the IIIF info.json 'sizes' property, but as array of Size objects.
 * @property {import("../extent.js").Extent} [extent=[0, -height, width, 0]]
 * @property {number} [transition]
 * @property {number|import("../size.js").Size} [tileSize] Tile size.
 * Same tile size is used for all zoom levels. If tile size is a number,
 * a square tile is assumed. If the IIIF image service supports arbitrary
 * tiling (sizeByH, sizeByW or sizeByPct as well as regionByPx and regionByPct
 * are supported), the default tilesize is 256.
 * @property {boolean} [wrapX=false]
 */


/**
 * @classdesc
 * Layer source for tile data in IIIF format.
 * @api
 */
class IIIF extends TileImage {

  constructor(opt_options) {

    const options = opt_options || {};

    let baseUrl = options.url || '';
    baseUrl = baseUrl + (baseUrl.lastIndexOf('/') === baseUrl.length - 1 || baseUrl === '' ? '' : '/');
    const version = options.version || Versions.VERSION2;
    const sizes = options.sizes || [];
    const size = options.size;
    // TODO Appropriate error code
    assert(size != undefined && Array.isArray(size) && size.length == 2 &&
      !isNaN(size[0]) && size[0] > 0 && !isNaN(size[1] && size[1] > 0), 999);
    const width = size[0];
    const height = size[1];
    const tileSize = options.tileSize;
    const format = options.format || 'jpg';
    const quality = options.quality || (options.version == Versions.VERSION1 ? 'native' : 'default');
    let resolutions = options.resolutions || [];
    const features = options.features || [];
    const extent = options.extent || [0, -height, width, 0];

    const supportsListedSizes = sizes != undefined && Array.isArray(sizes) && sizes.length > 0;
    const supportsListedTiles = tileSize != undefined && (Number.isInteger(tileSize) && tileSize > 0 || Array.isArray(tileSize) && tileSize.length > 0);
    const supportsArbitraryTiling = features != undefined && Array.isArray(features) &&
      (features.includes('regionByPx') || features.includes('regionByPct')) &&
      (features.includes('sizeByWh') || features.includes('sizeByH') ||
      features.includes('sizeByW') || features.includes('sizeByPct'));

    let tileWidth,
        tileHeight,
        maxZoom;

    resolutions.sort(function(a, b) {
      return b - a;
    });

    if (supportsListedTiles || supportsArbitraryTiling) {
      if (tileSize != undefined) {
        if (Number.isInteger(tileSize) && tileSize > 0) {
          tileWidth = tileSize;
          tileHeight = tileSize;
        } else if (Array.isArray(tileSize) && tileSize.length > 0) {
          if (tileSize.length == 1 || tileSize[1] == undefined && Number.isInteger(tileSize[0])) {
            tileWidth = tileSize[0];
            tileHeight = tileSize[0];
          }
          if (tileSize.length == 2) {
            if (Number.isInteger(tileSize[0]) && Number.isInteger(tileSize[1])) {
              tileWidth = tileSize[0];
              tileHeight = tileSize[1];
            } else if (tileSize[0] == undefined && Number.isInteger(tileSize[1])) {
              tileWidth = tileSize[1];
              tileHeight = tileSize[1];
            }
          }
        }
      }
      if (tileWidth === undefined || tileHeight === undefined) {
        tileWidth = DEFAULT_TILE_SIZE;
        tileHeight = DEFAULT_TILE_SIZE;
      }
      if (resolutions.length == 0) {
        maxZoom = Math.max(
          Math.ceil(Math.log(width / tileWidth) / Math.LN2),
          Math.ceil(Math.log(height / tileHeight) / Math.LN2)
        );
        for (let i = maxZoom; i >= 0; i--) {
          resolutions.push(Math.pow(2, i));
        }
      } else {
        const maxScaleFactor = Math.max([...resolutions]);
        // TODO maxScaleFactor might not be a power to 2
        maxZoom = Math.round(Math.log(maxScaleFactor) / Math.LN2);
      }
    } else {
      // No tile support.
      tileWidth = width;
      tileHeight = height;
      resolutions = [];
      if (supportsListedSizes) {
        /*
         * 'sizes' provided. Use full region in different resolutions. Every
         * resolution has only one tile.
         */
        sizes.sort(function(a, b) {
          return a[0] - b[0];
        });
        for (let i = 0; i < sizes.length; i++) {
          const resolution = width / sizes[i][0];
          resolutions.push(resolution);
          maxZoom = i;
        }
      } else {
        // No useful image information at all. Try pseudo tile with full image.
        resolutions.push(1);
        sizes.push([width, height]);
        maxZoom = 0;
      }
    }

    const tileGrid = new TileGrid({
      tileSize: [tileWidth, tileHeight],
      extent: extent,
      origin: getTopLeft(extent),
      resolutions: resolutions
    });

    const tileUrlFunction = function(tileCoord, pixelRatio, projection) {
      let regionParam,
          sizeParam;
      const zoom = tileCoord[0];
      if (maxZoom < zoom) {
        return;
      }
      const tileX = tileCoord[1],
          tileY = tileCoord[2],
          scale = resolutions[zoom];
      if (tileX < 0 || Math.ceil(width / scale / tileWidth) <= tileX ||
      tileY < 0 || Math.ceil(height / scale / tileHeight) <= tileY) {
        return;
      }
      if (supportsArbitraryTiling || supportsListedTiles) {
        const regionX = tileX * tileWidth * scale,
            regionY = tileY * tileHeight * scale;
        let regionW = tileWidth * scale,
            regionH = tileHeight * scale,
            sizeW = tileWidth,
            sizeH = tileHeight;
        if (regionX + regionW > width) {
          regionW = width - regionX;
        }
        if (regionY + regionH > height) {
          regionH = height - regionY;
        }
        if (regionX + tileWidth * scale > width) {
          sizeW = Math.floor((width - regionX + scale - 1) / scale);
        }
        if (regionY + tileHeight * scale > height) {
          sizeH = Math.floor((height - regionY + scale - 1) / scale);
        }
        const sizeHBySizeW = Math.round(sizeW / regionW * regionH),
            sizeWBySizeH = Math.round(sizeH / regionH * regionW),
            preferSizeByH = (sizeHBySizeW > sizeH) && (sizeW == sizeWBySizeH);
        if (regionX == 0 && regionW == width && regionY == 0 && regionH == height) {
          // canonical full image region parameter is 'full', not 'x,y,w,h'
          regionParam = 'full';
        } else if (!supportsArbitraryTiling || features.includes('regionByPx')) {
          regionParam = regionX + ',' + regionY + ',' + regionW + ',' + regionH;
        } else if (features.includes('regionByPct')) {
          const pctX = regionX / width * 100,
              pctY = regionY / height * 100,
              pctW = regionW / width * 100,
              pctH = regionH / height * 100;
          regionParam = 'pct:' + pctX + ',' + pctY + ',' + pctW + ',' + pctH;
        }
        if (version == Versions.VERSION3 && (!supportsArbitraryTiling || features.includes('sizeByWh'))) {
          sizeParam = sizeW + ',' + sizeH;
        } else if (!supportsArbitraryTiling || features.includes('sizeByW') && (!preferSizeByH || !(features.includes('sizeByH')))) {
          sizeParam = sizeW + ',';
        } else if (features.includes('sizeByH')) {
          sizeParam = ',' + sizeH;
        } else if (features.includes('sizeByWh')) {
          sizeParam = sizeW + ',' + sizeH;
        } else if (features.includes('sizeByPct')) {
          sizeParam = 'pct:' + (100 / scale);
        }
      } else {
        regionParam = 'full';
        if (supportsListedSizes) {
          sizeParam = sizes[zoom][0] + ',' + (version == Versions.VERSION3 ? sizes[zoom][0] : '');
        } else {
          sizeParam = version == Versions.VERSION3 ? 'max' : 'full';
        }
      }
      return baseUrl + regionParam + '/' + sizeParam + '/0/' + quality + '.' + format;
    };

    const IiifTileClass = CustomTile.bind(null, tileGrid);

    super({
      attributions: options.attributions,
      attributionsCollapsible: options.attributionsCollapsible,
      cacheSize: options.cacheSize,
      crossOrigin: options.crossOrigin,
      opaque: options.opaque,
      projection: options.projection,
      reprojectionErrorThreshold: options.reprojectionErrorThreshold,
      state: options.state,<commit_msg>Fix error in IIIF v3 level0 size paramter<commit_after>/**
 * @module ol/source/IIIF
 */

import {DEFAULT_TILE_SIZE} from '../tilegrid/common.js';
import {getTopLeft} from '../extent.js';
import {CustomTile} from './Zoomify.js';
import {Versions} from '../format/IIIFInfo.js';
import {assert} from '../asserts.js';
import TileGrid from '../tilegrid/TileGrid.js';
import TileImage from './TileImage.js';

/**
 * @typedef {Object} Options
 * @property {import("./Source.js").AttributionLike} [attributions] Attributions.
 * @property {boolean} [attributionsCollapsible=true] Attributions are collapsible.
 * @property {number} [cacheSize]
 * @property {null|string} [crossOrigin]
 * @property {import("../proj.js").ProjectionLike} [projection]
 * @property {number} [tilePixelRatio]
 * @property {number} [reprojectionErrorThreshold=0.5]
 * @property {string} [url] Base URL of the IIIF Image service.
 * This shoulf be the same as the IIIF Image @id.
 * @property {import("../size.js").Size} [size] Size of the image [width, height].
 * @property {import("../size.js").Size[]} [sizes] Supported scaled image sizes.
 * Content of the IIIF info.json 'sizes' property, but as array of Size objects.
 * @property {import("../extent.js").Extent} [extent=[0, -height, width, 0]]
 * @property {number} [transition]
 * @property {number|import("../size.js").Size} [tileSize] Tile size.
 * Same tile size is used for all zoom levels. If tile size is a number,
 * a square tile is assumed. If the IIIF image service supports arbitrary
 * tiling (sizeByH, sizeByW or sizeByPct as well as regionByPx and regionByPct
 * are supported), the default tilesize is 256.
 * @property {boolean} [wrapX=false]
 */


/**
 * @classdesc
 * Layer source for tile data in IIIF format.
 * @api
 */
class IIIF extends TileImage {

  constructor(opt_options) {

    const options = opt_options || {};

    let baseUrl = options.url || '';
    baseUrl = baseUrl + (baseUrl.lastIndexOf('/') === baseUrl.length - 1 || baseUrl === '' ? '' : '/');
    const version = options.version || Versions.VERSION2;
    const sizes = options.sizes || [];
    const size = options.size;
    // TODO Appropriate error code
    assert(size != undefined && Array.isArray(size) && size.length == 2 &&
      !isNaN(size[0]) && size[0] > 0 && !isNaN(size[1] && size[1] > 0), 999);
    const width = size[0];
    const height = size[1];
    const tileSize = options.tileSize;
    const format = options.format || 'jpg';
    const quality = options.quality || (options.version == Versions.VERSION1 ? 'native' : 'default');
    let resolutions = options.resolutions || [];
    const features = options.features || [];
    const extent = options.extent || [0, -height, width, 0];

    const supportsListedSizes = sizes != undefined && Array.isArray(sizes) && sizes.length > 0;
    const supportsListedTiles = tileSize != undefined && (Number.isInteger(tileSize) && tileSize > 0 || Array.isArray(tileSize) && tileSize.length > 0);
    const supportsArbitraryTiling = features != undefined && Array.isArray(features) &&
      (features.includes('regionByPx') || features.includes('regionByPct')) &&
      (features.includes('sizeByWh') || features.includes('sizeByH') ||
      features.includes('sizeByW') || features.includes('sizeByPct'));

    let tileWidth,
        tileHeight,
        maxZoom;

    resolutions.sort(function(a, b) {
      return b - a;
    });

    if (supportsListedTiles || supportsArbitraryTiling) {
      if (tileSize != undefined) {
        if (Number.isInteger(tileSize) && tileSize > 0) {
          tileWidth = tileSize;
          tileHeight = tileSize;
        } else if (Array.isArray(tileSize) && tileSize.length > 0) {
          if (tileSize.length == 1 || tileSize[1] == undefined && Number.isInteger(tileSize[0])) {
            tileWidth = tileSize[0];
            tileHeight = tileSize[0];
          }
          if (tileSize.length == 2) {
            if (Number.isInteger(tileSize[0]) && Number.isInteger(tileSize[1])) {
              tileWidth = tileSize[0];
              tileHeight = tileSize[1];
            } else if (tileSize[0] == undefined && Number.isInteger(tileSize[1])) {
              tileWidth = tileSize[1];
              tileHeight = tileSize[1];
            }
          }
        }
      }
      if (tileWidth === undefined || tileHeight === undefined) {
        tileWidth = DEFAULT_TILE_SIZE;
        tileHeight = DEFAULT_TILE_SIZE;
      }
      if (resolutions.length == 0) {
        maxZoom = Math.max(
          Math.ceil(Math.log(width / tileWidth) / Math.LN2),
          Math.ceil(Math.log(height / tileHeight) / Math.LN2)
        );
        for (let i = maxZoom; i >= 0; i--) {
          resolutions.push(Math.pow(2, i));
        }
      } else {
        const maxScaleFactor = Math.max([...resolutions]);
        // TODO maxScaleFactor might not be a power to 2
        maxZoom = Math.round(Math.log(maxScaleFactor) / Math.LN2);
      }
    } else {
      // No tile support.
      tileWidth = width;
      tileHeight = height;
      resolutions = [];
      if (supportsListedSizes) {
        /*
         * 'sizes' provided. Use full region in different resolutions. Every
         * resolution has only one tile.
         */
        sizes.sort(function(a, b) {
          return a[0] - b[0];
        });
        for (let i = 0; i < sizes.length; i++) {
          const resolution = width / sizes[i][0];
          resolutions.push(resolution);
          maxZoom = i;
        }
      } else {
        // No useful image information at all. Try pseudo tile with full image.
        resolutions.push(1);
        sizes.push([width, height]);
        maxZoom = 0;
      }
    }

    const tileGrid = new TileGrid({
      tileSize: [tileWidth, tileHeight],
      extent: extent,
      origin: getTopLeft(extent),
      resolutions: resolutions
    });

    const tileUrlFunction = function(tileCoord, pixelRatio, projection) {
      let regionParam,
          sizeParam;
      const zoom = tileCoord[0];
      if (maxZoom < zoom) {
        return;
      }
      const tileX = tileCoord[1],
          tileY = tileCoord[2],
          scale = resolutions[zoom];
      if (tileX < 0 || Math.ceil(width / scale / tileWidth) <= tileX ||
      tileY < 0 || Math.ceil(height / scale / tileHeight) <= tileY) {
        return;
      }
      if (supportsArbitraryTiling || supportsListedTiles) {
        const regionX = tileX * tileWidth * scale,
            regionY = tileY * tileHeight * scale;
        let regionW = tileWidth * scale,
            regionH = tileHeight * scale,
            sizeW = tileWidth,
            sizeH = tileHeight;
        if (regionX + regionW > width) {
          regionW = width - regionX;
        }
        if (regionY + regionH > height) {
          regionH = height - regionY;
        }
        if (regionX + tileWidth * scale > width) {
          sizeW = Math.floor((width - regionX + scale - 1) / scale);
        }
        if (regionY + tileHeight * scale > height) {
          sizeH = Math.floor((height - regionY + scale - 1) / scale);
        }
        const sizeHBySizeW = Math.round(sizeW / regionW * regionH),
            sizeWBySizeH = Math.round(sizeH / regionH * regionW),
            preferSizeByH = (sizeHBySizeW > sizeH) && (sizeW == sizeWBySizeH);
        if (regionX == 0 && regionW == width && regionY == 0 && regionH == height) {
          // canonical full image region parameter is 'full', not 'x,y,w,h'
          regionParam = 'full';
        } else if (!supportsArbitraryTiling || features.includes('regionByPx')) {
          regionParam = regionX + ',' + regionY + ',' + regionW + ',' + regionH;
        } else if (features.includes('regionByPct')) {
          const pctX = regionX / width * 100,
              pctY = regionY / height * 100,
              pctW = regionW / width * 100,
              pctH = regionH / height * 100;
          regionParam = 'pct:' + pctX + ',' + pctY + ',' + pctW + ',' + pctH;
        }
        if (version == Versions.VERSION3 && (!supportsArbitraryTiling || features.includes('sizeByWh'))) {
          sizeParam = sizeW + ',' + sizeH;
        } else if (!supportsArbitraryTiling || features.includes('sizeByW') && (!preferSizeByH || !(features.includes('sizeByH')))) {
          sizeParam = sizeW + ',';
        } else if (features.includes('sizeByH')) {
          sizeParam = ',' + sizeH;
        } else if (features.includes('sizeByWh')) {
          sizeParam = sizeW + ',' + sizeH;
        } else if (features.includes('sizeByPct')) {
          sizeParam = 'pct:' + (100 / scale);
        }
      } else {
        regionParam = 'full';
        if (supportsListedSizes) {
          sizeParam = sizes[zoom][0] + ',' + (version == Versions.VERSION3 ? sizes[zoom][1] : '');
        } else {
          sizeParam = version == Versions.VERSION3 ? 'max' : 'full';
        }
      }
      return baseUrl + regionParam + '/' + sizeParam + '/0/' + quality + '.' + format;
    };

    const IiifTileClass = CustomTile.bind(null, tileGrid);

    super({
      attributions: options.attributions,
      attributionsCollapsible: options.attributionsCollapsible,
      cacheSize: options.cacheSize,
      crossOrigin: options.crossOrigin,
      opaque: options.opaque,
      projection: options.projection,
      reprojectionErrorThreshold: options.reprojectionErrorThreshold,
      state: options.state,
{'fraction_non_alphanumeric': 0.14227685729556386, 'fraction_numerical': 0.009299839657936931, 'mean_word_length': 2.431951577402788, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 29, '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': '731434', 'n_tokens_mistral': 5732, 'n_tokens_neox': 5611, 'n_words': 1412}