cozy-iiif 0.1.6 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +189 -9
- package/dist/Cozy.d.ts +0 -2
- package/dist/helpers/index.js +93 -0
- package/dist/index.js +238 -330
- package/package.json +2 -1
- package/src/Cozy.ts +1 -2
- package/vite.config.ts +1 -0
package/README.md
CHANGED
@@ -1,16 +1,15 @@
|
|
1
1
|
# cozy-iiif
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
A developer-friendly collection of abstractions and utilities built on top of the IIIF Commons [@iiif/presentation-3](https://github.com/IIIF-Commons/presentation-3-types) and [@iiif/parser](https://github.com/IIIF-Commons/parser) libraries.
|
3
|
+
A developer-friendly API for working with IIIF resources. Built on top of the IIIF Commons [@iiif/presentation-3](https://github.com/IIIF-Commons/presentation-3-types) and [@iiif/parser](https://github.com/IIIF-Commons/parser) libraries.
|
6
4
|
|
7
5
|
## Features
|
8
6
|
|
9
|
-
- Resource identification for any URL: IIIF manifests,
|
7
|
+
- Resource identification for any URL: IIIF collection and presentation manifests, image services, static image, and more.
|
10
8
|
- Developer-friendly TypeScript API for parsing and working with IIIF resources.
|
11
|
-
- Seamless upgrade from IIIF Presentation API v2 to v3 using `@iiif/parser
|
9
|
+
- Seamless upgrade from IIIF Presentation API v2 to v3 (using `@iiif/parser` under the hood).
|
12
10
|
- Preserves access to underlying `@iiif-presentation/3` types.
|
13
|
-
-
|
11
|
+
- Helpers for stitching thumbnails and cropping regions from IIIF Level 0 tilesets.
|
12
|
+
- Helpers for adding annotations to Canvases and Presentation manifests.
|
14
13
|
|
15
14
|
## Installation
|
16
15
|
|
@@ -20,11 +19,192 @@ npm install cozy-iiif
|
|
20
19
|
|
21
20
|
## Basic Usage
|
22
21
|
|
23
|
-
|
22
|
+
Identify and parse a URL:
|
23
|
+
|
24
|
+
```ts
|
25
|
+
import { Cozy } from 'cozy-iiif';
|
26
|
+
|
27
|
+
const parsed = await Cozy.parseURL('https://www.example.com/manifest.json');
|
28
|
+
|
29
|
+
if (parsed.type === 'manifest') {
|
30
|
+
const manifest = parsed.resource; // CozyManifest
|
31
|
+
console.log(`Presentation API ${manifest.majorVersion}`);
|
32
|
+
} else if (parsed.type === 'collection') {
|
33
|
+
const collection = parsed.resource; // CozyCollection
|
34
|
+
console.log(`Collection, Presentation API ${collection.majorVersion}`);
|
35
|
+
} else if (parsed.type === 'iiif-image') {
|
36
|
+
const image = parsed.resource; // CozyImageResource
|
37
|
+
console.log(`Image API ${image.majorVersion}`);
|
38
|
+
} else if (parsed.type === 'plain-image') {
|
39
|
+
console.log('Plaing (JPEG or PNG) image');
|
40
|
+
} else if (parsed.type === 'webpage') {
|
41
|
+
console.log('URL points to a web page!');
|
42
|
+
} else if (parsed.type === 'error') {
|
43
|
+
console.log('Error:', parsed.code, parsed.message);
|
44
|
+
}
|
45
|
+
```
|
46
|
+
|
47
|
+
Alternatively, you can parse an existing JSON object.
|
48
|
+
|
49
|
+
```ts
|
50
|
+
const json = await fetch('https://www.example.com/manifest.json').then(res => res.json());
|
51
|
+
Cozy.parse(json);
|
52
|
+
```
|
53
|
+
|
54
|
+
Cozy provides normalized utility types for key IIIF entities: **CozyManifest**,
|
55
|
+
**CozyCanvas**, **CozyRange**, **CozyImageResource**, **CozyCollection**, etc. Each
|
56
|
+
utility type provides helpers to simplify common access operations (e.g. metadata,
|
57
|
+
labels in different locales, etc.) and retains the original source data
|
58
|
+
as a `source` field.
|
59
|
+
|
60
|
+
```ts
|
61
|
+
// Parsed CozyManifest
|
62
|
+
const manifest = parsed.resource;
|
63
|
+
|
64
|
+
// Default
|
65
|
+
console.log(manifest.getLabel());
|
66
|
+
|
67
|
+
// For locale (with fallback)
|
68
|
+
console.log(manifest.getLabel('de'));
|
69
|
+
|
70
|
+
// Metadata as normalized CozyMetada[]
|
71
|
+
console.log(manifest.getMetadata());
|
72
|
+
```
|
73
|
+
|
74
|
+
### Thumbnail Helper
|
75
|
+
|
76
|
+
CozyCanvas has a simple helper for getting a Thumbnail URL. The URL
|
77
|
+
will use the `thumbnail` property if available, or the image service
|
78
|
+
otherwise.
|
79
|
+
|
80
|
+
```ts
|
81
|
+
const firstCanvas = manifest.canvases[0];
|
82
|
+
|
83
|
+
// With default size (400px smallest dimension)
|
84
|
+
console.log(firstCanvas.getThumbnailURL());
|
85
|
+
|
86
|
+
// With custom minimum smallest dimension
|
87
|
+
console.log(firstCanvas.getThumbnailURL(600));
|
88
|
+
```
|
89
|
+
|
90
|
+
### Table of Contents Helper
|
91
|
+
|
92
|
+
cozy-iiif includes a utility to easily get a table of contents for a Presentation manifest,
|
93
|
+
based on the manifest's `structures` property.
|
94
|
+
|
95
|
+
```ts
|
96
|
+
// Returns a list of CozyTOCNode objects.
|
97
|
+
const root = manifest.getTableOfContents();
|
98
|
+
|
99
|
+
const logTOCNode = (node: CozyTOCNode) => {
|
100
|
+
console.log(node.getLabel());
|
101
|
+
node.children.forEach(logTOCNode);
|
102
|
+
}
|
103
|
+
|
104
|
+
root.forEach(logTOCNode);
|
105
|
+
```
|
106
|
+
|
107
|
+
### Working with different Image Types and Levels
|
108
|
+
|
109
|
+
The **CozyImageResource** type provides a helper property for identify the type and level of
|
110
|
+
an image.
|
24
111
|
|
25
|
-
|
112
|
+
```ts
|
113
|
+
const firstCanvas = manifest.canvases[0];
|
114
|
+
const image = firstCanvas.images[0];
|
26
115
|
|
27
|
-
|
116
|
+
console.log(image.type); // 'static', 'dynamic' or 'level0';
|
117
|
+
```
|
118
|
+
|
119
|
+
Dynamic images are served from an image server and have helpers to retrieve specific region URLs.
|
120
|
+
|
121
|
+
```ts
|
122
|
+
const bounds = {
|
123
|
+
x: 100,
|
124
|
+
y: 100,
|
125
|
+
w: 320,
|
126
|
+
h: 240
|
127
|
+
};
|
128
|
+
|
129
|
+
// With default minimum shortest dimension (400px);
|
130
|
+
console.log(image.getRegionURL(bounds));
|
131
|
+
|
132
|
+
// With custom minimum shorted dimension
|
133
|
+
console.log(image.getRegionURL(bounds, 800));
|
134
|
+
```
|
135
|
+
|
136
|
+
## Cozy Helpers
|
137
|
+
|
138
|
+
### Stitching and Cropping for Level 0 Tilesets
|
139
|
+
|
140
|
+
Working with a Level 0 tileset, but need a thumbnail, or crop a region? The `cozy-iiif/level-0` module
|
141
|
+
has you covered! Cozy uses Web workers for background image processing and request throttling when
|
142
|
+
harvesting tilesets for stitching. Stitched images are harvested at the smallest possible size,
|
143
|
+
to keep things fast and prevent unnecessary downloads.
|
144
|
+
|
145
|
+
|
146
|
+
**Thumbnails**
|
147
|
+
|
148
|
+
```ts
|
149
|
+
import { getThumbnail } from 'cozy-iiif/level-0';
|
150
|
+
|
151
|
+
const firstImage = canvas.images[0];
|
152
|
+
if (firstImage.type !== 'level0') {
|
153
|
+
// Normal thumbnail URL (string)
|
154
|
+
console.log(canvas.getThumbnailURL());
|
155
|
+
} else {
|
156
|
+
getThumbnail(firstImage).then(blob => {
|
157
|
+
// Creates a data URL you can use as `src` for an image
|
158
|
+
console.log(URL.createObjectURL(blob));
|
159
|
+
});
|
160
|
+
}
|
161
|
+
```
|
162
|
+
|
163
|
+
**Regions**
|
164
|
+
|
165
|
+
```ts
|
166
|
+
import { cropRegion } from 'cozy-iiif/level-0';
|
167
|
+
|
168
|
+
const firstImage = canvas.images[0];
|
169
|
+
|
170
|
+
const bounds = {
|
171
|
+
x: 100,
|
172
|
+
y: 100,
|
173
|
+
w: 320,
|
174
|
+
h: 240
|
175
|
+
};
|
176
|
+
|
177
|
+
if (firstImage.type === 'level0') {
|
178
|
+
cropRegion(firstImage, bounds).then(blob => {
|
179
|
+
console.log(URL.createObjectURL(blob));
|
180
|
+
});
|
181
|
+
}
|
182
|
+
```
|
183
|
+
|
184
|
+
### Annotation Helpers
|
185
|
+
|
186
|
+
Utilities for working with annotations on on Canvases.
|
187
|
+
|
188
|
+
```ts
|
189
|
+
import type { Annotation } from '@iiif/presentation-3';
|
190
|
+
import { importAnnotations } from 'cozy-iiif/helpers';
|
191
|
+
|
192
|
+
const annotations: Annotation[] = [{
|
193
|
+
id: 'https://iiif.io/api/cookbook/recipe/0021-tagging/annotation/p0002-tag',
|
194
|
+
type: 'Annotation',
|
195
|
+
motivation: 'tagging',
|
196
|
+
body: {
|
197
|
+
type: 'TextualBody',
|
198
|
+
value: 'Gänseliesel-Brunnen',
|
199
|
+
language: 'de',
|
200
|
+
format: "text/plain"
|
201
|
+
},
|
202
|
+
target: 'https://iiif.io/api/cookbook/recipe/0021-tagging/canvas/p1#xywh=265,661,1260,1239'
|
203
|
+
}]
|
204
|
+
|
205
|
+
// Generates a new CozyManifest with annotations from an original CozyManifest.
|
206
|
+
const updated = importAnnotations(original, annotations);
|
207
|
+
```
|
28
208
|
|
29
209
|
## License
|
30
210
|
|
package/dist/Cozy.d.ts
CHANGED
@@ -0,0 +1,93 @@
|
|
1
|
+
const n = [];
|
2
|
+
for (let t = 0; t < 256; ++t)
|
3
|
+
n.push((t + 256).toString(16).slice(1));
|
4
|
+
function h(t, e = 0) {
|
5
|
+
return (n[t[e + 0]] + n[t[e + 1]] + n[t[e + 2]] + n[t[e + 3]] + "-" + n[t[e + 4]] + n[t[e + 5]] + "-" + n[t[e + 6]] + n[t[e + 7]] + "-" + n[t[e + 8]] + n[t[e + 9]] + "-" + n[t[e + 10]] + n[t[e + 11]] + n[t[e + 12]] + n[t[e + 13]] + n[t[e + 14]] + n[t[e + 15]]).toLowerCase();
|
6
|
+
}
|
7
|
+
let p;
|
8
|
+
const m = new Uint8Array(16);
|
9
|
+
function y() {
|
10
|
+
if (!p) {
|
11
|
+
if (typeof crypto > "u" || !crypto.getRandomValues)
|
12
|
+
throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
|
13
|
+
p = crypto.getRandomValues.bind(crypto);
|
14
|
+
}
|
15
|
+
return p(m);
|
16
|
+
}
|
17
|
+
const b = typeof crypto < "u" && crypto.randomUUID && crypto.randomUUID.bind(crypto), c = { randomUUID: b };
|
18
|
+
function x(t, e, i) {
|
19
|
+
var d;
|
20
|
+
if (c.randomUUID && !t)
|
21
|
+
return c.randomUUID();
|
22
|
+
t = t || {};
|
23
|
+
const u = t.random ?? ((d = t.rng) == null ? void 0 : d.call(t)) ?? y();
|
24
|
+
if (u.length < 16)
|
25
|
+
throw new Error("Random bytes length must be >= 16");
|
26
|
+
return u[6] = u[6] & 15 | 64, u[8] = u[8] & 63 | 128, h(u);
|
27
|
+
}
|
28
|
+
const a = (t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), U = (t, e) => {
|
29
|
+
if (e) {
|
30
|
+
const i = t.annotations;
|
31
|
+
if (i.length > 0) {
|
32
|
+
const u = new RegExp(`${a(t.id)}/${a(e)}/page/p(\\d+)$`), d = i.reduce((s, o) => {
|
33
|
+
const r = o.id.match(u);
|
34
|
+
if (r && r[1]) {
|
35
|
+
const g = parseInt(r[1]);
|
36
|
+
return Math.max(s, g);
|
37
|
+
} else
|
38
|
+
return s;
|
39
|
+
}, 1);
|
40
|
+
return `${t.id}/${e}/annotations/page/p${d}`;
|
41
|
+
} else
|
42
|
+
return `${t.id}/${e}/annotations/page/p1`;
|
43
|
+
} else
|
44
|
+
return `${t.id}/annotations/page/${x()}`;
|
45
|
+
}, l = (t, e, i) => {
|
46
|
+
const u = {
|
47
|
+
id: U(t, i),
|
48
|
+
type: "AnnotationPage",
|
49
|
+
items: e
|
50
|
+
};
|
51
|
+
return {
|
52
|
+
source: {
|
53
|
+
...t.source,
|
54
|
+
annotations: [...t.annotations, u]
|
55
|
+
},
|
56
|
+
id: t.id,
|
57
|
+
width: t.width,
|
58
|
+
height: t.height,
|
59
|
+
images: [...t.images],
|
60
|
+
annotations: [...t.annotations, u],
|
61
|
+
getLabel: t.getLabel,
|
62
|
+
getMetadata: t.getMetadata,
|
63
|
+
getThumbnailURL: t.getThumbnailURL
|
64
|
+
};
|
65
|
+
}, $ = (t, e, i) => {
|
66
|
+
const u = (o) => {
|
67
|
+
const r = o.target;
|
68
|
+
if (r)
|
69
|
+
return typeof r == "string" ? r.substring(0, r.indexOf("#")) : r.source;
|
70
|
+
}, d = e.reduce((o, r) => {
|
71
|
+
const g = u(r);
|
72
|
+
return g && (o[g] || (o[g] = []), o[g].push(r)), o;
|
73
|
+
}, {}), s = t.canvases.map((o) => {
|
74
|
+
const r = d[o.id] || [];
|
75
|
+
return r.length > 0 ? l(o, r, i) : o;
|
76
|
+
});
|
77
|
+
return {
|
78
|
+
source: {
|
79
|
+
...t.source,
|
80
|
+
items: s.map((o) => o.source)
|
81
|
+
},
|
82
|
+
id: t.id,
|
83
|
+
majorVersion: t.majorVersion,
|
84
|
+
canvases: s,
|
85
|
+
structure: t.structure,
|
86
|
+
getLabel: t.getLabel,
|
87
|
+
getMetadata: t.getMetadata,
|
88
|
+
getTableOfContents: t.getTableOfContents
|
89
|
+
};
|
90
|
+
}, I = (t, e, i) => t.source.type === "Canvas" ? l(t, e, i) : $(t, e, i);
|
91
|
+
export {
|
92
|
+
I as importAnnotations
|
93
|
+
};
|