Compare commits

...

15 Commits

Author SHA1 Message Date
Alexander O'Neill bd327d98b5 959-iiif-width-height-caching Add IIIF service function to get downlaod link with given dimensions. 7 months ago
dannylamb 0b66fba803 Checking for width/height on media first when generating IIIF manifest 7 months ago
Alexander O'Neill fa7f4494b5 959-iiif-width-height-caching Add option to skip retrieveing TIFF and JP2 dimensions from IIIF server. 8 months ago
Alexander O'Neill 6a1c033b99 959-iiif-width-height-caching Add handler for ServerException in Islandora IIIF manifest generator. 8 months ago
Alexander O'Neill ec29a45bde 959-iiif-width-height-caching Islandora IIIF: Add search endpoint config to manifest. 10 months ago
Alexander O'Neill 847fb4f3cf islandora_iiif: Fix Authorization header syntax. 10 months ago
Alexander O'Neill 8151059d7c Islandora IIIF: Address PHPCS errors. 11 months ago
Alexander O'Neill c1b41410ed Islandora IIIF: Address PHPCS errors. 11 months ago
Alexander O'Neill 8c8de83e9d Islandora IIIF: Address PHPCS errors. 11 months ago
Alexander O'Neill 565a1b42b9 Islandora IIIF: Get image dimensions from field on media if they exist.: 11 months ago
Alexander O'Neill 21d468218b Islandora IIIF: Update README. 11 months ago
Alexander O'Neill ba3024d3fd Islandora IIIF: Change media action to node. 11 months ago
Alexander O'Neill 4450248078 Islandora IIIF: Add auth headers to IIIF Info request. 11 months ago
Alexander O'Neill 13b74009de Islandora IIIF: Add action to retrieve image attributes from IIIF server. 11 months ago
Alexander O'Neill 95d3152a66 Refactor IIIF, create IIIF Info Service. 11 months ago
  1. 15
      modules/islandora_iiif/README.md
  2. 10
      modules/islandora_iiif/config/optional/system.action.media_attributes_from_iiif_action.yml
  3. 15
      modules/islandora_iiif/config/schema/islandora_iiif.schema.yml
  4. 18
      modules/islandora_iiif/islandora_iiif.install
  5. 4
      modules/islandora_iiif/islandora_iiif.services.yml
  6. 153
      modules/islandora_iiif/src/IiifInfo.php
  7. 165
      modules/islandora_iiif/src/Plugin/Action/MediaAttributesFromIiif.php
  8. 204
      modules/islandora_iiif/src/Plugin/views/style/IIIFManifest.php

15
modules/islandora_iiif/README.md

