# @allmaps/iiif-parser

This is a JavaScript module that parser IIIF [Collections](https://iiif.io/api/presentation/3.0/#51-collection), [Manifests](https://iiif.io/api/presentation/3.0/#52-manifest) and [Images](https://iiif.io/api/image/3.0/#5-image-information). This is a core module of [Allmaps](https://allmaps.org/) and is used in all its apps and components.

*Note: this module parses IIIF data to an intermediate format that is used by Allmaps internally. It does not parse **all** properties defined by the IIIF Image and Presentation APIs, only properties that are needed for Allmaps to function. See the files in the [`src/classes`](src/classes) and [`src/schemas`](src/schemas) directories for details about these proeprties.*

@allmaps/iiif-parser is written in TypeScript and is built using [Zod](https://zod.dev/).

Versions 1, 2 and 3 of the IIIF Image and Presentation APIs are supported.

This module has been tested on a wide variety of IIIF servers. Run `npm test` to run all tests.

## Installation

This is an ESM-only module that works in browsers and Node.js.

Node.js:

First, run `npm install @allmaps/iiif-parser` to add this module to your project.

```js
import { IIIF } from '@allmaps/iiif-parser'
```

Browser:

```html
<script type="module">
  import { IIIF } from 'https://unpkg.com/@allmaps/iiif-parser?module'
</script>
```

## Usage

```js
import { IIIF } from '@allmaps/iiif-parser'

const manifestUrl =
  'https://collections.leventhalmap.org/search/commonwealth:wd376720z/manifest'
const manifest = await fetch(manifestUrl).then((response) => response.json())
const parsedManifest = IIIF.parse(manifest)
console.log(parsedManifest)
```

This will log `parsedManifest` to the console:

```json
{
  "embedded": false,
  "type": "manifest",
  "uri": "https://ark.digitalcommonwealth.org/ark:/50959/wd376720z/manifest",
  "majorVersion": 2,
  "label": {
    "none": [
      "Map of Boston and vicinity showing tracks operated by the Boston Elevated Railway Co., surface lines"
    ]
  },
  "canvases": [
    {
      "type": "canvas",
      "width": 7486,
      "height": 9138,
      "uri": "https://ark.digitalcommonwealth.org/ark:/50959/wd376720z/canvas/wd3767217",
      "label": {
        "none": ["image 1"]
      },
      "image": {
        "embedded": true,
        "type": "image",
        "uri": "https://iiif.digitalcommonwealth.org/iiif/2/commonwealth:wd3767217",
        "majorVersion": 2,
        "supportsAnyRegionAndSize": true,
        "width": 7486,
        "height": 9138
      }
    }
  ],
  "metadata": [
    {
      "label": {
        "none": ["Title"]
      },
      "value": {
        "none": [
          "Map of Boston and vicinity showing tracks operated by the Boston Elevated Railway Co., surface lines"
        ]
      }
    },
    {
      "label": {
        "none": ["Date"]
      },
      "value": {
        "none": ["1898"]
      }
    },
    {
      "label": {
        "none": ["Publisher"]
      },
      "value": {
        "none": ["Boston, Mass : [Geo. H. Walker & Co.]"]
      }
    },
    {
      "label": {
        "none": ["Type of Resource"]
      },
      "value": {
        "none": ["Cartographic"]
      }
    },
    {
      "label": {
        "none": ["Format"]
      },
      "value": {
        "none": ["Maps"]
      }
    },
    {
      "label": {
        "none": ["Language"]
      },
      "value": {
        "none": ["English"]
      }
    },
    {
      "label": {
        "none": ["Subjects"]
      },
      "value": {
        "none": [
          "Boston Elevated Railway Company",
          "Street-railroads--Massachusetts--Boston--Maps",
          "Boston (Mass.)--Maps",
          "Massachusetts--Maps"
        ]
      }
    },
    {
      "label": {
        "none": ["Location"]
      },
      "value": {
        "none": ["Boston Public Library"]
      }
    },
    {
      "label": {
        "none": ["Collection (local)"]
      },
      "value": {
        "none": ["Norman B. Leventhal Map Center Collection"]
      }
    },
    {
      "label": {
        "none": ["Identifier"]
      },
      "value": {
        "none": [
          "https://ark.digitalcommonwealth.org/ark:/50959/wd376720z",
          "06_01_003041",
          "G3764.B6P33 1898 .M3",
          "39999058997337"
        ]
      }
    },
    {
      "label": {
        "none": ["Terms of Use"]
      },
      "value": {
        "none": [
          "No known copyright restrictions.",
          "No known restrictions on use."
        ]
      }
    }
  ]
}
```

You can also call the `parse` function on a specific IIIF class:

```js
import { Image } from '@allmaps/iiif-parser'

const imageUrl =
  'https://iiif.digitalcommonwealth.org/iiif/2/commonwealth:wd3767217'
const image = await fetch(`${imageUrl}/info.json`).then((response) =>
  response.json()
)
const parsedImage = Image.parse(image)
console.log(parsedImage)
```

This will log `parsedImage` to the console:

```json
{
  "embedded": false,
  "type": "image",
  "uri": "https://iiif.digitalcommonwealth.org/iiif/2/commonwealth:wd3767217",
  "majorVersion": 2,
  "supportsAnyRegionAndSize": true,
  "width": 7486,
  "height": 9138,
  "tileZoomLevels": [
    {
      "scaleFactor": 1,
      "width": 1024,
      "height": 1024,
      "originalWidth": 1024,
      "originalHeight": 1024,
      "columns": 8,
      "rows": 9
    },
    {
      "scaleFactor": 2,
      "width": 1024,
      "height": 1024,
      "originalWidth": 2048,
      "originalHeight": 2048,
      "columns": 4,
      "rows": 5
    },
    {
      "scaleFactor": 4,
      "width": 1024,
      "height": 1024,
      "originalWidth": 4096,
      "originalHeight": 4096,
      "columns": 2,
      "rows": 3
    },
    {
      "scaleFactor": 8,
      "width": 1024,
      "height": 1024,
      "originalWidth": 8192,
      "originalHeight": 8192,
      "columns": 1,
      "rows": 2
    },
    {
      "scaleFactor": 16,
      "width": 1024,
      "height": 1024,
      "originalWidth": 16384,
      "originalHeight": 16384,
      "columns": 1,
      "rows": 1
    },
    {
      "scaleFactor": 32,
      "width": 1024,
      "height": 1024,
      "originalWidth": 32768,
      "originalHeight": 32768,
      "columns": 1,
      "rows": 1
    },
    {
      "scaleFactor": 64,
      "width": 1024,
      "height": 1024,
      "originalWidth": 65536,
      "originalHeight": 65536,
      "columns": 1,
      "rows": 1
    }
  ],
  "sizes": [
    {
      "width": 117,
      "height": 143
    },
    {
      "width": 234,
      "height": 286
    },
    {
      "width": 468,
      "height": 571
    },
    {
      "width": 936,
      "height": 1142
    },
    {
      "width": 1872,
      "height": 2285
    },
    {
      "width": 3743,
      "height": 4569
    },
    {
      "width": 7486,
      "height": 9138
    }
  ]
}
```

You can check if a parsed IIIF resource object is of a specific class by using the `instanceof` operator or checking the `type` property:

```ts
import { IIIF } from '@allmaps/iiif-parser'

const url =
  'https://collections.leventhalmap.org/search/commonwealth:wd376720z/manifest'
const iiif = await fetch(url).then((response) => response.json())
const parsedIiif = IIIF.parse(manifest)

if (parsedIiif.type === 'manifest') {
  console.log('This is a IIIF Manifest!')
}
```

## CLI

Parsing IIIF resources is also possible using the [Allmaps CLI](https://github.com/allmaps/allmaps/tree/main/apps/cli).

For example:

```sh
curl https://collections.leventhalmap.org/search/commonwealth:wd376720z/manifest | allmaps iiif parse
```

## License

MIT

## API

### `new Canvas(parsedCanvas)`

###### Parameters

* `parsedCanvas` (`{ '@id': string; width: number; height: number; '@type': "sc:Canvas"; images: Array<{ resource: { service: { '@id': string; profile: string | ValidImage2ProfileArray; '@context'?: string | undefined; width?: number | undefined; height?: number | undefined; '@type'?: "ImageService2" | ... 2 more ... | undefined; } | ...`)

###### Returns

`Canvas`.

### `Canvas#annotations?`

###### Type

```ts
Array<{id: string; type: 'AnnotationPage'}>
```

### `Canvas#description?`

###### Type

```ts
{[language: string]: Array<string | number | boolean>}
```

### `Canvas#height`

###### Type

```ts
number
```

### `Canvas#homepage?`

###### Type

```ts
Array<{
  id: string
  type?: string
  label?: LanguageString
  format?: string
  language?: string | string[]
}>
```

### `Canvas#image`

###### Type

```ts
Image | EmbeddedImage
```

### `Canvas#label?`

###### Type

```ts
{[language: string]: Array<string | number | boolean>}
```

### `Canvas#metadata?`

###### Type

```ts
Array<MetadataItem>
```

### `Canvas#navDate?`

###### Type

```ts
Date
```

### `Canvas#navPlace?`

###### Type

```ts
object
```

### `Canvas#rendering?`

###### Type

```ts
Array<{
  id: string
  type?: string
  label?: LanguageString
  format?: string
}>
```

### `Canvas#requiredStatement?`

###### Type

```ts
{label: LanguageString; value: LanguageString}
```

### `Canvas#seeAlso?`

###### Type

```ts
Array<{id: string; type?: string; format?: string; profile?: string}>
```

### `Canvas#summary?`

###### Type

```ts
{[language: string]: Array<string | number | boolean>}
```

### `Canvas#thumbnail?`

###### Type

```ts
Array<{
  id: string
  type?: string
  format?: string
  width?: number
  height?: number
}>
```

### `Canvas#type`

###### Type

```ts
CanvasTypeString
```

### `Canvas#uri`

###### Type

```ts
string
```

### `Canvas#width`

###### Type

```ts
number
```

### `new Collection(parsedCollection, options)`

###### Parameters

* `parsedCollection` (`Collection2 | Collection3`)
* `options?` (`Partial<ConstructorOptions> | undefined`)

###### Returns

`Collection`.

###### Extends

* `EmbeddedCollection`

### `Collection#annotations?`

###### Type

```ts
Array<{id: string; type: 'AnnotationPage'}>
```

### `Collection#canvases`

###### Type

```ts
Array<Canvas>
```

### `Collection#embedded`

###### Type

```ts
false
```

### `Collection#fetchAllItems(options)`

###### Parameters

* `options?` (`Partial<FetchNextItemOptions> | undefined`)

###### Returns

`Promise<Array<FetchNextItemResults<Image | Manifest | Collection>>>`.

### `Collection#fetchItemWithId(id, fetchFn)`

###### Parameters

* `id` (`string`)
* `fetchFn` (`  | ((input: RequestInfo | URL, init?: RequestInit) => Promise<Response>)
    | undefined`)

###### Returns

`Promise<EmbeddedManifest | EmbeddedCollection | undefined>`.

### `Collection#fetchItemWithIndex(index, fetchFn)`

###### Parameters

* `index` (`number`)
* `fetchFn` (`  | ((input: RequestInfo | URL, init?: RequestInit) => Promise<Response>)
    | undefined`)

###### Returns

`Promise<Manifest | EmbeddedManifest | Collection | EmbeddedCollection>`.

### `Collection#fetchNextItem(options, depth)`

###### Parameters

* `options?` (`Partial<FetchNextItemOptions> | undefined`)
* `depth` (`number | undefined`)

###### Returns

`AsyncGenerator<
  FetchNextItemResults<Image | Manifest | Collection>,
  void,
  void >`.

### `Collection#fetchUntilPath(path)`

###### Parameters

* `path` (`Array<number>`)

###### Returns

`Promise<void>`.

### `Collection#getItemAtPath(path)`

###### Parameters

* `path` (`Array<number>`)

###### Returns

`EmbeddedManifest | EmbeddedCollection | Canvas | undefined`.

### `Collection#homepage?`

###### Type

```ts
Array<{
  id: string
  type?: string
  label?: LanguageString
  format?: string
  language?: string | string[]
}>
```

### `Collection#images`

###### Type

```ts
Array<Image | EmbeddedImage>
```

### `Collection#items`

###### Type

```ts
Array<never>
```

### `Collection#rendering?`

###### Type

```ts
Array<{
  id: string
  type?: string
  label?: LanguageString
  format?: string
}>
```

### `Collection#requiredStatement?`

###### Type

```ts
{label: LanguageString; value: LanguageString}
```

### `Collection#seeAlso?`

###### Type

```ts
Array<{id: string; type?: string; format?: string; profile?: string}>
```

### `Collection#source?`

###### Type

```ts
unknown
```

### `Collection#summary?`

###### Type

```ts
{[language: string]: Array<string | number | boolean>}
```

### `Collection.parse(iiifCollection, options)`

Parses a IIIF Collection and returns a [Collection](#collection) containing the parsed version

###### Parameters

* `iiifCollection` (`unknown`)
  * Source data of IIIF Collection
* `options?` (`Partial<ParseOptions> | undefined`)

###### Returns

Parsed IIIF Collection (`Collection`).

### `new EmbeddedCollection(parsedCollection)`

###### Parameters

* `parsedCollection` (`Collection2 | Collection3 | EmbeddedCollectionType`)

###### Returns

`EmbeddedCollection`.

### `EmbeddedCollection#description?`

###### Type

```ts
{[language: string]: Array<string | number | boolean>}
```

### `EmbeddedCollection#embedded`

###### Type

```ts
true
```

### `EmbeddedCollection#label?`

###### Type

```ts
{[language: string]: Array<string | number | boolean>}
```

### `EmbeddedCollection#majorVersion`

###### Type

```ts
1 | 2 | 3
```

### `EmbeddedCollection#metadata?`

###### Type

```ts
Array<MetadataItem>
```

### `EmbeddedCollection#navDate?`

###### Type

```ts
Date
```

### `EmbeddedCollection#navPlace?`

###### Type

```ts
object
```

### `EmbeddedCollection#thumbnail?`

###### Type

```ts
Array<{
  id: string
  type?: string
  format?: string
  width?: number
  height?: number
}>
```

### `EmbeddedCollection#type`

###### Type

```ts
CollectionTypeString
```

### `EmbeddedCollection#uri`

###### Type

```ts
string
```

### `EmbeddedCollection.parse(iiifCollection, options)`

Parses a IIIF Collection and returns a [Collection](#collection) containing the parsed version

###### Parameters

* `iiifCollection` (`unknown`)
  * Source data of IIIF Collection
* `options?` (`Partial<ParseOptions> | undefined`)

###### Returns

Parsed IIIF Collection (`Collection`).

### `new EmbeddedImage(parsedImage, options)`

###### Parameters

* `parsedImage` (`{ '@context': "http://library.stanford.edu/iiif/image-api/1.1/context.json"; '@id': string; width: number; height: number; profile?: string | undefined; scale_factors?: Array<number> | undefined; tile_width?: number | undefined; tile_height?: number | undefined; } | { ...; } | { ...; } | EmbeddedImageType`)
* `options?` (`Partial<ImageConstructorOptions> | undefined`)

###### Returns

`EmbeddedImage`.

### `EmbeddedImage#embedded`

###### Type

```ts
true
```

### `EmbeddedImage#getImageRequest(size, mode)`

###### Parameters

* `size` (`{width: number; height: number}`)
* `mode` (`Fit | undefined`)

###### Returns

`ImageRequest | Array<Array<ImageRequest>>`.

### `EmbeddedImage#getImageUrl(imageRequest)`

Generates a IIIF Image API URL for the requested region and size

###### Parameters

* `imageRequest` (`{region?: Region; size?: SizeObject}`)
  * Image request object containing the desired region and size of the requested image

###### Returns

Image API URL that can be used to fetch the requested image (`string`).

### `EmbeddedImage#height`

###### Type

```ts
number
```

### `EmbeddedImage#majorVersion`

###### Type

```ts
1 | 2 | 3
```

### `EmbeddedImage#maxArea`

###### Type

```ts
number | undefined
```

### `EmbeddedImage#maxHeight`

###### Type

```ts
number | undefined
```

### `EmbeddedImage#maxWidth`

###### Type

```ts
number | undefined
```

### `EmbeddedImage#supportsAnyRegionAndSize`

###### Type

```ts
boolean
```

### `EmbeddedImage#type`

###### Type

```ts
ImageTypeString
```

### `EmbeddedImage#uri`

###### Type

```ts
string
```

### `EmbeddedImage#width`

###### Type

```ts
number
```

### `new EmbeddedManifest(parsedManifest)`

###### Parameters

* `parsedManifest` (`{ '@id': string; '@type': "sc:Manifest"; sequences: Array<{ canvases: [{ '@id': string; width: number; height: number; '@type': "sc:Canvas"; images: Array<{ resource: { service: { '@id': string; profile: string | ValidImage2ProfileArray; '@context'?: string | undefined; width?: number | undefined; height?: number | ...`)

###### Returns

`EmbeddedManifest`.

### `EmbeddedManifest#description?`

###### Type

```ts
{[language: string]: Array<string | number | boolean>}
```

### `EmbeddedManifest#embedded`

###### Type

```ts
true
```

### `EmbeddedManifest#label?`

###### Type

```ts
{[language: string]: Array<string | number | boolean>}
```

### `EmbeddedManifest#majorVersion`

###### Type

```ts
1 | 2 | 3
```

### `EmbeddedManifest#metadata?`

###### Type

```ts
Array<MetadataItem>
```

### `EmbeddedManifest#navDate?`

###### Type

```ts
Date
```

### `EmbeddedManifest#navPlace?`

###### Type

```ts
object
```

### `EmbeddedManifest#thumbnail?`

###### Type

```ts
Array<{
  id: string
  type?: string
  format?: string
  width?: number
  height?: number
}>
```

### `EmbeddedManifest#type`

###### Type

```ts
ManifestTypeString
```

### `EmbeddedManifest#uri`

###### Type

```ts
string
```

### `new IIIF()`

Base class that contains a static parse function for IIIF resources

###### Parameters

There are no parameters.

###### Returns

`IIIF`.

### `IIIF.parse(iiifResource, options)`

Parses as IIIF resource and returns a class containing the parsed version

###### Parameters

* `iiifResource` (`unknown`)
  * Source data of a IIIF resource
* `options?` (`Partial<ParseOptions> | undefined`)

###### Returns

Parsed IIIF resource (`Image | Manifest | Collection`).

### `new Image(parsedImage, options)`

###### Parameters

* `parsedImage` (`{ '@context': "http://library.stanford.edu/iiif/image-api/1.1/context.json"; '@id': string; width: number; height: number; profile?: string | undefined; scale_factors?: Array<number> | undefined; tile_width?: number | undefined; tile_height?: number | undefined; } | { ...; } | { ...; }`)
* `options?` (`Partial<ConstructorOptions> | undefined`)

###### Returns

`Image`.

###### Extends

* `EmbeddedImage`

### `Image#embedded`

###### Type

```ts
false
```

### `Image#getImageRequest(size, mode)`

Returns a Image request object for the requested region and size

###### Parameters

* `size` (`{width: number; height: number}`)
  * Size of the requested thumbnail
* `mode` (`Fit | undefined`)
  * Desired fit mode of the requested thumbnail

###### Returns

Image request object that can be used to fetch the requested thumbnail (`ImageRequest | Array<Array<ImageRequest>>`).

### `Image#getTileImageRequest(zoomLevel, column, row)`

Returns a Image request object for a tile with the requested zoom level, column, and row

###### Parameters

* `zoomLevel` (`{
    scaleFactor: number
    width: number
    height: number
    originalWidth: number
    originalHeight: number
    columns: number
    rows: number
  }`)
  * Desired zoom level of the requested tile
* `column` (`number`)
  * Column of the requested tile
* `row` (`number`)
  * Row of the requested tile

###### Returns

Image request object that can be used to fetch the requested tile (`{region?: Region; size?: SizeObject}`).

### `Image#sizes?`

###### Type

```ts
Array<SizeObject>
```

### `Image#source?`

###### Type

```ts
unknown
```

### `Image#tileZoomLevels`

###### Type

```ts
Array<TileZoomLevel>
```

### `Image.parse(iiifImage, parseOptions)`

Parses a IIIF image and returns a [Image](#image) containing the parsed version

###### Parameters

* `iiifImage` (`unknown`)
  * Source data of IIIF Image
* `parseOptions?` (`Partial<ParseOptions> | undefined`)

###### Returns

Parsed IIIF Image (`Image`).

### `ImageRequest`

###### Fields

* `region?` (`{x: number; y: number; width: number; height: number}`)
* `size?` (`{width: number; height: number}`)

### `LanguageString`

###### Fields

* `[language: string]` (`Array<string | number | boolean>`)

### `MajorVersion`

###### Type

```ts
1 | 2 | 3
```

### `new Manifest(parsedManifest, options)`

###### Parameters

* `parsedManifest` (`{ '@id': string; '@type': "sc:Manifest"; sequences: Array<{ canvases: [{ '@id': string; width: number; height: number; '@type': "sc:Canvas"; images: Array<{ resource: { service: { '@id': string; profile: string | ValidImage2ProfileArray; '@context'?: string | undefined; width?: number | undefined; height?: number | ...`)
* `options?` (`Partial<ConstructorOptions> | undefined`)

###### Returns

`Manifest`.

###### Extends

* `EmbeddedManifest`

### `Manifest#annotations?`

###### Type

```ts
Array<{id: string; type: 'AnnotationPage'}>
```

### `Manifest#canvases`

###### Type

```ts
Array<never>
```

### `Manifest#embedded`

###### Type

```ts
false
```

### `Manifest#fetchAllItems(fetchFn)`

###### Parameters

* `fetchFn` (`  | ((input: RequestInfo | URL, init?: RequestInit) => Promise<Response>)
    | undefined`)

###### Returns

`Promise<Array<FetchNextItemResults<Image>>>`.

### `Manifest#fetchImageByUri(imageUri, fetchFn)`

###### Parameters

* `imageUri` (`string`)
* `fetchFn` (`  | ((input: RequestInfo | URL, init?: RequestInit) => Promise<Response>)
    | undefined`)

###### Returns

`Promise<Image | undefined>`.

### `Manifest#fetchNextItem(fetchFn, depth)`

###### Parameters

* `fetchFn` (`  | ((input: RequestInfo | URL, init?: RequestInit) => Promise<Response>)
    | undefined`)
* `depth` (`number | undefined`)

###### Returns

`AsyncGenerator<FetchNextItemResults<Image>, void, void>`.

### `Manifest#homepage?`

###### Type

```ts
Array<{
  id: string
  type?: string
  label?: LanguageString
  format?: string
  language?: string | string[]
}>
```

### `Manifest#images`

###### Type

```ts
Array<Image | EmbeddedImage>
```

### `Manifest#rendering?`

###### Type

```ts
Array<{
  id: string
  type?: string
  label?: LanguageString
  format?: string
}>
```

### `Manifest#requiredStatement?`

###### Type

```ts
{label: LanguageString; value: LanguageString}
```

### `Manifest#seeAlso?`

###### Type

```ts
Array<{id: string; type?: string; format?: string; profile?: string}>
```

### `Manifest#source?`

###### Type

```ts
unknown
```

### `Manifest#summary?`

###### Type

```ts
{[language: string]: Array<string | number | boolean>}
```

### `Manifest.parse(iiifManifest, options)`

Parses a IIIF resource and returns a [Manifest](#manifest) containing the parsed version

###### Parameters

* `iiifManifest` (`unknown`)
  * Source data of IIIF Manifest
* `options?` (`Partial<ParseOptions> | undefined`)

###### Returns

Parsed IIIF Manifest (`Manifest`).

### `Metadata`

###### Type

```ts
Array<MetadataItem>
```

### `ProfileProperties`

###### Fields

* `maxArea?` (`number`)
* `maxHeight?` (`number`)
* `maxWidth?` (`number`)
* `supportsAnyRegionAndSize` (`boolean`)

### `Region`

###### Fields

* `height` (`number`)
* `width` (`number`)
* `x` (`number`)
* `y` (`number`)

### `Size`

Two numbers indicating the size of a Bbox as \[width, height] or \[xSize, ySize] (`[number, number]`).
Alternatively, two numbers indicating the minimum and maximum of, for example, an array of numbers
Alternatively, two numbers indicating the dimensions of a matrix: rows, cols (which is a different handedness!)

### `TileZoomLevel`

###### Fields

* `columns` (`number`)
* `height` (`number`)
* `originalHeight` (`number`)
* `originalWidth` (`number`)
* `rows` (`number`)
* `scaleFactor` (`number`)
* `width` (`number`)

### `Tileset`

###### Type

```ts
{
  width: number
  scaleFactors: Array<number>
  height?: number | undefined
}
```
