iiif_manifest 1.3.1 → 1.4.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.
- 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
|