@ -38,6 +38,21 @@ This module implements a Views Style plugin. It provides the following settings:
1. Tile Source: A field that was added to the views list of fields with the image to be served. This should be a File or Image type field on a Media. 1. Tile Source: A field that was added to the views list of fields with the image to be served. This should be a File or Image type field on a Media.
2. Structured Text field: This lets you specify a file field where OCR text with positional data, e.g., hOCR can be found. 2. Structured Text field: This lets you specify a file field where OCR text with positional data, e.g., hOCR can be found.
### Media Attributes from IIIF Action
The module also provides an action that lets a site owner populate a TIFF or JP2 image's width and
height attributes into fields so the IIIF server is not bogged down trying to generate a manifest if
it doesn't have them.
To use it, either:
- Add it as a derivative reaction to a node with an Original FIle as its child, or
- Use it as a batch action, such as on a Paged Content object's list of child pages.
The action assumes the media type has fields with machine names of field_height and
field_width. Making this configurable would mean they would not appear
on entity list pages.
## Documentation ## Documentation
Official documentation is available on the [Islandora 8 documentation site](https://islandora.github.io/documentation/). Official documentation is available on the [Islandora 8 documentation site](https://islandora.github.io/documentation/).

10
modules/islandora_iiif/config/optional/system.action.media_attributes_from_iiif_action.yml

@ -0,0 +1,10 @@
langcode: en
status: true
dependencies:
module:
- islandora_iiif
id: media_attributes_from_iiif_action
label: 'Media attributes from IIIF'
type: node
plugin: islandora_iiif:media_attributes_from_iiif_action:media
configuration: { }

15
modules/islandora_iiif/config/schema/islandora_iiif.schema.yml

@ -16,3 +16,18 @@ views.style.iiif_manifest:
type: sequence type: sequence
sequence: sequence:
type: string type: string
label: "Tile source field(s)"
iiif_ocr_file_field:
type: sequence
sequence:
type: string:
label: "IIIF hOCR file field"
structured_text_term:
type: string
label: "Structured text term"
getdimensions_from_sewrver:
type: boolean
label: "Retrieve image dimensions from IIIF server"
search_endpoint:
type: string
label: "Search endpoint path"

18
modules/islandora_iiif/islandora_iiif.install

@ -0,0 +1,18 @@
<?php
/**
* @file
* Install/update hook implementations.
*/
use Symfony\Component\Yaml\Yaml;
/**
* Add Media Attributes from IIIF action.
*/
function islandora_iiif_update_92001(&$sandbox) {
$config_id = 'system.action.media_attributes_from_iiif_action';
$config_path = \Drupal::service('extension.list.module')->getPath('islandora_iiif') . '/config/optional/' . $config_id . '.yml';
$data = Yaml::parseFile($config_path);
\Drupal::configFactory()->getEditable($config_id)->setData($data)->save(TRUE);
}

4
modules/islandora_iiif/islandora_iiif.services.yml

@ -0,0 +1,4 @@
services:
islandora_iiif:
class: Drupal\islandora_iiif\IiifInfo
arguments: ['@config.factory', '@http_client', '@logger.channel.islandora', '@jwt.authentication.jwt']

153
modules/islandora_iiif/src/IiifInfo.php

@ -0,0 +1,153 @@
<?php
namespace Drupal\islandora_iiif;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\file\FileInterface;
use Drupal\jwt\Authentication\Provider\JwtAuth;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\ServerException;
/**
* Get IIIF related info for a given File or Image entity.
*/
class IiifInfo {
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The HTTP client.
*
* @var \GuzzleHttp\Client
*/
protected $httpClient;
/**
* This module's config.
*
* @var \Drupal\Core\Config\ImmutableConfig
*/
protected $iiifConfig;
/**
* JWT Auth provider service.
*
* @var \Drupal\jwt\Authentication\Provider\JwtAuth
*/
protected $jwtAuth;
/**
* The logger.
*
* @var \Drupal\Core\Logger\LoggerChannelInterface
*/
protected $logger;
/**
* Constructs an IiifInfo object.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
* @param \Guzzle\Http\Client $http_client
* The HTTP Client.
* @param \Drupal\Core\Logger\LoggerChannelInterface $channel
* Logger channel.
* @param \Drupal\jwt\Authentication\Provider\JwtAuth $jwt_auth
* The JWT auth provider.
*/
public function __construct(ConfigFactoryInterface $config_factory, Client $http_client, LoggerChannelInterface $channel, JwtAuth $jwt_auth) {
$this->configFactory = $config_factory;
$this->iiifConfig = $this->configFactory->get('islandora_iiif.settings');
$this->httpClient = $http_client;
$this->logger = $channel;
$this->jwtAuth = $jwt_auth;
}
/**
* The IIIF base URL for an image.
*
* Visiting this URL will resolve to the info.json for the image.
*
* @return string
* The absolute URL on the IIIF server.
*/
public function baseUrl($image) {
if ($this->iiifConfig->get('use_relative_paths')) {
$file_url = ltrim($image->createFileUrl(TRUE), '/');
}
else {
$file_url = $image->createFileUrl(FALSE);
}
$iiif_address = $this->iiifConfig->get('iiif_server');
$iiif_url = rtrim($iiif_address, '/') . '/' . urlencode($file_url);
return $iiif_url;
}
/**
* Retrieve an image's original dimensions via the IIIF server.
*
* @param \Drupal\File\FileInterface $file
* The image file.
*
* @return array|false
* The image dimensions in an array as [$width, $height]
*/
public function getImageDimensions(FileInterface $file) {
$iiif_url = $this->baseUrl($file);
try {
$info_json = $this->httpClient->request('get', $iiif_url, [
'headers' => [
'Authorization' => 'Bearer ' . $this->jwtAuth->generateToken(),
],
])->getBody();
$resource = json_decode($info_json, TRUE);
$width = $resource['width'];
$height = $resource['height'];
if (is_numeric($width) && is_numeric($height)) {
return [intval($width), intval($height)];
}
}
catch (ClientException | ConnectException | ServerException $e) {
$this->logger->info("Error getting image file dimensions from IIIF server: " . $e->getMessage());
}
return FALSE;
}
/**
* The IIIF base URL for an image.
*
* Visiting this URL will resolve to the full image resized to the maximum dimensions given.
*
* @see https://iiif.io/api/image/2.1/
*
* @param Drupal\file\FileInterface $image
* The image entity.
* @param int width
* The maximum width of the image to be returned. 0 for no constraint.
* @param int $height
* The maxim um height of the image to be returned. 0 for no contraint.
*
* @return string
* The IIIF URl to retrieve the full image with the given max dimensions.
*/
public function getImageWithMaxDimensions($image, $width = 0, $height = 0) {
$base_url = $this->baseUrl($image);
return $base_url . "/full/!$width,$height/0/default.jpg";
}
}

165
modules/islandora_iiif/src/Plugin/Action/MediaAttributesFromIiif.php

@ -0,0 +1,165 @@
<?php
namespace Drupal\islandora_iiif\Plugin\Action;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Action\Plugin\Action\SaveAction;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\islandora\IslandoraUtils;
use Drupal\islandora\MediaSource\MediaSourceService;
use Drupal\islandora_iiif\IiifInfo;
use GuzzleHttp\Client;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides an action that can save any entity.
*
* @Action(
* id = "islandora_iiif:media_attributes_from_iiif_action",
* action_label = @Translation("Add image dimensions retrieved from the IIIF server"),
* deriver = "Drupal\Core\Action\Plugin\Action\Derivative\EntityChangedActionDeriver",
* )
*/
class MediaAttributesFromIiif extends SaveAction {
/**
* The HTTP client.
*
* @var \GuzzleHttp\Client
*/
protected $httpClient;
/**
* The IIIF Info service.
*
* @var \Drupal\islandora_iiif\IiifInfo
*/
protected $iiifInfo;
/**
* The logger.
*
* @var \Drupal\Core\Logger\LoggerChannelInterface
*/
protected $logger;
/**
* Islandora utility functions.
*
* @var \Drupal\islandora\IslandoraUtils
*/
protected $utils;
/**
* A MediaSourceService.
*
* @var \Drupal\islandora\MediaSource\MediaSourceService
*/
protected $mediaSource;
/**
* Constructs a TiffMediaSaveAction object.
*
* @param mixed[] $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Component\Datetime\TimeInterface $time
* The time service.
* @param \Guzzle\Http\Client $http_client
* The HTTP Client.
* @param \Drupal\islandora_iiif\IiifInfo $iiif_info
* The IIIF INfo service.
* @param \Drupal\islandora\IslandoraUtils $islandora_utils
* Islandora utility functions.
* @param \Drupal\islandora\MediaSource\MediaSourceService $media_source
* Islandora media service.
* @param \Drupal\Core\Logger\LoggerChannelInterface $channel
* Logger channel.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, TimeInterface $time, Client $http_client, IiifInfo $iiif_info, IslandoraUtils $islandora_utils, MediaSourceService $media_source, LoggerChannelInterface $channel) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $time);
$this->httpClient = $http_client;
$this->iiifInfo = $iiif_info;
$this->utils = $islandora_utils;
$this->mediaSource = $media_source;
$this->logger = $channel;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_type.manager'),
$container->get('datetime.time'),
$container->get('http_client'),
$container->get('islandora_iiif'),
$container->get('islandora.utils'),
$container->get('islandora.media_source_service'),
$container->get('logger.channel.islandora')
);
}
/**
* {@inheritdoc}
*/
public function execute($entity = NULL) {
$width = $height = FALSE;
// Get the original File media use term.
$original_file_term = $this->utils->getTermForUri('http://pcdm.org/use#OriginalFile');
/**
* @var \Drupal\media\MediaInterface $original_file_media
*/
$original_file_mids = $this->utils->getMediaReferencingNodeAndTerm($entity, $original_file_term);
if (!empty($original_file_mids)) {
// Ordinarily there shouldn't be more than one Original File media but
// it's not guaranteed.
foreach ($original_file_mids as $original_file_mid) {
/**
* @var \Drupal\Media\MediaInterface $original_file_media
*/
$original_file_media = $this->entityTypeManager->getStorage('media')->load($original_file_mid);
// Get the media MIME Type.
$original_file = $this->mediaSource->getSourceFile($original_file_media);
$mime_type = $original_file->getMimeType();
if (in_array($mime_type, ['image/tiff', 'image/jp2'])) {
[$width, $height] = $this->iiifInfo->getImageDimensions($original_file);
}
// @todo Make field configurable. Low priority since this whole thing is a workaround for an Islandora limitation.
if ($original_file_media->hasField('field_width') && $original_file_media->hasField('field_height')) {
$original_file_media->set('field_height', $height);
$original_file_media->set('field_width', $width);
$original_file_media->save();
}
}
}
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\Core\Entity\EntityInterface $object */
return $object->access('update', $account, $return_as_object);
}
}

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

@ -6,20 +6,20 @@ use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Field\FieldItemInterface; use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface; use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Url; use Drupal\Core\Url;
use Drupal\media\Entity\Media;
use Drupal\islandora\IslandoraUtils;
use Drupal\islandora_iiif\IiifInfo;
use Drupal\views\Plugin\views\style\StylePluginBase; use Drupal\views\Plugin\views\style\StylePluginBase;
use Drupal\views\ResultRow; use Drupal\views\ResultRow;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\ServerException;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Serializer\SerializerInterface;
/** /**
* Provide serializer format for IIIF Manifest. * Provide serializer format for IIIF Manifest.
@ -35,6 +35,14 @@ use Symfony\Component\HttpFoundation\Request;
*/ */
class IIIFManifest extends StylePluginBase { class IIIFManifest extends StylePluginBase {
/**
* Islandora utility functions.
*
* @var \Drupal\islandora\IslandoraUtils
*/
protected $utils;
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -59,6 +67,13 @@ class IIIFManifest extends StylePluginBase {
*/ */
protected $serializer; protected $serializer;
/**
* The IIIF Info service.
*
* @var \Drupal\islandora_iiif\IiifInfo
*/
protected $iiifInfo;
/** /**
* The request service. * The request service.
* *
@ -104,7 +119,7 @@ class IIIFManifest extends StylePluginBase {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function __construct(array $configuration, $plugin_id, $plugin_definition, SerializerInterface $serializer, Request $request, ImmutableConfig $iiif_config, EntityTypeManagerInterface $entity_type_manager, FileSystemInterface $file_system, Client $http_client, MessengerInterface $messenger, ModuleHandlerInterface $moduleHandler) { public function __construct(array $configuration, $plugin_id, $plugin_definition, SerializerInterface $serializer, Request $request, ImmutableConfig $iiif_config, EntityTypeManagerInterface $entity_type_manager, FileSystemInterface $file_system, Client $http_client, MessengerInterface $messenger, ModuleHandlerInterface $moduleHandler, IslandoraUtils $utils, IiifInfo $iiif_info) {
parent::__construct($configuration, $plugin_id, $plugin_definition); parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->serializer = $serializer; $this->serializer = $serializer;
@ -115,6 +130,8 @@ class IIIFManifest extends StylePluginBase {
$this->httpClient = $http_client; $this->httpClient = $http_client;
$this->messenger = $messenger; $this->messenger = $messenger;
$this->moduleHandler = $moduleHandler; $this->moduleHandler = $moduleHandler;
$this->utils = $utils;
$this->iiifInfo = $iiif_info;
} }
/** /**
@ -132,7 +149,9 @@ class IIIFManifest extends StylePluginBase {
$container->get('file_system'), $container->get('file_system'),
$container->get('http_client'), $container->get('http_client'),
$container->get('messenger'), $container->get('messenger'),
$container->get('module_handler') $container->get('module_handler'),
$container->get('islandora.utils'),
$container->get('islandora_iiif')
); );
} }
@ -163,6 +182,11 @@ class IIIFManifest extends StylePluginBase {
$content_path = implode('/', $url_components); $content_path = implode('/', $url_components);
$iiif_base_id = $request_host . '/' . $content_path; $iiif_base_id = $request_host . '/' . $content_path;
/**
* @var \Drupal\taxonomy\TermInterface|null
*/
$structured_text_term = $this->utils->getTermForUri($this->options['structured_text_term_uri']);
// @see https://iiif.io/api/presentation/2.1/#manifest // @see https://iiif.io/api/presentation/2.1/#manifest
$json += [ $json += [
'@type' => 'sc:Manifest', '@type' => 'sc:Manifest',
@ -182,7 +206,7 @@ class IIIFManifest extends StylePluginBase {
// For each row in the View result. // For each row in the View result.
foreach ($this->view->result as $row) { foreach ($this->view->result as $row) {
// Add the IIIF URL to the image to print out as JSON. // Add the IIIF URL to the image to print out as JSON.
$canvases = $this->getTileSourceFromRow($row, $iiif_address, $iiif_base_id); $canvases = $this->getTileSourceFromRow($row, $iiif_address, $iiif_base_id, $structured_text_term);
foreach ($canvases as $tile_source) { foreach ($canvases as $tile_source) {
$json['sequences'][0]['canvases'][] = $tile_source; $json['sequences'][0]['canvases'][] = $tile_source;
} }
@ -192,6 +216,9 @@ class IIIFManifest extends StylePluginBase {
$content_type = 'json'; $content_type = 'json';
// Add a search endpoint if one is defined
$this->addSearchEndpoint($json, $url_components);
// Give other modules a chance to alter the manifest. // Give other modules a chance to alter the manifest.
$this->moduleHandler->alter('islandora_iiif_manifest', $json, $this); $this->moduleHandler->alter('islandora_iiif_manifest', $json, $this);
@ -208,11 +235,13 @@ class IIIFManifest extends StylePluginBase {
* @param string $iiif_base_id * @param string $iiif_base_id
* The URL for the request, minus the last part of the URL, * The URL for the request, minus the last part of the URL,
* which is likely "manifest". * which is likely "manifest".
* @param \Drupal\taxonomy\TermInterface|null $structured_text_term
* The term that structured text media references, if any.
* *
* @return array * @return array
* List of IIIF URLs to display in the Openseadragon viewer. * List of IIIF URLs to display in the Openseadragon viewer.
*/ */
protected function getTileSourceFromRow(ResultRow $row, $iiif_address, $iiif_base_id) { protected function getTileSourceFromRow(ResultRow $row, $iiif_address, $iiif_base_id, $structured_text_term) {
$canvases = []; $canvases = [];
foreach (array_filter(array_values($this->options['iiif_tile_field'])) as $iiif_tile_field) { foreach (array_filter(array_values($this->options['iiif_tile_field'])) as $iiif_tile_field) {
$viewsField = $this->view->field[$iiif_tile_field]; $viewsField = $this->view->field[$iiif_tile_field];
@ -243,7 +272,10 @@ class IIIFManifest extends StylePluginBase {
$canvas_id = $iiif_base_id . '/canvas/' . $entity->id(); $canvas_id = $iiif_base_id . '/canvas/' . $entity->id();
$annotation_id = $iiif_base_id . '/annotation/' . $entity->id(); $annotation_id = $iiif_base_id . '/annotation/' . $entity->id();
[$width, $height] = $this->getCanvasDimensions($iiif_url, $image, $mime_type); [$width, $height] = $this->getCanvasDimensions($iiif_url, $entity, $image, $mime_type);
if ($width == 0) {
continue;
}
$tmp_canvas = [ $tmp_canvas = [
// @see https://iiif.io/api/presentation/2.1/#canvas // @see https://iiif.io/api/presentation/2.1/#canvas
@ -275,7 +307,7 @@ class IIIFManifest extends StylePluginBase {
], ],
]; ];
if ($ocr_url = $this->getOcrUrl($entity, $row, $i)) { if ($ocr_url = $this->getOcrUrl($entity, $structured_text_term)) {
$tmp_canvas['seeAlso'] = [ $tmp_canvas['seeAlso'] = [
'@id' => $ocr_url, '@id' => $ocr_url,
'format' => 'text/vnd.hocr+html', 'format' => 'text/vnd.hocr+html',
@ -312,42 +344,70 @@ class IIIFManifest extends StylePluginBase {
* @return [string] * @return [string]
* The width and height of the image. * The width and height of the image.
*/ */
protected function getCanvasDimensions(string $iiif_url, FieldItemInterface $image, string $mime_type) { protected function getCanvasDimensions(string $iiif_url, Media $media, FieldItemInterface $image, string $mime_type) {
// If the media has field_height and field_width, return those values.
if ($media->hasField('field_height')
&& !$media->get('field_height')->isEmpty()
&& $media->get('field_height')->value > 0
&& $media->hasField('field_width')
&& !$media->get('field_width')->isEmpty()
&& $media->get('field_width')->value > 0) {
return [intval($media->get('field_width')->value),
intval($media->get('field_height')->value),
];
}
// Otherwise start looking at the field/file level for the numbers.
if (isset($image->width) && is_numeric($image->width) if (isset($image->width) && is_numeric($image->width)
&& isset($image->height) && is_numeric($image->height)) { && isset($image->height) && is_numeric($image->height)) {
return [intval($image->width), intval($image->height)]; return [intval($image->width),
intval($image->height),
];
} }
try { if ($properties = $image->getProperties()
$info_json = $this->httpClient->get($iiif_url)->getBody(); && isset($properties['width']) && is_numeric($properties['width'])
$resource = json_decode($info_json, TRUE); && isset($properties['height']) && is_numeric($properties['width'])) {
$width = $resource['width']; return [intval($properties['width']),
$height = $resource['height']; intval($properties['height']),
} ];
catch (ClientException | ServerException | ConnectException $e) { }
// If we couldn't get the info.json from IIIF
// try seeing if we can get it from Drupal. $entity = $image->entity;
if (empty($width) || empty($height)) { if ($entity->hasField('field_height') && !$entity->get('field_height')->isEmpty()
// Get the image properties so we know the image width/height. && $entity->get('field_height')->value > 0
$properties = $image->getProperties(); && $entity->hasField('field_width')
$width = isset($properties['width']) ? $properties['width'] : 0; && !$entity->get('field_width')->isEmpty()
$height = isset($properties['height']) ? $properties['height'] : 0; && $entity->get('field_width')->value > 0) {
return [$entity->get('field_width')->value,
$entity->get('field_height')->value,
];
}
if ($mime_type === 'image/tiff') {
// If this is a TIFF AND we don't know the width/height // If this is a TIFF AND we don't know the width/height
// see if we can get the image size via PHP's core function. // see if we can get the image size via PHP's core function.
if ($mime_type === 'image/tiff' && !$width || !$height) {
$uri = $image->entity->getFileUri(); $uri = $image->entity->getFileUri();
$path = $this->fileSystem->realpath($uri); $path = $this->fileSystem->realpath($uri);
if (!empty($path)) {
$image_size = getimagesize($path); $image_size = getimagesize($path);
if ($image_size) { if ($image_size) {
$width = $image_size[0]; return [intval($image_size[0]),
$height = $image_size[1]; intval($image_size[1]),
];
} }
} }
} }
// As a last resort, get it from the IIIF server.
// This can be very slow and will fail if there are too many pages.
$dimensions = $this->iiifInfo->getImageDimensions($image->entity);
if ($dimensions !== FALSE) {
return $dimensions;
} }
return [$width, $height];
return [0, 0];
} }
/** /**
@ -355,29 +415,37 @@ class IIIFManifest extends StylePluginBase {
* *
* @param \Drupal\Core\Entity\EntityInterface $entity * @param \Drupal\Core\Entity\EntityInterface $entity
* The entity at the current row. * The entity at the current row.
* @param \Drupal\views\ResultRow $row * @param \Drupal\taxonomy\TermInterface|null $structured_text_term
* Result row. * The term that structured text media references, if any.
* @param int $delta
* The delta in case there are multiple canvases on one media.
* *
* @return string|false * return String|FALSE
* The absolute URL of the current row's structured text, * The absolute URL of the current row's structured text,
* or FALSE if none. * or FALSE if none.
*/ */
protected function getOcrUrl(EntityInterface $entity, ResultRow $row, $delta) { protected function getOcrUrl(EntityInterface $entity, $structured_text_term) {
$ocr_url = FALSE; $ocr_url = FALSE;
$iiif_ocr_file_field = !empty($this->options['iiif_ocr_file_field']) ? array_filter(array_values($this->options['iiif_ocr_file_field'])) : []; $iiif_ocr_file_field = !empty($this->options['iiif_ocr_file_field']) ? array_filter(array_values($this->options['iiif_ocr_file_field'])) : [];
$ocrField = count($iiif_ocr_file_field) > 0 ? $this->view->field[$iiif_ocr_file_field[0]] : NULL; $ocrField = count($iiif_ocr_file_field) > 0 ? $this->view->field[$iiif_ocr_file_field[0]] : NULL;
if ($ocrField) { if ($ocrField) {
$ocr_entity = $ocrField->getEntity($row); $ocr_entity = $entity;
$ocr_field_name = $ocrField->definition['field_name']; $ocr_field_name = $ocrField->definition['field_name'];
if (!is_null($ocr_field_name)) { if (!is_null($ocr_field_name)) {
$ocrs = $ocr_entity->{$ocr_field_name}; $ocrs = $ocr_entity->{$ocr_field_name};
$ocr = isset($ocrs[$delta]) ? $ocrs[$delta] : FALSE; $ocr = $ocrs[0] ?? FALSE;
if ($ocr) {
$ocr_url = $ocr->entity->createFileUrl(FALSE); $ocr_url = $ocr->entity->createFileUrl(FALSE);
} }
} }
elseif ($structured_text_term) {
$parent_node = $this->utils->getParentNode($entity);
$ocr_entity_array = $this->utils->getMediaReferencingNodeAndTerm($parent_node, $structured_text_term);
$ocr_entity_id = is_array($ocr_entity_array) ? array_shift($ocr_entity_array) : NULL;
$ocr_entity = $ocr_entity_id ? $this->entityTypeManager->getStorage('media')->load($ocr_entity_id) : NULL;
if ($ocr_entity) {
$ocr_file_source = $ocr_entity->getSource();
$ocr_fid = $ocr_file_source->getSourceFieldValue($ocr_entity);
$ocr_file = $this->entityTypeManager->getStorage('file')->load($ocr_fid);
$ocr_url = $ocr_file->createFileUrl(FALSE);
}
} }
return $ocr_url; return $ocr_url;
@ -411,6 +479,23 @@ class IIIFManifest extends StylePluginBase {
return $entity_title; return $entity_title;
} }
protected function addSearchEndpoint(array &$json, array $url_components) {
$url_base = $this->getRequest()->getSchemeAndHttpHost();
$hocr_search_path = $this->options['search_endpoint'];
$hocr_search_url = $url_base . '/' . ltrim($hocr_search_path, '/');
$hocr_search_url = str_replace('%node', $url_components[1], $hocr_search_url);
$json['service'][] = [
"@context" => "http://iiif.io/api/search/0/context.json",
"@id" => $hocr_search_url,
"profile" => "http://iiif.io/api/search/0/search",
"label" => t("Search inside this work"),
];
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -479,10 +564,26 @@ class IIIFManifest extends StylePluginBase {
'#title' => $this->t('Structured OCR data file field'), '#title' => $this->t('Structured OCR data file field'),
'#type' => 'checkboxes', '#type' => 'checkboxes',
'#default_value' => $this->options['iiif_ocr_file_field'], '#default_value' => $this->options['iiif_ocr_file_field'],
'#description' => $this->t('The source of structured OCR text for each entity.'), '#description' => $this->t('The source of structured OCR text for each entity. If the term setting below is left blank, it will be the same entity as the source image'),
'#options' => $field_options, '#options' => $field_options,
'#required' => FALSE, '#required' => FALSE,
]; ];
$form['structured_text_term'] = [
'#type' => 'entity_autocomplete',
'#target_type' => 'taxonomy_term',
'#title' => $this->t('Structured OCR text term'),
'#default_value' => $this->utils->getTermForUri($this->options['structured_text_term_uri']),
'#required' => FALSE,
'#description' => $this->t('Term indicating the media that holds structured text, such as hOCR, for the given object. Use this if the text is on a separate media from the tile source.'),
];
$form['search_endpoint'] = [
'#type' => 'textfield',
'#title' => $this->t("Search endpoint path."),
'#description' => $this->t("If there is a search endpoint to search within the book that returns IIIF annotations, put it here. Use substitutions %node and %keywords.<br>E.g., paged-content-search/%node?search-in-pages=%keywords"),
'#default_value' => $this->options['search_endpoint'],
'#required' => FALSE,
];
} }
/** /**
@ -495,4 +596,25 @@ class IIIFManifest extends StylePluginBase {
return ['json' => 'json']; return ['json' => 'json'];
} }
/**
* Submit handler for options form.
*
* Used to store the structured text media term by URL instead of Ttid.
*
* @param array $form
* The form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state object.
*/
// @codingStandardsIgnoreStart
public function submitOptionsForm(&$form, FormStateInterface $form_state) {
// @codingStandardsIgnoreEnd
$style_options = $form_state->getValue('style_options');
$tid = $style_options['structured_text_term'];
$term = $this->entityTypeManager->getStorage('taxonomy_term')->load($tid);
$style_options['structured_text_term_uri'] = $this->utils->getUriForTerm($term);
$form_state->setValue('style_options', $style_options);
parent::submitOptionsForm($form, $form_state);
}
} }

Loading…
Cancel
Save