diff --git a/README.md b/README.md index 95712af..6d29858 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,26 @@ To enable this, add the following code to a config file at `config/initializers/ ``` In the above example of a V3 manifest (since it is a `summary` instead of `description`), the `summary` property will be using the model's `#abstract` attribute value instead of the default `#description`. The `rights` property will use the model's `#license` attribute instead of the default `#rights_statement`. All other configurable properties will use their defaults. +```ruby + # Example: use this configuration to set the max edge length of thumbnails, default is 200 + IIIFManifest.confg do |config| + config.max_edge_for_thumbnail = 100 + end +``` +Thumbnails have been added for version 3 manifests because [Universal Viewer](https://github.com/UniversalViewer/universalviewer/issues/102) currently require them to be explicitly set otherwise they would not show up. The above example is used to configure what the default size for the thumbnails would be. + +```ruby +# Example: use this configuration to disable thumbnails to show up by default on the manifest level (version 3 only) + IIIFManifest.confg do |config| + config.manifest_thumbnail = false + end +``` +According to the Presentation API 3.0 [specifications](https://iiif.io/api/presentation/3.0/#thumbnail): +> A Manifest *SHOULD* have the `thumbnail` property with at least one item. + +The above configuration allows you to disable that if desired since it is not a *MUST*. + + # Development After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. diff --git a/lib/iiif_manifest.rb b/lib/iiif_manifest.rb index 419b5f4..ee91198 100644 --- a/lib/iiif_manifest.rb +++ b/lib/iiif_manifest.rb @@ -33,6 +33,18 @@ module IIIFManifest # config.manifest_property_to_record_method_name_map.merge!(summary: :abstract) # end # + # # set max edge length for thumbnail images + # # the below example will set the max edge to 100px + # IIIFManifest.confg do |config| + # config.max_edge_for_thumbnail = 100 + # end + # + # # disable the thumbnail property on the manifest level + # # since it will be shown by default + # IIIFManifest.confg do |config| + # config.manifest_thumbnail = false + # end + # # @yield [IIIFManifest::Configuration] if a block is passed # @return [IIIFManifest::Configuration] # @see IIIFManifest::Configuration for configuration options diff --git a/lib/iiif_manifest/configuration.rb b/lib/iiif_manifest/configuration.rb index 4736d4b..b56f4e9 100644 --- a/lib/iiif_manifest/configuration.rb +++ b/lib/iiif_manifest/configuration.rb @@ -29,6 +29,24 @@ def manifest_property_to_record_method_name_map @manifest_property_to_record_method_name_map ||= DEFAULT_MANIFEST_PROPERTY_TO_RECORD_METHOD_NAME_MAP.dup end + ## + # @param value [Integer] + attr_writer :max_edge_for_thumbnail + # Used to set max edge length for thumbnail generation. + # @return [Integer] + def max_edge_for_thumbnail + return @max_edge_for_thumbnail unless @max_edge_for_thumbnail.nil? + @max_edge_for_thumbnail = 200 + end + + attr_writer :manifest_thumbnail + # Used to configure whether or not to show the manifest thumbnail property. + # @return [Boolean] + def manifest_thumbnail + return @manifest_thumbnail unless @manifest_thumbnail.nil? + @manifest_thumbnail = true + end + ## # @api private # @param record [Object] has the value for the :property we want to set diff --git a/lib/iiif_manifest/display_image.rb b/lib/iiif_manifest/display_image.rb index 7eac4c7..71e9408 100644 --- a/lib/iiif_manifest/display_image.rb +++ b/lib/iiif_manifest/display_image.rb @@ -1,12 +1,14 @@ module IIIFManifest class DisplayImage - attr_reader :url, :width, :height, :iiif_endpoint, :format - def initialize(url, width:, height:, format: nil, iiif_endpoint: nil) + attr_reader :url, :type, :width, :height, :iiif_endpoint, :format, :thumbnail + def initialize(url, width:, height:, format: nil, iiif_endpoint: nil, thumbnail: nil) @url = url + @type = 'Image' @width = width @height = height @format = format @iiif_endpoint = iiif_endpoint + @thumbnail = thumbnail end end end diff --git a/lib/iiif_manifest/v3/display_content.rb b/lib/iiif_manifest/v3/display_content.rb index 42647e2..c0f4385 100644 --- a/lib/iiif_manifest/v3/display_content.rb +++ b/lib/iiif_manifest/v3/display_content.rb @@ -2,18 +2,18 @@ module IIIFManifest module V3 class DisplayContent attr_reader :url, :width, :height, :duration, :iiif_endpoint, :format, :type, - :label, :auth_service - def initialize(url, type:, width: nil, height: nil, duration: nil, label: nil, - format: nil, iiif_endpoint: nil, auth_service: nil) + :label, :auth_service, :thumbnail + def initialize(url, type:, **kwargs) @url = url @type = type - @width = width - @height = height - @duration = duration - @label = label - @format = format - @iiif_endpoint = iiif_endpoint - @auth_service = auth_service + @width = kwargs[:width] + @height = kwargs[:height] + @duration = kwargs[:duration] + @label = kwargs[:label] + @format = kwargs[:format] + @iiif_endpoint = kwargs[:iiif_endpoint] + @auth_service = kwargs[:auth_service] + @thumbnail = kwargs[:thumbnail] end end end diff --git a/lib/iiif_manifest/v3/manifest_builder.rb b/lib/iiif_manifest/v3/manifest_builder.rb index b8273ec..821b121 100644 --- a/lib/iiif_manifest/v3/manifest_builder.rb +++ b/lib/iiif_manifest/v3/manifest_builder.rb @@ -6,6 +6,7 @@ require_relative 'manifest_builder/body_builder' require_relative 'manifest_builder/structure_builder' require_relative 'manifest_builder/image_service_builder' +require_relative 'manifest_builder/thumbnail_builder' module IIIFManifest module V3 diff --git a/lib/iiif_manifest/v3/manifest_builder/canvas_builder.rb b/lib/iiif_manifest/v3/manifest_builder/canvas_builder.rb index 428f233..11bbea7 100644 --- a/lib/iiif_manifest/v3/manifest_builder/canvas_builder.rb +++ b/lib/iiif_manifest/v3/manifest_builder/canvas_builder.rb @@ -3,20 +3,22 @@ module V3 class ManifestBuilder class CanvasBuilder attr_reader :record, :parent, :iiif_canvas_factory, :content_builder, - :choice_builder, :iiif_annotation_page_factory + :choice_builder, :iiif_annotation_page_factory, :thumbnail_builder_factory def initialize(record, parent, iiif_canvas_factory:, content_builder:, choice_builder:, - iiif_annotation_page_factory:) + iiif_annotation_page_factory:, + thumbnail_builder_factory:) @record = record @parent = parent @iiif_canvas_factory = iiif_canvas_factory @content_builder = content_builder @choice_builder = choice_builder @iiif_annotation_page_factory = iiif_annotation_page_factory + @thumbnail_builder_factory = thumbnail_builder_factory apply_record_properties # Presentation 2.x approach attach_image if display_image @@ -45,6 +47,8 @@ def display_image record.display_image if record.respond_to?(:display_image) end + # @return [Array] if the record has a display content + # @return [NilClass] if there is no display content def display_content Array.wrap(record.display_content) if record.respond_to?(:display_content) && record.display_content.present? end @@ -54,6 +58,15 @@ def apply_record_properties canvas.label = ManifestBuilder.language_map(record.to_s) if record.to_s.present? annotation_page['id'] = "#{path}/annotation_page/#{annotation_page.index}" canvas.items = [annotation_page] + apply_thumbnail_to(canvas) + end + + def apply_thumbnail_to(canvas) + if display_image + canvas.thumbnail = Array(thumbnail_builder_factory.new(display_image).build) + elsif display_content.try(:first) + canvas.thumbnail = Array(thumbnail_builder_factory.new(display_content.first).build) + end end def annotation_page diff --git a/lib/iiif_manifest/v3/manifest_builder/iiif_service.rb b/lib/iiif_manifest/v3/manifest_builder/iiif_service.rb index 34ee69b..6585fb5 100644 --- a/lib/iiif_manifest/v3/manifest_builder/iiif_service.rb +++ b/lib/iiif_manifest/v3/manifest_builder/iiif_service.rb @@ -72,6 +72,10 @@ def homepage=(homepage) inner_hash['homepage'] = homepage end + def thumbnail=(thumbnail) + inner_hash['thumbnail'] = thumbnail + end + def initial_attributes { '@context' => [ @@ -119,6 +123,14 @@ def items=(items) inner_hash['items'] = items end + def thumbnail + inner_hash['thumbnail'] + end + + def thumbnail=(thumbnail) + inner_hash['thumbnail'] = thumbnail + end + def initial_attributes { 'type' => 'Canvas' @@ -239,6 +251,17 @@ def initial_attributes } end end + + class Thumbnail < IIIFService + def service=(service) + inner_hash['service'] = service + end + + def initial_attributes + { + } + end + end end end end diff --git a/lib/iiif_manifest/v3/manifest_builder/record_property_builder.rb b/lib/iiif_manifest/v3/manifest_builder/record_property_builder.rb index 480e075..7c6ae48 100644 --- a/lib/iiif_manifest/v3/manifest_builder/record_property_builder.rb +++ b/lib/iiif_manifest/v3/manifest_builder/record_property_builder.rb @@ -2,21 +2,24 @@ module IIIFManifest module V3 class ManifestBuilder class RecordPropertyBuilder < ::IIIFManifest::ManifestBuilder::RecordPropertyBuilder - attr_reader :canvas_builder_factory + attr_reader :canvas_builder_factory, :thumbnail_builder_factory def initialize(record, iiif_search_service_factory:, iiif_autocomplete_service_factory:, - canvas_builder_factory:) + canvas_builder_factory:, + thumbnail_builder_factory:) super(record, iiif_search_service_factory: iiif_search_service_factory, iiif_autocomplete_service_factory: iiif_autocomplete_service_factory) @canvas_builder_factory = canvas_builder_factory + @thumbnail_builder_factory = thumbnail_builder_factory end def apply(manifest) setup_manifest_from_record(manifest, record) # Build the items array canvas_builder.apply(manifest.items) + apply_thumbnail_to(manifest) unless manifest_thumbnail? manifest end @@ -38,7 +41,7 @@ def canvas_builder canvas_builder_factory.from(record) end - # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize, Metrics/MethodLength def setup_manifest_from_record(manifest, record) manifest['id'] = record.manifest_url.to_s label = ::IIIFManifest.config.manifest_value_for(record, property: :label) @@ -55,7 +58,7 @@ def setup_manifest_from_record(manifest, record) homepage = ::IIIFManifest.config.manifest_value_for(record, property: :homepage) manifest.homepage = homepage if homepage.present? end - # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize, Metrics/MethodLength def metadata_from_record(record) if valid_v3_metadata? @@ -96,6 +99,18 @@ def transform_field(field) metadata_field['value'] = ManifestBuilder.language_map(field['value']) metadata_field end + + def apply_thumbnail_to(manifest) + if manifest.is_a? IIIFManifest::Collection + manifest.thumbnail = manifest.items.collect(&:thumbnail).compact + elsif manifest.items.first&.thumbnail.present? + manifest.thumbnail = manifest.items.first&.thumbnail + end + end + + def manifest_thumbnail? + ::IIIFManifest.config.manifest_thumbnail == false + end end end end diff --git a/lib/iiif_manifest/v3/manifest_builder/thumbnail_builder.rb b/lib/iiif_manifest/v3/manifest_builder/thumbnail_builder.rb new file mode 100644 index 0000000..9a53550 --- /dev/null +++ b/lib/iiif_manifest/v3/manifest_builder/thumbnail_builder.rb @@ -0,0 +1,66 @@ +module IIIFManifest + module V3 + class ManifestBuilder + class ThumbnailBuilder + attr_reader :display_content, :iiif_thumbnail_factory, :image_service_builder_factory + def initialize(display_content, iiif_thumbnail_factory:, image_service_builder_factory:) + @display_content = display_content + @iiif_thumbnail_factory = iiif_thumbnail_factory + @image_service_builder_factory = image_service_builder_factory + end + + # @return [Array] + def build + return Array(display_content.thumbnail.map(&:stringify_keys)) unless display_content.thumbnail.nil? + return nil if display_content.type != "Image" || iiif_endpoint.nil? + + build_thumbnail + image_service_builder.apply(thumbnail) + [thumbnail] + end + + private + + def build_thumbnail + thumbnail['id'] = File.join( + display_content.iiif_endpoint.url, + 'full', + "!#{max_edge},#{max_edge}", + '0', + 'default.jpg' + ) + thumbnail['type'] = display_content.type + thumbnail['height'] = (display_content.height * reduction_ratio).round + thumbnail['width'] = (display_content.width * reduction_ratio).round + thumbnail['format'] = display_content.format + end + + def reduction_ratio + width = display_content.width + height = display_content.height + max_edge = @max_edge.to_f + return 1 if width <= max_edge && height <= max_edge + + long_edge = [height, width].max + max_edge / long_edge + end + + def max_edge + @max_edge = ::IIIFManifest.config.max_edge_for_thumbnail + end + + def thumbnail + @thumbnail ||= iiif_thumbnail_factory.new + end + + def iiif_endpoint + display_content.try(:iiif_endpoint) + end + + def image_service_builder + image_service_builder_factory.new(iiif_endpoint) + end + end + end + end +end diff --git a/lib/iiif_manifest/v3/manifest_service_locator.rb b/lib/iiif_manifest/v3/manifest_service_locator.rb index 75f7478..913079c 100644 --- a/lib/iiif_manifest/v3/manifest_service_locator.rb +++ b/lib/iiif_manifest/v3/manifest_service_locator.rb @@ -29,7 +29,8 @@ def record_property_builder ManifestBuilder::RecordPropertyBuilder, iiif_search_service_factory: iiif_search_service_factory, iiif_autocomplete_service_factory: iiif_autocomplete_service_factory, - canvas_builder_factory: deep_canvas_builder_factory + canvas_builder_factory: deep_canvas_builder_factory, + thumbnail_builder_factory: thumbnail_builder_factory # canvas_builder_factory: canvas_builder_factory ) end @@ -55,7 +56,8 @@ def canvas_builder iiif_canvas_factory: iiif_canvas_factory, content_builder: content_builder, choice_builder: choice_builder, - iiif_annotation_page_factory: iiif_annotation_page_factory + iiif_annotation_page_factory: iiif_annotation_page_factory, + thumbnail_builder_factory: thumbnail_builder_factory ) end @@ -84,6 +86,14 @@ def body_builder_factory ) end + def thumbnail_builder_factory + IIIFManifest::ManifestServiceLocator::InjectedFactory.new( + ManifestBuilder::ThumbnailBuilder, + iiif_thumbnail_factory: iiif_thumbnail_factory, + image_service_builder_factory: image_service_builder_factory + ) + end + def image_service_builder_factory IIIFManifest::ManifestServiceLocator::InjectedFactory.new( ManifestBuilder::ImageServiceBuilder, @@ -142,6 +152,10 @@ def iiif_search_service_factory def iiif_autocomplete_service_factory IIIFManifest::V3::ManifestBuilder::IIIFManifest::AutocompleteService end + + def iiif_thumbnail_factory + IIIFManifest::V3::ManifestBuilder::IIIFManifest::Thumbnail + end end end end diff --git a/spec/lib/iiif_manifest/v3/manifest_builder/canvas_builder_spec.rb b/spec/lib/iiif_manifest/v3/manifest_builder/canvas_builder_spec.rb index 5c60bed..6b9fa3f 100644 --- a/spec/lib/iiif_manifest/v3/manifest_builder/canvas_builder_spec.rb +++ b/spec/lib/iiif_manifest/v3/manifest_builder/canvas_builder_spec.rb @@ -12,65 +12,110 @@ iiif_canvas_factory: IIIFManifest::V3::ManifestBuilder::IIIFManifest::Canvas, content_builder: content_builder, choice_builder: IIIFManifest::V3::ManifestBuilder::ChoiceBuilder, - iiif_annotation_page_factory: IIIFManifest::V3::ManifestBuilder::IIIFManifest::AnnotationPage + iiif_annotation_page_factory: IIIFManifest::V3::ManifestBuilder::IIIFManifest::AnnotationPage, + thumbnail_builder_factory: thumbnail_builder_factory ) end - let(:record) { double(id: 'test-22') } + let(:parent) { double(manifest_url: 'http://test.host/books/book-77/manifest') } - describe '#new' do - it 'builds a canvas with a label' do - allow(record).to receive(:to_s).and_return('Test Canvas') - expect(builder.canvas.label).to eq('none' => ['Test Canvas']) - end + after do + Object.send(:remove_const, :MyWork) end - describe '#path' do - it 'returns a canvas url' do - expect(builder.path).to eq 'http://test.host/books/book-77/manifest/canvas/test-22' - end + let(:url) { 'http://example.com/img1.jpg' } + let(:iiif_endpoint) { double('endpoint', url: 'http://example.com/img1') } + let(:display_content) do + IIIFManifest::V3::DisplayContent.new(url, + width: 640, + height: 480, + type: 'Image', + format: 'image/jpeg', + label: 'full', + iiif_endpoint: iiif_endpoint) + end + let(:record) do + MyWork.new(display_content: display_content) + end - context 'when media_fragment is defined' do - before do - allow(record).to receive(:media_fragment).and_return('xywh=160,120,320,240') - end - it 'returns a canvas url' do - expect(builder.path).to eq 'http://test.host/books/book-77/manifest/canvas/test-22#xywh=160,120,320,240' + before do + class MyWork + attr_reader :display_content + + def initialize(display_content:) + @display_content = display_content end - context 'and is blank' do - before do - allow(record).to receive(:media_fragment).and_return(nil) - end - it 'returns a canvas url' do - expect(builder.path).to eq 'http://test.host/books/book-77/manifest/canvas/test-22' - end + def id + 'test-22' end end + + allow(body_builder).to receive(:apply).and_return(iiif_body) + allow(body_builder_factory).to receive(:new).and_return(body_builder) + allow(iiif_annotation_factory).to receive(:new).and_return(iiif_annotation) + allow(content_builder).to receive(:new).and_return(built_content) + allow(thumbnail_builder).to receive(:build).and_return(iiif_thumbnail) + allow(thumbnail_builder_factory).to receive(:new).and_return(thumbnail_builder) end - describe '#canvas' do - let(:record) do - MyWork.new - end + let(:iiif_body) do + body = IIIFManifest::V3::ManifestBuilder::IIIFManifest::Body.new + body['width'] = '100px' + body['height'] = '100px' + body['duration'] = nil + body + end - after do - Object.send(:remove_const, :MyWork) - end + let(:iiif_thumbnail) do + thumbnail = IIIFManifest::V3::ManifestBuilder::IIIFManifest::Thumbnail.new + thumbnail['type'] = 'Image' + thumbnail['width'] = 200 + thumbnail['height'] = 150 + thumbnail['duration'] = nil + thumbnail + end - context 'when the display content is empty for an item' do - before do - class MyWork - def id - 'test-22' - end + let(:iiif_annotation) do + annotation = IIIFManifest::V3::ManifestBuilder::IIIFManifest::Annotation.new + annotation.body = iiif_body + annotation + end - def display_content - [] - end - end - end + let(:iiif_annotation_factory) do + double('IIIF Annotation Factory') + end + + let(:body_builder) do + instance_double(IIIFManifest::V3::ManifestBuilder::BodyBuilder) + end + + let(:body_builder_factory) do + double('Body Builder') + end + + let(:thumbnail_builder) do + instance_double(IIIFManifest::V3::ManifestBuilder::ThumbnailBuilder) + end + + let(:thumbnail_builder_factory) do + double('Thumbnail Builder') + end + + let(:built_content) do + IIIFManifest::V3::ManifestBuilder::ContentBuilder.new( + record.display_content, + iiif_annotation_factory: iiif_annotation_factory, + body_builder_factory: body_builder_factory + ) + end + let(:content_builder) do + double('Content Builder') + end + + describe '#canvas' do + context 'when the display content is populated for a record' do it 'generates the canvas' do canvas = builder.canvas expect(canvas).to be_a IIIFManifest::V3::ManifestBuilder::IIIFManifest::Canvas @@ -78,85 +123,46 @@ def display_content expect(values).to include "type" => "Canvas" expect(values).to include "id" => "http://test.host/books/book-77/manifest/canvas/test-22" + expect(values).to include "items" + + expect(values).to include "thumbnail" + thumbnail = values['thumbnail'].first + expect(thumbnail).to be_a IIIFManifest::V3::ManifestBuilder::IIIFManifest::Thumbnail + thumbnail_values = thumbnail.inner_hash + expect(thumbnail_values).to include "type" => "Image" + expect(thumbnail_values).to include "width" => 200 + expect(thumbnail_values).to include "height" => 150 - expect(values).to include 'items' items = values['items'] expect(items.length).to eq 1 page = items.first expect(page).to be_a IIIFManifest::V3::ManifestBuilder::IIIFManifest::AnnotationPage - expect(page.items).to be_empty + items = page.items + expect(items.length).to eq 1 + annotation = items.first + expect(annotation).to be_a IIIFManifest::V3::ManifestBuilder::IIIFManifest::Annotation + values = annotation.inner_hash + expect(values).to include('body') + body = values['body'] + expect(body).to be_a IIIFManifest::V3::ManifestBuilder::IIIFManifest::Body + values = body.inner_hash + expect(values).to include "width" => "100px" + expect(values).to include "height" => "100px" + expect(values).to include "duration" => nil end end - context 'when the display content is populated for a record' do - let(:url) { 'http://example.com/img1' } - let(:display_content) do - IIIFManifest::V3::DisplayContent.new(url, - width: 640, - height: 480, - type: 'Image', - format: 'image/jpeg', - label: 'full') - end - let(:record) do - MyWork.new(display_content: display_content) - end - + context 'when the display content is empty for an item' do before do class MyWork - attr_reader :display_content - - def initialize(display_content:) - @display_content = display_content - end - def id 'test-22' end - end - - allow(body_builder).to receive(:apply).and_return(iiif_body) - allow(body_builder_factory).to receive(:new).and_return(body_builder) - allow(iiif_annotation_factory).to receive(:new).and_return(iiif_annotation) - allow(content_builder).to receive(:new).and_return(built_content) - end - - let(:iiif_body) do - body = IIIFManifest::V3::ManifestBuilder::IIIFManifest::Body.new - body['width'] = '100px' - body['height'] = '100px' - body['duration'] = nil - body - end - - let(:iiif_annotation) do - annotation = IIIFManifest::V3::ManifestBuilder::IIIFManifest::Annotation.new - annotation.body = iiif_body - annotation - end - - let(:iiif_annotation_factory) do - double - end - let(:body_builder) do - instance_double(IIIFManifest::V3::ManifestBuilder::BodyBuilder) - end - - let(:body_builder_factory) do - double - end - - let(:built_content) do - IIIFManifest::V3::ManifestBuilder::ContentBuilder.new( - record.display_content, - iiif_annotation_factory: iiif_annotation_factory, - body_builder_factory: body_builder_factory - ) - end - - let(:content_builder) do - double + def display_content + [] + end + end end it 'generates the canvas' do @@ -172,18 +178,38 @@ def id expect(items.length).to eq 1 page = items.first expect(page).to be_a IIIFManifest::V3::ManifestBuilder::IIIFManifest::AnnotationPage - items = page.items - expect(items.length).to eq 1 - annotation = items.first - expect(annotation).to be_a IIIFManifest::V3::ManifestBuilder::IIIFManifest::Annotation - values = annotation.inner_hash - expect(values).to include('body') - body = values['body'] - expect(body).to be_a IIIFManifest::V3::ManifestBuilder::IIIFManifest::Body - values = body.inner_hash - expect(values).to include "width" => "100px" - expect(values).to include "height" => "100px" - expect(values).to include "duration" => nil + expect(page.items).to be_empty + end + end + end + + describe '#new' do + it 'builds a canvas with a label' do + allow(record).to receive(:to_s).and_return('Test Canvas') + expect(builder.canvas.label).to eq('none' => ['Test Canvas']) + end + end + + describe '#path' do + it 'returns a canvas url' do + expect(builder.path).to eq 'http://test.host/books/book-77/manifest/canvas/test-22' + end + + context 'when media_fragment is defined' do + before do + allow(record).to receive(:media_fragment).and_return('xywh=160,120,320,240') + end + it 'returns a canvas url' do + expect(builder.path).to eq 'http://test.host/books/book-77/manifest/canvas/test-22#xywh=160,120,320,240' + end + + context 'and is blank' do + before do + allow(record).to receive(:media_fragment).and_return(nil) + end + it 'returns a canvas url' do + expect(builder.path).to eq 'http://test.host/books/book-77/manifest/canvas/test-22' + end end end end diff --git a/spec/lib/iiif_manifest/v3/manifest_builder/thumbnail_builder_spec.rb b/spec/lib/iiif_manifest/v3/manifest_builder/thumbnail_builder_spec.rb new file mode 100644 index 0000000..b84055d --- /dev/null +++ b/spec/lib/iiif_manifest/v3/manifest_builder/thumbnail_builder_spec.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe IIIFManifest::V3::ManifestBuilder::ThumbnailBuilder do + subject(:thumbnail_builder) { builder.build } + let(:builder) do + described_class.new( + display_content, + iiif_thumbnail_factory: IIIFManifest::V3::ManifestBuilder::IIIFManifest::Thumbnail, + image_service_builder_factory: image_service_builder_factory + ) + end + let(:url) { 'http://example.com/img1' } + let(:iiif_endpoint) do + instance_double('endpoint', + url: url, + profile: 'http://iiif.io/api/image/2/level2.json', + context: 'http://iiif.io/api/image/2/context.json') + end + let(:display_content) do + IIIFManifest::DisplayImage.new( + url, + width: 640, + height: 480, + iiif_endpoint: iiif_endpoint, + format: 'image/jpeg' + ) + end + let(:canvas) { IIIFManifest::V3::ManifestBuilder::IIIFManifest::Canvas.new } + let(:image_service_builder_factory) { IIIFManifest::V3::ManifestServiceLocator.image_service_builder_factory } + let(:thumbnail) { thumbnail_builder.first } + + before do + thumbnail_builder + end + + describe '#build' do + it 'sets a thumbnail on the canvas' do + expect(thumbnail).to be_kind_of IIIFManifest::V3::ManifestBuilder::IIIFManifest::Thumbnail + end + end + + describe '#build_thumbnail' do + it 'sets properties on the thumbnail' do + expect(thumbnail['type']).to eq 'Image' + expect(thumbnail['id']).to eq url + '/full/!200,200/0/default.jpg' + expect(thumbnail['width']).to eq 200 + expect(thumbnail['height']).to eq 150 + expect(thumbnail['service']).to be_an Array + service = thumbnail['service'].first + expect(service).to be_kind_of IIIFManifest::V3::ManifestBuilder::IIIFService + expect(service['@id']).to eq iiif_endpoint.url + expect(service['profile']).to eq iiif_endpoint.profile + expect(service['@type']).to eq 'ImageService2' + end + end + + describe '#reduction_ratio' do + context 'when the content is wider' do + it 'creates a multiplier based off the max width of 200' do + allow(display_content).to receive(:width).and_return(2000) + allow(display_content).to receive(:height).and_return(1000) + expect(builder.send(:reduction_ratio)).to eq 0.1 + end + end + + context 'when the content is taller' do + it 'creates a multiplier based off the max height of 200' do + allow(display_content).to receive(:width).and_return(1000) + allow(display_content).to receive(:height).and_return(2000) + expect(builder.send(:reduction_ratio)).to eq 0.1 + end + end + end + + describe '#thumbnail' do + it 'creates a new thumbnail' do + expect(builder.send(:thumbnail)).to be_a IIIFManifest::V3::ManifestBuilder::IIIFManifest::Thumbnail + end + end +end diff --git a/spec/lib/iiif_manifest/v3/manifest_factory_spec.rb b/spec/lib/iiif_manifest/v3/manifest_factory_spec.rb index f0830ba..bd608e1 100644 --- a/spec/lib/iiif_manifest/v3/manifest_factory_spec.rb +++ b/spec/lib/iiif_manifest/v3/manifest_factory_spec.rb @@ -67,7 +67,13 @@ def to_s end def display_image - IIIFManifest::DisplayImage.new(id, width: 100, height: 100, format: 'image/jpeg') + IIIFManifest::DisplayImage.new( + 'test.host/images/image-77/full/600,/0/default.jpg', + width: 2000, + height: 1500, + format: 'image/jpeg', + iiif_endpoint: IIIFManifest::IIIFEndpoint.new('test.host/images/image-77') + ) end end @@ -131,13 +137,11 @@ def display_content it 'returns items' do allow(book_presenter).to receive(:file_set_presenters).and_return([file_presenter]) - result - expect(result['items'].length).to eq 1 expect(result['items'].first['type']).to eq 'Canvas' expect(result['items'].first['id']).to eq 'http://test.host/books/book-77/manifest/canvas/test-22' - expect(result['items'].first['height']).to eq 100 - expect(result['items'].first['width']).to eq 100 + expect(result['items'].first['height']).to eq 1500 + expect(result['items'].first['width']).to eq 2000 expect(result['items'].first['items'].first['type']).to eq 'AnnotationPage' expect(result['items'].first['items'].first['id']).not_to be_empty expect(result['items'].first['items'].first['items'].length).to eq 1 @@ -147,10 +151,21 @@ def display_content expect(result['items'].first['items'].first['items'].first['target']).to eq result['items'].first['id'] expect(result['items'].first['items'].first['items'].first['body']['type']).to eq 'Image' expect(result['items'].first['items'].first['items'].first['body']['id']).not_to be_empty - expect(result['items'].first['items'].first['items'].first['body']['height']).to eq 100 - expect(result['items'].first['items'].first['items'].first['body']['width']).to eq 100 + expect(result['items'].first['items'].first['items'].first['body']['height']).to eq 1500 + expect(result['items'].first['items'].first['items'].first['body']['width']).to eq 2000 expect(result['items'].first['items'].first['items'].first['body']['format']).to eq 'image/jpeg' end + + it 'has a thumbnail property' do + allow(book_presenter).to receive(:file_set_presenters).and_return([file_presenter]) + thumbnail = result['thumbnail'].first + expect(thumbnail['id']).to eq 'test.host/images/image-77/full/!200,200/0/default.jpg' + expect(thumbnail['height']).to eq 150 + expect(thumbnail['width']).to eq 200 + expect(thumbnail['format']).to eq 'image/jpeg' + expect(thumbnail['service'].first).to be_kind_of IIIFManifest::V3::ManifestBuilder::IIIFService + end + it 'builds a structure if it can' do allow(book_presenter).to receive(:file_set_presenters).and_return([file_presenter]) allow(book_presenter.ranges[0].ranges[0]).to receive(:file_set_presenters).and_return([file_presenter]) @@ -174,8 +189,6 @@ def display_content it 'returns items' do allow(book_presenter).to receive(:file_set_presenters).and_return([file_presenter]) - result - expect(result['items'].length).to eq 1 expect(result['items'].first['type']).to eq 'Canvas' expect(result['items'].first['id']).to eq 'http://test.host/books/book-77/manifest/canvas/test-22' @@ -552,6 +565,40 @@ def display_content expect(content_annotation_body['label']).to eq('none' => ['High']) end end + + context 'with thumbnail provided' do + let(:content) do + IIIFManifest::V3::DisplayContent.new(SecureRandom.uuid, + duration: 100, + type: 'Sound', + format: 'audio/mp4', + label: 'High', + thumbnail: [{ id: "test.host/thumbnails/thumb.mp4", + format: "audio/mp4" }]) + end + it 'uses provided thumbnail on manifest and canvas' do + manifest_thumbnail = result['thumbnail'].first + expect(manifest_thumbnail['id']).to eq 'test.host/thumbnails/thumb.mp4' + expect(manifest_thumbnail['format']).to eq 'audio/mp4' + canvas_thumbnail = result.items.first['thumbnail'].first + expect(canvas_thumbnail['id']).to eq 'test.host/thumbnails/thumb.mp4' + expect(canvas_thumbnail['format']).to eq 'audio/mp4' + end + + # rubocop:disable RSpec/NestedGroups + context 'when manifest thumbnail is disabled' do + let(:config) { instance_double(IIIFManifest::Configuration) } + it 'does not have a thumbnail property on the manifest' do + allow(IIIFManifest).to receive(:config).and_return config + allow(config).to receive(:manifest_thumbnail).and_return false + # adding this line to get past Rpsec unexpected message error + allow(config).to receive(:manifest_value_for).and_return 'A good book' + + expect(result.key?('thumbnail')).to be false + end + end + # rubocop:enable RSpec/NestedGroups + end end context 'with multiple files' do