cozy-iiif 0.1.6 → 0.2.1

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 CHANGED
@@ -1,16 +1,15 @@
1
1
  # cozy-iiif
2
2
 
3
- **WORK IN PROGRESS**
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, manifest, image services, static image, and more.
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/`.
12
- - Preserves access to underlying `@iiif-presentation/3` types.
13
- - Provides helpers for stitching thumbnails and cropping regions from IIIF Level 0 tilesets.
9
+ - Seamless upgrade from IIIF Presentation API v2 to v3 (using `@iiif/parser` under the hood).
10
+ - Preserves access to underlying `@iiif/presentation-3` types.
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,12 +19,199 @@ npm install cozy-iiif
20
19
 
21
20
  ## Basic Usage
22
21
 
23
- Todo...
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
+ // The raw source data, @iiif/presentation-3 typed
74
+ console.log(manifest.source);
75
+ ```
76
+
77
+ ### Thumbnail Helper
78
+
79
+ CozyCanvas has a simple helper for getting a Thumbnail URL. The URL
80
+ will use the `thumbnail` property of the original resource if available, or the image service
81
+ otherwise.
82
+
83
+ ```ts
84
+ const firstCanvas = manifest.canvases[0];
85
+
86
+ // With default size (400px smallest dimension)
87
+ console.log(firstCanvas.getThumbnailURL());
88
+
89
+ // With custom minimum smallest dimension
90
+ console.log(firstCanvas.getThumbnailURL(600));
91
+ ```
92
+
93
+ ### Table of Contents Helper
94
+
95
+ cozy-iiif includes a utility to easily get a table of contents for a Presentation manifest,
96
+ based on the manifest's `structures` property.
97
+
98
+ ```ts
99
+ // Returns a list of CozyTOCNode objects.
100
+ const toc = manifest.getTableOfContents();
101
+
102
+ const logTOCNode = (node: CozyTOCNode) => {
103
+ console.log(node.getLabel());
104
+ node.children.forEach(logTOCNode);
105
+ }
106
+
107
+ root.forEach(logTOCNode.root);
108
+ ```
109
+
110
+ ### Image Types and Levels
111
+
112
+ The **CozyImageResource** provides a helper properties that identify the type and level of
113
+ an image.
24
114
 
25
- ## API
115
+ ```ts
116
+ const firstCanvas = manifest.canvases[0];
117
+ const image = firstCanvas.images[0];
26
118
 
27
- Todo...
119
+ console.log(image.type); // 'static', 'dynamic' or 'level0';
120
+ ```
121
+
122
+ Dynamic images are served from an image server and have helpers to retrieve specific region URLs.
123
+
124
+ ```ts
125
+ const bounds = {
126
+ x: 100,
127
+ y: 100,
128
+ w: 320,
129
+ h: 240
130
+ };
131
+
132
+ // With default minimum shortest dimension (400px);
133
+ console.log(image.getRegionURL(bounds));
134
+
135
+ // With custom minimum shorted dimension
136
+ console.log(image.getRegionURL(bounds, 800));
137
+ ```
138
+
139
+ ## Cozy Helpers
140
+
141
+ ### Stitching and Cropping for Level 0 Tilesets
142
+
143
+ Working with a Level 0 tileset, but need a thumbnail, or crop a region? The `cozy-iiif/level-0` module
144
+ has you covered! Cozy uses Web workers for background image processing and request throttling when
145
+ harvesting tilesets for stitching. Stitched images are harvested at the smallest possible size,
146
+ to keep things fast and prevent unnecessary downloads.
147
+
148
+
149
+ **Thumbnails**
150
+
151
+ ```ts
152
+ import { getThumbnail } from 'cozy-iiif/level-0';
153
+
154
+ const firstImage = canvas.images[0];
155
+ if (firstImage.type !== 'level0') {
156
+ // Normal thumbnail URL (string)
157
+ console.log(canvas.getThumbnailURL());
158
+ } else {
159
+ getThumbnail(firstImage).then(blob => {
160
+ // Creates a data URL you can use as `src` for an image
161
+ console.log(URL.createObjectURL(blob));
162
+ });
163
+ }
164
+ ```
165
+
166
+ **Regions**
167
+
168
+ ```ts
169
+ import { cropRegion } from 'cozy-iiif/level-0';
170
+
171
+ const firstImage = canvas.images[0];
172
+
173
+ const bounds = {
174
+ x: 100,
175
+ y: 100,
176
+ w: 320,
177
+ h: 240
178
+ };
179
+
180
+ if (firstImage.type === 'level0') {
181
+ cropRegion(firstImage, bounds).then(blob => {
182
+ console.log(URL.createObjectURL(blob));
183
+ });
184
+ }
185
+ ```
186
+
187
+ ### Annotation Helpers
188
+
189
+ Utilities for working with annotations on on Canvases.
190
+
191
+ ```ts
192
+ import type { Annotation } from '@iiif/presentation-3';
193
+ import { importAnnotations } from 'cozy-iiif/helpers';
194
+
195
+ const annotations: Annotation[] = [{
196
+ id: 'https://iiif.io/api/cookbook/recipe/0021-tagging/annotation/p0002-tag',
197
+ type: 'Annotation',
198
+ motivation: 'tagging',
199
+ body: {
200
+ type: 'TextualBody',
201
+ value: 'Gänseliesel-Brunnen',
202
+ language: 'de',
203
+ format: "text/plain"
204
+ },
205
+ target: 'https://iiif.io/api/cookbook/recipe/0021-tagging/canvas/p1#xywh=265,661,1260,1239'
206
+ }]
207
+
208
+ // Generates a new CozyManifest with annotations from an original CozyManifest.
209
+ const updated = importAnnotations(original, annotations);
210
+
211
+ // The source field has the raw manifest JSON (annotations included!)
212
+ console.log(updated.source);
213
+ ```
28
214
 
29
215
  ## License
30
216
 
31
- MIT License - see the [LICENSE](LICENSE) file for details.
217
+ MIT License - see the [LICENSE](LICENSE) file for details.
package/dist/Cozy.d.ts CHANGED
@@ -1,7 +1,5 @@
1
1
  import { CozyParseResult } from './types';
2
- import * as Helpers from './helpers';
3
2
  export declare const Cozy: {
4
3
  parse: (json: any, url?: string) => CozyParseResult;
5
4
  parseURL: (input: string) => Promise<CozyParseResult>;
6
- Helpers: typeof Helpers;
7
5
  };
@@ -1,2 +1,2 @@
1
- import { CozyRange, CozyTOCNode } from '../types';
2
- export declare const getTableOfContents: (ranges: CozyRange[]) => () => CozyTOCNode[];
1
+ import { CozyRange, CozyTOC } from '../types';
2
+ export declare const getTableOfContents: (ranges: CozyRange[]) => () => CozyTOC;
@@ -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
+ };