Skip to content

Commit 285ba5b

Browse files
authored
Fix IIIF manifest output for IIIF Image API v3 (#1102)
* Fix IIIF manifest Image API v2/v3 service metadata and add regression tests * Add 'functional' to test-suite in workflow * remove duplicate
1 parent 66ab1fb commit 285ba5b

4 files changed

Lines changed: 186 additions & 8 deletions

File tree

.github/workflows/build-2.x.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
fail-fast: false
2020
matrix:
2121
php-versions: ["8.3", "8.4"]
22-
test-suite: ["kernel", "functional", "functional-javascript"]
22+
test-suite: ["kernel", "unit", "functional", "functional-javascript"]
2323
drupal-version: ["10.5", "10.6", "11.2", "11.3"]
2424
name: PHP ${{ matrix.php-versions }} | drupal ${{ matrix.drupal-version }} | test-suite ${{ matrix.test-suite }}
2525
steps:

modules/islandora_iiif/src/Plugin/views/style/IIIFManifest.php

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,9 @@ protected function getTileSourceFromRow(ResultRow $row, $iiif_address, $iiif_bas
332332

333333
$mime_type = $image->entity->getMimeType();
334334
$iiif_url = rtrim($iiif_address, '/') . '/' . urlencode($file_url);
335+
$full_image_url = $this->buildFullImageUrl($iiif_url);
336+
$thumbnail_url = $this->buildThumbnailUrl($iiif_url);
337+
$iiif_service = $this->getImageServiceDescriptor($iiif_url);
335338

336339
// Create the necessary ID's for the canvas and annotation.
337340
$canvas_id = $iiif_base_id . '/canvas/' . $entity->id();
@@ -357,20 +360,24 @@ protected function getTileSourceFromRow(ResultRow $row, $iiif_address, $iiif_bas
357360
"@type" => "oa:Annotation",
358361
'motivation' => 'sc:painting',
359362
'resource' => [
360-
'@id' => $iiif_url . '/full/full/0/default.jpg',
363+
'@id' => $full_image_url,
361364
"@type" => "dctypes:Image",
362-
'format' => $mime_type,
365+
// The painted resource is the JPEG derivative, not the
366+
// original source MIME type.
367+
'format' => 'image/jpeg',
363368
'height' => $height,
364369
'width' => $width,
365-
'service' => [
366-
'@id' => $iiif_url,
367-
'@context' => 'http://iiif.io/api/image/2/context.json',
368-
'profile' => 'http://iiif.io/api/image/2/profiles/level2.json',
369-
],
370+
'service' => $iiif_service,
370371
],
371372
'on' => $canvas_id,
372373
],
373374
],
375+
'thumbnail' => [
376+
'@id' => $thumbnail_url,
377+
'@type' => 'dctypes:Image',
378+
'format' => 'image/jpeg',
379+
'service' => $iiif_service,
380+
],
374381
];
375382

376383
// Canvas label field to override the default media label.
@@ -500,6 +507,58 @@ protected function getCanvasDimensions(string $iiif_url, MediaInterface $media,
500507
return [0, 0];
501508
}
502509

