iiif_manifest 1.3.1 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +6 -0
- data/.gitignore +1 -0
- data/README.md +45 -4
- data/lib/iiif_manifest/manifest_builder/record_property_builder.rb +13 -4
- data/lib/iiif_manifest/v3/annotation_content.rb +23 -0
- data/lib/iiif_manifest/v3/manifest_builder/annotation_content_builder.rb +58 -0
- data/lib/iiif_manifest/v3/manifest_builder/body_builder.rb +27 -12
- data/lib/iiif_manifest/v3/manifest_builder/canvas_builder.rb +97 -16
- data/lib/iiif_manifest/v3/manifest_builder/iiif_service.rb +38 -0
- data/lib/iiif_manifest/v3/manifest_builder/placeholdercanvas_builder.rb +57 -0
- data/lib/iiif_manifest/v3/manifest_builder/record_property_builder.rb +1 -1
- data/lib/iiif_manifest/v3/manifest_builder/structure_builder.rb +16 -3
- data/lib/iiif_manifest/v3/manifest_builder.rb +2 -0
- data/lib/iiif_manifest/v3/manifest_service_locator.rb +24 -1
- data/lib/iiif_manifest/v3.rb +1 -0
- data/lib/iiif_manifest/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 21edd1255e73222b1c238869e8c2d47fb0d33bc22fd0e490e0b64e034de1a147
|
4
|
+
data.tar.gz: f969ee0b9bc4c9f1e2933d0cce4aeaec8cbcd532520cfa1b03e88a07f0f55d9e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9dba593b91faf18b8ba717e668cad98c1ca3384a97b989fa380e15ba20cced1c4e7d9d01d8698860d2a45fdaf3d7808cf93ba767853cac8af7acc2fbc8aef417
|
7
|
+
data.tar.gz: 1e9738fc181e851f23ab5bf37f797c9aa40c561a6201e9c6d56590a84d7b088641eef3d884fa59107a6e9f47d5570b22151a87f04c4747bc9b59558b249bc50f
|
data/.circleci/config.yml
CHANGED
@@ -34,6 +34,9 @@ workflows:
|
|
34
34
|
version: 2
|
35
35
|
ci:
|
36
36
|
jobs:
|
37
|
+
- test:
|
38
|
+
name: "ruby3-3"
|
39
|
+
ruby_version: "3.3.0"
|
37
40
|
- test:
|
38
41
|
name: "ruby3-2"
|
39
42
|
ruby_version: "3.2.0"
|
@@ -62,6 +65,9 @@ workflows:
|
|
62
65
|
only:
|
63
66
|
- main
|
64
67
|
jobs:
|
68
|
+
- test:
|
69
|
+
name: "ruby3-3"
|
70
|
+
ruby_version: "3.3.0"
|
65
71
|
- test:
|
66
72
|
name: "ruby3-2"
|
67
73
|
ruby_version: "3.2.0"
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -32,6 +32,8 @@ Additionally it **_should_** implement `#manifest_metadata` to provide an array
|
|
32
32
|
|
33
33
|
Additionally it **_may_** implement `#search_service` to contain the url for a IIIF search api compliant search endpoint and `#autocomplete_service` to contain the url for a IIIF search api compliant autocomplete endpoint. Please note, the autocomplete service is embedded within the search service description so if an autocomplete_service is supplied without a search_service it will be ignored. The IIIF `profile` added to the service descriptions is version 0 as this is the version supported by the current version of Universal Viewer. Only include a search_service within the manifest if your application has implemented a IIIF search service at the endpoint specified in the manifest.
|
34
34
|
|
35
|
+
Additionally it **_may_** implement `#service` to contain an array of hashes for services other than `search_service` or `autocomplete_service`. Each must contain "@id" (V2) or "id" (V3) and "@context" (V2) or "type" (V3) and may contain any other arbitrary properties.
|
36
|
+
|
35
37
|
Additionally it **_may_** implement `#sequence_rendering` to contain an array of hashes for file downloads to be offered at sequences level. Each hash must contain "@id", "format" (mime type) and "label" (eg. `{ "@id" => "download url", "format" => "application/pdf", "label" => "user friendly label" }`).
|
36
38
|
|
37
39
|
Finally, it **_may_** implement `ranges`, which returns an array of objects which represent a table of contents or similar structure, each of which responds to `label`, `ranges`, and `file_set_presenters`.
|
@@ -76,6 +78,16 @@ For example:
|
|
76
78
|
"http://test.host/books/#{@id}/autocomplete"
|
77
79
|
end
|
78
80
|
|
81
|
+
def service
|
82
|
+
[
|
83
|
+
{
|
84
|
+
"@context" => "http://iiif.io/api/annext/services/example/context.json",
|
85
|
+
"@id" => "https://example.org/service",
|
86
|
+
"profile" => "https://example.org/docs/service"
|
87
|
+
}
|
88
|
+
]
|
89
|
+
end
|
90
|
+
|
79
91
|
def sequence_rendering
|
80
92
|
[{"@id" => "http://test.host/file_set/id/download", "format" => "application/pdf", "label" => "Download"}]
|
81
93
|
end
|
@@ -107,7 +119,12 @@ For example:
|
|
107
119
|
|
108
120
|
The class that represents the leaf nodes, must implement `#id`. It must also implement `#display_image` which returns an instance of `IIIFManifest::DisplayImage`
|
109
121
|
|
110
|
-
|
122
|
+
In Presentation 3.0, additionally it **_may_** implement;
|
123
|
+
- `#item_metadata` to contain an array of hashes for metadata to be displayed at each leaf node. Items must contain "label" and "value" properties.
|
124
|
+
- `#sequence_rendering` to contain an array of hashes for file downloads to be offered at each leaf node. This follows a similar format as `#sequence_rendering` at sequences level
|
125
|
+
- `#see_also` to contain an array of hashes for related resources to be offered at each leaf node. Items must contain "id" and "type" properties. Items should contain "label", "format", and "profile" properties.
|
126
|
+
- `#part_of` to contain an array of hashes for parent resources to be offered at each leaf node. Items must contain "id" and "type" properties. Items should contain "label".
|
127
|
+
- `#placeholder_content` to contain an instance of `IIIFManifest::V3::DisplayContent` for [`placeholderCanvas`](https://iiif.io/api/presentation/3.0/#placeholdercanvas) at each leaf node
|
111
128
|
|
112
129
|
```ruby
|
113
130
|
class Page
|
@@ -128,10 +145,28 @@ Additionally it **_may_** implement `#sequence_rendering` to contain an array of
|
|
128
145
|
)
|
129
146
|
end
|
130
147
|
|
148
|
+
# --------------------------------------- Presentation 3.0 (Alpha) ---------------------------------------
|
131
149
|
def sequence_rendering
|
132
150
|
[{"@id" => "http://test.host/display_image/id/download", "format" => "application/pdf", "label" => "Download"}]
|
133
151
|
end
|
134
152
|
|
153
|
+
def see_also
|
154
|
+
[{"id" => "http://test.host/display_image/id/image.json", "type" => "dataset", "format" => "application/json", "label" => "Related Resource"}]
|
155
|
+
end
|
156
|
+
|
157
|
+
def part_of
|
158
|
+
[{"id" => "http://test.host/display_image/id/parent.json", "type" => "manifest"}]
|
159
|
+
end
|
160
|
+
|
161
|
+
def placeholder_content
|
162
|
+
IIIFManifest::V3::DisplayContent.new(id,
|
163
|
+
width: 100,
|
164
|
+
height: 100,
|
165
|
+
type: "Image",
|
166
|
+
format: "image/jpeg")
|
167
|
+
end
|
168
|
+
# --------------------------------------- Presentation 3.0 (Alpha) ---------------------------------------
|
169
|
+
|
135
170
|
private
|
136
171
|
|
137
172
|
def endpoint
|
@@ -148,9 +183,9 @@ Then you can produce the manifest on the book object like this:
|
|
148
183
|
IIIFManifest::ManifestFactory.new(book).to_h.to_json
|
149
184
|
```
|
150
185
|
|
151
|
-
## Presentation 3.0
|
186
|
+
## Presentation 3.0
|
152
187
|
|
153
|
-
Provisional support for the [3.0
|
188
|
+
Provisional support for the [3.0 version of the IIIF presentation api spec](https://iiif.io/api/presentation/3.0/) has been added with a focus on audiovisual content. The [change log](https://iiif.io/api/presentation/3.0/change-log/) lists the changes to the specification.
|
154
189
|
|
155
190
|
The presentation 3.0 support has been contained to the `V3` namespace. Version 2.0 manifests are still being built using `IIIFManifest::ManifestFactory` while version 3.0 manifests can now be built using `IIIFManifest::V3::ManifestFactory`.
|
156
191
|
|
@@ -166,7 +201,13 @@ The presentation 3.0 support has been contained to the `V3` namespace. Version 2
|
|
166
201
|
- Presenters **_may_** implement `#homepage` to contain a hash for linking back to a repository webpage for this manifest. The hash must contain "id", "format" (mime type), "type", and "label" (eg. `{ "id" => "repository url", "format" => "text/html", "type" => "Text", "label" => { "en": ["View in repository"] }`).
|
167
202
|
- File set presenters may target a fragment of its content by providing `#media_fragment` which will be appended to its `id`.
|
168
203
|
- Range objects may now implement `#items` instead of `#ranges` and `#file_set_presenters` to allow for interleaving these objects. `#items` is not required and existing range objects should continue to work.
|
169
|
-
- File set presenters may provide
|
204
|
+
- File set presenters may provide,
|
205
|
+
- `#display_content` which should return an instance of `IIIFManifest::V3::DisplayContent` (or an array of instances in the case of a user `Choice`)
|
206
|
+
- `#annotation_content` which should return an instance of `IIIFManifest::V3::AnnotationContent` (or an array of instances in the case of a canvas having multiple annotations)
|
207
|
+
- `#display_image` is no longer required but will still work if provided
|
208
|
+
- `#sequence_rendering` is supported at leaf node level, to present an array of file downloads available at each leaf node
|
209
|
+
- `#part_of` is supported at leaf node level, to present an array of parent resources available at each leaf node.
|
210
|
+
- `#placeholder_content` which returns an instance of `IIIFManifest::V3::DisplayContent` presents a [`placeholderCanvas`](https://iiif.io/api/presentation/3.0/#placeholdercanvas) at leaf node level
|
170
211
|
- DisplayContent may provide `#auth_service` which should return a hash containing a IIIF Authentication service definition (<https://iiif.io/api/auth/1.0/>) that will be included on the content resource.
|
171
212
|
|
172
213
|
## Configuration
|
@@ -48,15 +48,24 @@ module IIIFManifest
|
|
48
48
|
@iiif_autocomplete_service ||= iiif_autocomplete_service_factory.new
|
49
49
|
end
|
50
50
|
|
51
|
+
def custom_services
|
52
|
+
record.respond_to?(:service) ? record.send(:service) : []
|
53
|
+
end
|
54
|
+
|
51
55
|
# Build services. Currently supported:
|
52
56
|
# search_service, with (optional) embedded autocomplete service
|
53
57
|
#
|
54
58
|
# @return [Array] array of services
|
55
59
|
def services
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
+
services = []
|
61
|
+
if search_service.present?
|
62
|
+
iiif_search_service.search_service = search_service
|
63
|
+
iiif_autocomplete_service.autocomplete_service = autocomplete_service
|
64
|
+
iiif_search_service.service = iiif_autocomplete_service if autocomplete_service.present?
|
65
|
+
services << iiif_search_service
|
66
|
+
end
|
67
|
+
services += Array(custom_services)
|
68
|
+
services
|
60
69
|
end
|
61
70
|
|
62
71
|
# Validate manifest_metadata against the IIIF spec format for metadata
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module IIIFManifest
|
2
|
+
module V3
|
3
|
+
class AnnotationContent
|
4
|
+
attr_reader :annotation_id, :body_id, :type, :motivation, :format, :language, :label, :value, :media_fragment
|
5
|
+
|
6
|
+
def initialize(type:, motivation:, **kwargs)
|
7
|
+
# If a user requires a specific ID at the annotation level, this attr overrides
|
8
|
+
# the automatic ID creation.
|
9
|
+
@annotation_id = kwargs[:annotation_id]
|
10
|
+
# Body level ids are only required for annotations delivering content,
|
11
|
+
# such as a transcript/caption file or an annotation containing an image.
|
12
|
+
@body_id = kwargs[:body_id]
|
13
|
+
@type = type
|
14
|
+
@motivation = motivation
|
15
|
+
@format = kwargs[:format]
|
16
|
+
@language = kwargs[:language]
|
17
|
+
@label = kwargs[:label]
|
18
|
+
@value = kwargs[:value]
|
19
|
+
@media_fragment = kwargs[:media_fragment]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module IIIFManifest
|
2
|
+
module V3
|
3
|
+
class ManifestBuilder
|
4
|
+
class AnnotationContentBuilder
|
5
|
+
attr_reader :annotation_content, :iiif_annotation_factory, :body_builder_factory
|
6
|
+
def initialize(annotation_content, iiif_annotation_factory:, body_builder_factory:)
|
7
|
+
@annotation_content = annotation_content
|
8
|
+
@iiif_annotation_factory = iiif_annotation_factory
|
9
|
+
@body_builder_factory = body_builder_factory
|
10
|
+
build_annotation_resource
|
11
|
+
end
|
12
|
+
|
13
|
+
def apply(canvas)
|
14
|
+
# Assume first item in canvas annotations is an annotation page
|
15
|
+
canvas_id = canvas.annotations.first['id']
|
16
|
+
generic_annotation['id'] = annotation_id(canvas_id)
|
17
|
+
generic_annotation['target'] = target(canvas)
|
18
|
+
generic_annotation['motivation'] = motivation
|
19
|
+
generic_annotation
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def build_annotation_resource
|
25
|
+
annotation_body_builder.apply(generic_annotation)
|
26
|
+
end
|
27
|
+
|
28
|
+
def annotation_body_builder
|
29
|
+
body_builder_factory.new(annotation_content)
|
30
|
+
end
|
31
|
+
|
32
|
+
def generic_annotation
|
33
|
+
@generic_annotation ||= iiif_annotation_factory.new
|
34
|
+
end
|
35
|
+
|
36
|
+
def annotation_id(canvas_id)
|
37
|
+
if annotation_content.try(:annotation_id).blank?
|
38
|
+
"#{canvas_id}/#{motivation.presence || 'annotation'}/#{generic_annotation.index}"
|
39
|
+
else
|
40
|
+
annotation_content.annotation_id
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def motivation
|
45
|
+
annotation_content.motivation if annotation_content.try(:motivation).present?
|
46
|
+
end
|
47
|
+
|
48
|
+
def target(canvas)
|
49
|
+
if annotation_content.try(:media_fragment).present?
|
50
|
+
canvas['id'] + "##{annotation_content.media_fragment}"
|
51
|
+
else
|
52
|
+
canvas['id']
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -2,9 +2,9 @@ module IIIFManifest
|
|
2
2
|
module V3
|
3
3
|
class ManifestBuilder
|
4
4
|
class BodyBuilder
|
5
|
-
attr_reader :
|
6
|
-
def initialize(
|
7
|
-
@
|
5
|
+
attr_reader :content, :iiif_body_factory, :image_service_builder_factory
|
6
|
+
def initialize(content, iiif_body_factory:, image_service_builder_factory:)
|
7
|
+
@content = content
|
8
8
|
@iiif_body_factory = iiif_body_factory
|
9
9
|
@image_service_builder_factory = image_service_builder_factory
|
10
10
|
end
|
@@ -19,25 +19,40 @@ module IIIFManifest
|
|
19
19
|
private
|
20
20
|
|
21
21
|
def build_body
|
22
|
-
body['id'] =
|
22
|
+
body['id'] = body_id
|
23
23
|
body['type'] = body_type
|
24
|
-
|
25
|
-
body['
|
26
|
-
body['
|
27
|
-
body['
|
28
|
-
body['
|
24
|
+
body_display_dimensions
|
25
|
+
body['format'] = content.format if content.try(:format).present?
|
26
|
+
body['label'] = ManifestBuilder.language_map(content.label) if content.try(:label).present?
|
27
|
+
body['language'] = content.language if content.try(:language).present?
|
28
|
+
body['value'] = content.value if content.try(:value).present?
|
29
29
|
end
|
30
30
|
|
31
31
|
def body
|
32
32
|
@body ||= iiif_body_factory.new
|
33
33
|
end
|
34
34
|
|
35
|
+
def body_id
|
36
|
+
return if content.try(:body_id).blank? && content.try(:url).blank?
|
37
|
+
if content.try(:body_id).present?
|
38
|
+
content.body_id
|
39
|
+
else
|
40
|
+
content.url
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
35
44
|
def body_type
|
36
|
-
|
45
|
+
content.try(:type) || 'Image'
|
46
|
+
end
|
47
|
+
|
48
|
+
def body_display_dimensions
|
49
|
+
body['height'] = content.height if content.try(:height).present?
|
50
|
+
body['width'] = content.width if content.try(:width).present?
|
51
|
+
body['duration'] = content.duration if content.try(:duration).present?
|
37
52
|
end
|
38
53
|
|
39
54
|
def iiif_endpoint
|
40
|
-
|
55
|
+
content.try(:iiif_endpoint)
|
41
56
|
end
|
42
57
|
|
43
58
|
def image_service_builder
|
@@ -45,7 +60,7 @@ module IIIFManifest
|
|
45
60
|
end
|
46
61
|
|
47
62
|
def auth_service
|
48
|
-
|
63
|
+
content.try(:auth_service)
|
49
64
|
end
|
50
65
|
|
51
66
|
def apply_auth_service
|
@@ -2,28 +2,35 @@ module IIIFManifest
|
|
2
2
|
module V3
|
3
3
|
class ManifestBuilder
|
4
4
|
class CanvasBuilder
|
5
|
-
attr_reader :record, :parent, :iiif_canvas_factory, :content_builder,
|
6
|
-
:
|
5
|
+
attr_reader :record, :parent, :iiif_canvas_factory, :content_builder, :choice_builder,
|
6
|
+
:annotation_content_builder, :iiif_annotation_page_factory,
|
7
|
+
:thumbnail_builder_factory, :placeholder_canvas_builder_factory
|
7
8
|
|
8
9
|
def initialize(record,
|
9
10
|
parent,
|
10
11
|
iiif_canvas_factory:,
|
11
12
|
content_builder:,
|
12
13
|
choice_builder:,
|
14
|
+
annotation_content_builder:,
|
13
15
|
iiif_annotation_page_factory:,
|
14
|
-
thumbnail_builder_factory
|
16
|
+
thumbnail_builder_factory:,
|
17
|
+
placeholder_canvas_builder_factory:)
|
15
18
|
@record = record
|
16
19
|
@parent = parent
|
17
20
|
@iiif_canvas_factory = iiif_canvas_factory
|
18
21
|
@content_builder = content_builder
|
19
22
|
@choice_builder = choice_builder
|
23
|
+
@annotation_content_builder = annotation_content_builder
|
20
24
|
@iiif_annotation_page_factory = iiif_annotation_page_factory
|
21
25
|
@thumbnail_builder_factory = thumbnail_builder_factory
|
26
|
+
@placeholder_canvas_builder_factory = placeholder_canvas_builder_factory
|
22
27
|
apply_record_properties
|
23
28
|
# Presentation 2.x approach
|
24
29
|
attach_image if display_image
|
25
30
|
# Presentation 3.0 approach
|
26
31
|
attach_content if display_content
|
32
|
+
attach_annotation if annotation_content
|
33
|
+
attach_placeholder_canvas if placeholder_content
|
27
34
|
end
|
28
35
|
|
29
36
|
def canvas
|
@@ -44,22 +51,43 @@ module IIIFManifest
|
|
44
51
|
private
|
45
52
|
|
46
53
|
def display_image
|
47
|
-
record.display_image if record.respond_to?(:display_image)
|
54
|
+
@display_image ||= record.display_image if record.respond_to?(:display_image)
|
48
55
|
end
|
49
56
|
|
50
57
|
# @return [Array<Object>] if the record has a display content
|
51
58
|
# @return [NilClass] if there is no display content
|
52
59
|
def display_content
|
53
|
-
Array.wrap(record.display_content) if record.respond_to?(:display_content)
|
60
|
+
@display_content ||= Array.wrap(record.display_content) if record.respond_to?(:display_content)
|
61
|
+
@display_content.presence
|
54
62
|
end
|
55
63
|
|
64
|
+
# @return [Array<Object>] if the record has generic annotation content
|
65
|
+
# @return [NilClass] if there is no annotation content
|
66
|
+
def annotation_content
|
67
|
+
@annotation_content ||= Array.wrap(record.annotation_content) if record.respond_to?(:annotation_content)
|
68
|
+
@annotation_content.presence
|
69
|
+
end
|
70
|
+
|
71
|
+
def placeholder_content
|
72
|
+
@placeholder_content ||= record.placeholder_content if record.respond_to?(:placeholder_content)
|
73
|
+
end
|
74
|
+
|
75
|
+
# rubocop:disable Metrics/AbcSize
|
56
76
|
def apply_record_properties
|
57
77
|
canvas['id'] = path
|
58
|
-
canvas.label = ManifestBuilder.language_map(record.to_s) if record.to_s.present?
|
59
78
|
annotation_page['id'] = "#{path}/annotation_page/#{annotation_page.index}"
|
60
79
|
canvas.items = [annotation_page]
|
80
|
+
apply_canvas_attributes(canvas)
|
81
|
+
apply_annotation_content_to(canvas)
|
61
82
|
apply_thumbnail_to(canvas)
|
62
|
-
|
83
|
+
end
|
84
|
+
# rubocop:enable Metrics/AbcSize
|
85
|
+
|
86
|
+
def apply_annotation_content_to(canvas)
|
87
|
+
return if annotation_content.blank?
|
88
|
+
|
89
|
+
generic_annotation_page['id'] = "#{path}/annotation_page/#{generic_annotation_page.index}"
|
90
|
+
canvas.annotations = [generic_annotation_page]
|
63
91
|
end
|
64
92
|
|
65
93
|
def apply_thumbnail_to(canvas)
|
@@ -70,10 +98,26 @@ module IIIFManifest
|
|
70
98
|
end
|
71
99
|
end
|
72
100
|
|
101
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
102
|
+
def apply_canvas_attributes(canvas)
|
103
|
+
canvas.label = ManifestBuilder.language_map(record.to_s) if record.to_s.present?
|
104
|
+
canvas.rendering = populate(:rendering) if populate(:rendering).present?
|
105
|
+
canvas.see_also = populate(:see_also) if populate(:see_also).present?
|
106
|
+
canvas.part_of = populate(:part_of) if populate(:part_of).present?
|
107
|
+
canvas.metadata = metadata_from_record(record) if metadata_from_record(record).present?
|
108
|
+
canvas.summary = ManifestBuilder.language_map(record.description) if record.respond_to?(:description) &&
|
109
|
+
record.description.present?
|
110
|
+
end
|
111
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
112
|
+
|
73
113
|
def annotation_page
|
74
114
|
@annotation_page ||= iiif_annotation_page_factory.new
|
75
115
|
end
|
76
116
|
|
117
|
+
def generic_annotation_page
|
118
|
+
@generic_annotation_page ||= iiif_annotation_page_factory.new
|
119
|
+
end
|
120
|
+
|
77
121
|
def attach_image
|
78
122
|
content_builder.new(display_image).apply(canvas)
|
79
123
|
end
|
@@ -86,15 +130,52 @@ module IIIFManifest
|
|
86
130
|
end
|
87
131
|
end
|
88
132
|
|
89
|
-
def
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
133
|
+
def attach_annotation
|
134
|
+
annotation_content.each do |an|
|
135
|
+
annotation = annotation_content_builder.new(an).apply(canvas)
|
136
|
+
generic_annotation_page.items += [annotation]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def attach_placeholder_canvas
|
141
|
+
canvas.placeholder_canvas = placeholder_canvas_builder_factory.new(placeholder_content, path).build
|
142
|
+
end
|
143
|
+
|
144
|
+
def populate(property)
|
145
|
+
property = :sequence_rendering if property == :rendering
|
146
|
+
|
147
|
+
return unless record.respond_to?(property) && record.send(property).present?
|
148
|
+
record.send(property).collect do |prop|
|
149
|
+
output = prop.to_h.except('@id', 'label')
|
150
|
+
output['id'] = prop['@id']
|
151
|
+
output['label'] = ManifestBuilder.language_map(prop['label']) if prop['label'].present?
|
152
|
+
output
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def metadata_from_record(record)
|
157
|
+
return unless valid_v3_metadata?
|
158
|
+
record.item_metadata
|
159
|
+
end
|
160
|
+
|
161
|
+
# Validate item_metadata against the IIIF spec format for metadata
|
162
|
+
#
|
163
|
+
# @return [Boolean]
|
164
|
+
def valid_v3_metadata?
|
165
|
+
return false unless record.respond_to?(:item_metadata)
|
166
|
+
metadata = record.item_metadata
|
167
|
+
valid_v3_metadata_fields?(metadata)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Item metadata must be an array containing hashes
|
171
|
+
#
|
172
|
+
# @param metadata [Array<Hash>] a list of metadata with label and value as required keys for each entry
|
173
|
+
# @return [Boolean]
|
174
|
+
def valid_v3_metadata_fields?(metadata)
|
175
|
+
metadata.is_a?(Array) && metadata.all? do |metadata_field|
|
176
|
+
metadata_field.is_a?(Hash) &&
|
177
|
+
ManifestBuilder.valid_language_map?(metadata_field['label']) &&
|
178
|
+
ManifestBuilder.valid_language_map?(metadata_field['value'])
|
98
179
|
end
|
99
180
|
end
|
100
181
|
end
|
@@ -140,6 +140,44 @@ module IIIFManifest
|
|
140
140
|
def rendering=(rendering)
|
141
141
|
inner_hash['rendering'] = rendering
|
142
142
|
end
|
143
|
+
|
144
|
+
def placeholder_canvas=(placeholder_canvas)
|
145
|
+
return unless placeholder_canvas.present?
|
146
|
+
inner_hash['placeholderCanvas'] = placeholder_canvas
|
147
|
+
end
|
148
|
+
|
149
|
+
def placeholderCanvas
|
150
|
+
inner_hash['placeholderCanvas']
|
151
|
+
end
|
152
|
+
|
153
|
+
def annotations
|
154
|
+
inner_hash['annotations']
|
155
|
+
end
|
156
|
+
|
157
|
+
def annotations=(annotations)
|
158
|
+
inner_hash['annotations'] = annotations
|
159
|
+
end
|
160
|
+
|
161
|
+
def see_also=(see_also)
|
162
|
+
inner_hash['seeAlso'] = see_also
|
163
|
+
end
|
164
|
+
|
165
|
+
def part_of=(part_of)
|
166
|
+
inner_hash['partOf'] = part_of
|
167
|
+
end
|
168
|
+
|
169
|
+
def metadata=(metadata)
|
170
|
+
inner_hash['metadata'] = metadata
|
171
|
+
end
|
172
|
+
|
173
|
+
def summary
|
174
|
+
inner_hash['summary']
|
175
|
+
end
|
176
|
+
|
177
|
+
def summary=(summary)
|
178
|
+
return unless summary.present?
|
179
|
+
inner_hash['summary'] = summary
|
180
|
+
end
|
143
181
|
end
|
144
182
|
|
145
183
|
class Range < IIIFService
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module IIIFManifest
|
2
|
+
module V3
|
3
|
+
class ManifestBuilder
|
4
|
+
class PlaceholderCanvasBuilder
|
5
|
+
attr_reader :placeholder_content, :canvas_path, :iiif_placeholder_canvas_factory, :iiif_annotation_page_factory,
|
6
|
+
:content_builder
|
7
|
+
def initialize(placeholder_content,
|
8
|
+
canvas_path,
|
9
|
+
iiif_placeholder_canvas_factory:,
|
10
|
+
iiif_annotation_page_factory:,
|
11
|
+
content_builder:)
|
12
|
+
@placeholder_content = placeholder_content
|
13
|
+
@canvas_path = canvas_path
|
14
|
+
@iiif_placeholder_canvas_factory = iiif_placeholder_canvas_factory
|
15
|
+
@iiif_annotation_page_factory = iiif_annotation_page_factory
|
16
|
+
@content_builder = content_builder
|
17
|
+
end
|
18
|
+
|
19
|
+
def build
|
20
|
+
return nil if placeholder_content.nil?
|
21
|
+
|
22
|
+
build_placeholder_canvas
|
23
|
+
attach_content
|
24
|
+
|
25
|
+
placeholder_canvas
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def path
|
31
|
+
"#{canvas_path}/placeholder"
|
32
|
+
end
|
33
|
+
|
34
|
+
def placeholder_canvas
|
35
|
+
@placeholder_canvas ||= iiif_placeholder_canvas_factory.new
|
36
|
+
end
|
37
|
+
|
38
|
+
def build_placeholder_canvas
|
39
|
+
placeholder_canvas['id'] = path
|
40
|
+
placeholder_canvas['width'] = placeholder_content.width if placeholder_content.width.present?
|
41
|
+
placeholder_canvas['height'] = placeholder_content.height if placeholder_content.height.present?
|
42
|
+
placeholder_canvas['duration'] = placeholder_content.duration if placeholder_content.duration.present?
|
43
|
+
annotation_page['id'] = "#{path}/annotation_page/#{annotation_page.index}"
|
44
|
+
placeholder_canvas.items = [annotation_page]
|
45
|
+
end
|
46
|
+
|
47
|
+
def attach_content
|
48
|
+
content_builder.new(placeholder_content).apply(placeholder_canvas)
|
49
|
+
end
|
50
|
+
|
51
|
+
def annotation_page
|
52
|
+
@annotation_page ||= iiif_annotation_page_factory.new
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -53,7 +53,7 @@ module IIIFManifest
|
|
53
53
|
manifest.behavior = viewing_hint if viewing_hint.present?
|
54
54
|
manifest.metadata = metadata_from_record(record) if metadata_from_record(record).present?
|
55
55
|
manifest.viewing_direction = viewing_direction if viewing_direction.present?
|
56
|
-
manifest.service = services if
|
56
|
+
manifest.service = services if services.present?
|
57
57
|
manifest.rendering = populate_rendering if populate_rendering.present?
|
58
58
|
homepage = ::IIIFManifest.config.manifest_value_for(record, property: :homepage)
|
59
59
|
manifest.homepage = homepage if homepage.present?
|
@@ -32,7 +32,7 @@ module IIIFManifest
|
|
32
32
|
range['id'] = path
|
33
33
|
range['label'] = ManifestBuilder.language_map(record.label) if record.try(:label).present?
|
34
34
|
range['behavior'] = 'top' if top?
|
35
|
-
range['items'] =
|
35
|
+
range['items'] = file_set_presenters.collect { |fs| { 'type' => 'Canvas', 'id' => build_canvas_id(fs) } }
|
36
36
|
end
|
37
37
|
|
38
38
|
def canvas_builders
|
@@ -69,8 +69,7 @@ module IIIFManifest
|
|
69
69
|
end
|
70
70
|
|
71
71
|
def canvas_range_item(range_item)
|
72
|
-
|
73
|
-
{ 'type' => 'Canvas', 'id' => canvas_builder.path }
|
72
|
+
{ 'type' => 'Canvas', 'id' => build_canvas_id(range_item) }
|
74
73
|
end
|
75
74
|
|
76
75
|
def range_range_item(range_item)
|
@@ -81,6 +80,20 @@ module IIIFManifest
|
|
81
80
|
iiif_range_factory: iiif_range_factory
|
82
81
|
)
|
83
82
|
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def file_set_presenters
|
87
|
+
record.respond_to?(:file_set_presenters) ? record.file_set_presenters : []
|
88
|
+
end
|
89
|
+
|
90
|
+
def build_canvas_id(canvas_item)
|
91
|
+
path = "#{parent.manifest_url}/canvas/#{canvas_item.id}"
|
92
|
+
if canvas_item.respond_to?(:media_fragment) && canvas_item.media_fragment.present?
|
93
|
+
path << "##{canvas_item.media_fragment}"
|
94
|
+
end
|
95
|
+
path
|
96
|
+
end
|
84
97
|
end
|
85
98
|
end
|
86
99
|
end
|
@@ -3,6 +3,8 @@ require_relative 'manifest_builder/canvas_builder'
|
|
3
3
|
require_relative 'manifest_builder/record_property_builder'
|
4
4
|
require_relative 'manifest_builder/choice_builder'
|
5
5
|
require_relative 'manifest_builder/content_builder'
|
6
|
+
require_relative 'manifest_builder/annotation_content_builder'
|
7
|
+
require_relative 'manifest_builder/placeholdercanvas_builder'
|
6
8
|
require_relative 'manifest_builder/body_builder'
|
7
9
|
require_relative 'manifest_builder/structure_builder'
|
8
10
|
require_relative 'manifest_builder/image_service_builder'
|
@@ -56,8 +56,19 @@ module IIIFManifest
|
|
56
56
|
iiif_canvas_factory: iiif_canvas_factory,
|
57
57
|
content_builder: content_builder,
|
58
58
|
choice_builder: choice_builder,
|
59
|
+
annotation_content_builder: annotation_content_builder,
|
59
60
|
iiif_annotation_page_factory: iiif_annotation_page_factory,
|
60
|
-
thumbnail_builder_factory: thumbnail_builder_factory
|
61
|
+
thumbnail_builder_factory: thumbnail_builder_factory,
|
62
|
+
placeholder_canvas_builder_factory: placeholder_canvas_builder_factory
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
def placeholder_canvas_builder_factory
|
67
|
+
IIIFManifest::ManifestServiceLocator::InjectedFactory.new(
|
68
|
+
ManifestBuilder::PlaceholderCanvasBuilder,
|
69
|
+
iiif_placeholder_canvas_factory: iiif_placeholder_canvas_factory,
|
70
|
+
iiif_annotation_page_factory: iiif_annotation_page_factory,
|
71
|
+
content_builder: content_builder
|
61
72
|
)
|
62
73
|
end
|
63
74
|
|
@@ -78,6 +89,14 @@ module IIIFManifest
|
|
78
89
|
)
|
79
90
|
end
|
80
91
|
|
92
|
+
def annotation_content_builder
|
93
|
+
IIIFManifest::ManifestServiceLocator::InjectedFactory.new(
|
94
|
+
ManifestBuilder::AnnotationContentBuilder,
|
95
|
+
iiif_annotation_factory: iiif_annotation_factory,
|
96
|
+
body_builder_factory: body_builder_factory
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
81
100
|
def body_builder_factory
|
82
101
|
IIIFManifest::ManifestServiceLocator::InjectedFactory.new(
|
83
102
|
ManifestBuilder::BodyBuilder,
|
@@ -141,6 +160,10 @@ module IIIFManifest
|
|
141
160
|
IIIFManifest::V3::ManifestBuilder::IIIFManifest::Canvas
|
142
161
|
end
|
143
162
|
|
163
|
+
def iiif_placeholder_canvas_factory
|
164
|
+
IIIFManifest::V3::ManifestBuilder::IIIFManifest::Canvas
|
165
|
+
end
|
166
|
+
|
144
167
|
def iiif_range_factory
|
145
168
|
IIIFManifest::V3::ManifestBuilder::IIIFManifest::Range
|
146
169
|
end
|
data/lib/iiif_manifest/v3.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: iiif_manifest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Coyne
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2024-02-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -159,14 +159,17 @@ files:
|
|
159
159
|
- lib/iiif_manifest/manifest_factory.rb
|
160
160
|
- lib/iiif_manifest/manifest_service_locator.rb
|
161
161
|
- lib/iiif_manifest/v3.rb
|
162
|
+
- lib/iiif_manifest/v3/annotation_content.rb
|
162
163
|
- lib/iiif_manifest/v3/display_content.rb
|
163
164
|
- lib/iiif_manifest/v3/manifest_builder.rb
|
165
|
+
- lib/iiif_manifest/v3/manifest_builder/annotation_content_builder.rb
|
164
166
|
- lib/iiif_manifest/v3/manifest_builder/body_builder.rb
|
165
167
|
- lib/iiif_manifest/v3/manifest_builder/canvas_builder.rb
|
166
168
|
- lib/iiif_manifest/v3/manifest_builder/choice_builder.rb
|
167
169
|
- lib/iiif_manifest/v3/manifest_builder/content_builder.rb
|
168
170
|
- lib/iiif_manifest/v3/manifest_builder/iiif_service.rb
|
169
171
|
- lib/iiif_manifest/v3/manifest_builder/image_service_builder.rb
|
172
|
+
- lib/iiif_manifest/v3/manifest_builder/placeholdercanvas_builder.rb
|
170
173
|
- lib/iiif_manifest/v3/manifest_builder/record_property_builder.rb
|
171
174
|
- lib/iiif_manifest/v3/manifest_builder/structure_builder.rb
|
172
175
|
- lib/iiif_manifest/v3/manifest_builder/thumbnail_builder.rb
|
@@ -192,7 +195,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
192
195
|
- !ruby/object:Gem::Version
|
193
196
|
version: '0'
|
194
197
|
requirements: []
|
195
|
-
rubygems_version: 3.
|
198
|
+
rubygems_version: 3.5.6
|
196
199
|
signing_key:
|
197
200
|
specification_version: 4
|
198
201
|
summary: Generate IIIF presentation manifests for Hydra::Works
|