510+
/**
511+
* Build a IIIF Image API service descriptor from the service URL.
512+
*
513+
* @param string $iiif_url
514+
* The IIIF image service URL.
515+
*
516+
* @return array
517+
* A service block for the manifest image resource.
518+
*/
519+
protected function getImageServiceDescriptor(string $iiif_url): array {
520+
$service = [
521+
'@id' => $iiif_url,
522+
'@context' => 'http://iiif.io/api/image/2/context.json',
523+
'profile' => 'http://iiif.io/api/image/2/profiles/level2.json',
524+
];
525+
526+
if (str_contains($iiif_url, '/iiif/3/')) {
527+
$service['@context'] = 'http://iiif.io/api/image/3/context.json';
528+
$service['profile'] = 'level2';
529+
}
530+
531+
return $service;
532+
}
533+
534+
/**
535+
* Build the painted image URL from a IIIF image service URL.
536+
*
537+
* @param string $iiif_url
538+
* The IIIF image service URL.
539+
*
540+
* @return string
541+
* A full-size image request URL.
542+
*/
543+
protected function buildFullImageUrl(string $iiif_url): string {
544+
return str_contains($iiif_url, '/iiif/3/')
545+
? $iiif_url . '/full/max/0/default.jpg'
546+
: $iiif_url . '/full/full/0/default.jpg';
547+
}
548+
549+
/**
550+
* Build the thumbnail URL from a IIIF image service URL.
551+
*
552+
* @param string $iiif_url
553+
* The IIIF image service URL.
554+
*
555+
* @return string
556+
* A thumbnail image request URL.
557+
*/
558+
protected function buildThumbnailUrl(string $iiif_url): string {
559+
return $iiif_url . '/full/200,/0/default.jpg';
560+
}
561+
503562
/**
504563
* Retrieves a URL text with positional data such as hOCR.
505564
*
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php
2+
3+
namespace Drupal\Tests\islandora_iiif\Unit;
4+
5+
use Drupal\islandora_iiif\Plugin\views\style\IIIFManifest;
6+
use PHPUnit\Framework\TestCase;
7+
8+
/**
9+
* Tests IIIF manifest image service metadata helpers.
10+
*
11+
* @group islandora_iiif
12+
*/
13+
class IIIFManifestTest extends TestCase {
14+
15+
/**
16+
* Creates a manifest plugin instance without invoking the constructor.
17+
*/
18+
protected function createManifestPlugin(): TestableIIIFManifest {
19+
return (new \ReflectionClass(TestableIIIFManifest::class))
20+
->newInstanceWithoutConstructor();
21+
}
22+
23+
/**
24+
* Tests the v2 image service descriptor.
25+
*/
26+
public function testV2ImageServiceDescriptor(): void {
27+
$plugin = $this->createManifestPlugin();
28+
$url = 'https://example.test/iiif/2/example-id';
29+
30+
$service = $plugin->publicGetImageServiceDescriptor($url);
31+
32+
$this->assertSame($url, $service['@id']);
33+
$this->assertSame('http://iiif.io/api/image/2/context.json', $service['@context']);
34+
$this->assertSame('http://iiif.io/api/image/2/profiles/level2.json', $service['profile']);
35+
}
36+
37+
/**
38+
* Tests the v3 image service descriptor.
39+
*/
40+
public function testV3ImageServiceDescriptor(): void {
41+
$plugin = $this->createManifestPlugin();
42+
$url = 'https://example.test/iiif/3/example-id';
43+
44+
$service = $plugin->publicGetImageServiceDescriptor($url);
45+
46+
$this->assertSame($url, $service['@id']);
47+
$this->assertSame('http://iiif.io/api/image/3/context.json', $service['@context']);
48+
$this->assertSame('level2', $service['profile']);
49+
}
50+
51+
/**
52+
* Tests the full image URL for Image API 2.
53+
*/
54+
public function testV2FullImageUrl(): void {
55+
$plugin = $this->createManifestPlugin();
56+
$url = 'https://example.test/iiif/2/example-id';
57+
58+
$this->assertSame(
59+
'https://example.test/iiif/2/example-id/full/full/0/default.jpg',
60+
$plugin->publicBuildFullImageUrl($url)
61+
);
62+
}
63+
64+
/**
65+
* Tests the full image URL for Image API 3.
66+
*/
67+
public function testV3FullImageUrl(): void {
68+
$plugin = $this->createManifestPlugin();
69+
$url = 'https://example.test/iiif/3/example-id';
70+
71+
$this->assertSame(
72+
'https://example.test/iiif/3/example-id/full/max/0/default.jpg',
73+
$plugin->publicBuildFullImageUrl($url)
74+
);
75+
}
76+
77+
/**
78+
* Tests the thumbnail URL.
79+
*/
80+
public function testThumbnailUrl(): void {
81+
$plugin = $this->createManifestPlugin();
82+
$url = 'https://example.test/iiif/3/example-id';
83+
84+
$this->assertSame(
85+
'https://example.test/iiif/3/example-id/full/200,/0/default.jpg',
86+
$plugin->publicBuildThumbnailUrl($url)
87+
);
88+
}
89+
90+
}
91+
92+
/**
93+
* Testable wrapper exposing protected helper methods.
94+
*/
95+
class TestableIIIFManifest extends IIIFManifest {
96+
97+
/**
98+
* Exposes the protected service helper for unit testing.
99+
*/
100+
public function publicGetImageServiceDescriptor(string $iiif_url): array {
101+
return $this->getImageServiceDescriptor($iiif_url);
102+
}
103+
104+
/**
105+
* Exposes the protected full image URL helper for unit testing.
106+
*/
107+
public function publicBuildFullImageUrl(string $iiif_url): string {
108+
return $this->buildFullImageUrl($iiif_url);
109+
}
110+
111+
/**
112+
* Exposes the protected thumbnail URL helper for unit testing.
113+
*/
114+
public function publicBuildThumbnailUrl(string $iiif_url): string {
115+
return $this->buildThumbnailUrl($iiif_url);
116+
}
117+
118+
}

phpunit.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
</php>
5252
<testsuites>
5353
<testsuite name="unit">
54+
<directory>../modules/contrib/islandora/modules/*/tests/src/Unit</directory>
5455
</testsuite>
5556
<testsuite name="kernel">
5657
<directory>../modules/contrib/islandora/tests/src/Kernel</directory>

0 commit comments

Comments
 (0)