Annotation targets
This helper does not require @iiif/vault
to work and the helpers can be used on any loaded Presentation 3 resource.
When working with IIIF Annotations you will come across various types of Annotation targets supported by the W3C Annotation Model. In IIIF most Annotations will target a IIIF Canvas, either the whole Canvas or a region of it. This helper aims to make parsing selectors easier.
It offers the following helpers:
expandTarget
parseSelector
expandTarget()
This helper has the following signature:
function expandTarget(
target: W3CAnnotationTarget | W3CAnnotationTarget[],
options: {
typeMap?: Record<string, string>;
domParser?: DOMParser;
svgPreprocessor?: (svg: string) => string;
} = {}
): SupportedTarget;
You can use this on any annotation.target
property defined in a IIIF Canvas or external Annotation Page.
import { expandTarget } from '@iiif/vault-helpers/annotation-targets';
const expanded = expandTarget("https://example.org/canvas#xywh=1,2,3,4");
/*
{
type: 'BoxSelector',
spatial: {
x: 1,
y: 2,
width: 3,
height: 4,
}
}
*/
In addition to strings, it will also support expanded variations:
{
"type": "SpecificResource",
"source": "https://example.org/canvas",
"selector": {
"type": "FragmentSelector",
"value": "xywh=1,2,3,4"
}
}
parseSelector()
This helper is used by expandTarget()
and is simply a way to parse only the selector and not the full target. For
example:
import { parseSelector } from '@iiif/vault-helpers/annotation-targets';
const expanded = parseSelector({
type: 'FragmentSelector',
value: 'xywh=1,2,3,4'
});
/*
{
type: 'BoxSelector',
spatial: {
x: 1,
y: 2,
width: 3,
height: 4,
}
}
*/
Supported targets
With both helpers, the returned objects will have a custom shape to make it the selectors predictable. This is defined in the Typescript types and should offer completion when using the library in supported editors. Every selector will have the following shape:
interface SupportedSelector {
type: string;
temporal?: {
startTime: number;
endTime?: number;
};
spatial?: {
unit?: 'percent' | 'pixel';
x: number;
y: number;
width?: number;
height?: number;
};
points?: [number, number][];
svg?: string;
svgShape?: SvgShapeType; // 'rect' | 'circle' | 'ellipse' | 'line' | 'polyline' | 'polygon' | 'path'
style?: SelectorStyle; // { fill?: string, strokeWidth: ... }
}
Using this you can progressively support more complex selectors for your viewer. For example, if you support the
.spatial
property to render boxes, you will still see more complex SVG selectors highlighted by a bounding box.
Although this is not fully representative of the selector, it is better than not showing the selector!
All selectors can optionally have a temporal element, indicating that they should only be displayed between the
startTime
and endTime
for Canvases with a duration.
Currently, the following selectors are created by the helper:
BoxSelector
- a non-rotated box, with a non-zerospatial.height
andspatial.width
PointSelector
- a single point without aspatial.height
orspatial.width
SvgSelector
- an SVG shape, with style extracted and a bounding box inspatial
TemporalSelector
- only time selectorTemporalBoxSelector
- BoxSelector with additional temporal property
If you find an unsupported selector, please Raise an issue on GitHub.
If you are using Typescript or an editor that supports types you can narrow the types and get accurate completions:
switch (selector.type) {
case 'BoxSelector':
// selector.spatial.width; // number
case 'PointSelector':
// selector.spatial.width; // undefined
}
BoxSelector
The box selector will be returned when there is a compatible media fragment
selector, the fragment will be parsed - along with the unit (pixel
/ percent
).
Example output:
{
type: 'BoxSelector',
spatial: {
unit: 'pixel',
x: 1,
y: 2,
width: 3,
height: 4,
}
}
PointSelector
The box selector will be returned when there is a compatible media fragment
selector without a height or width, the fragment will be parsed - along with the unit (pixel
/ percent
).
Example output:
{
type: 'PointSelector',
spatial: {
unit: 'pixel',
x: 1,
y: 2,
}
}
SVG Selector
The SVG selector is more complicated than the others. It requires DOMParser
which is available in the browser or can
be installed using happy-dom
or jsdom
for usage in NodeJS.
Given the following SVG:
<svg width="800" height="600" xmlns="http://www.w3.org/2000/svg">
<g>
<rect x="100" y="100" width="300" height="300" stroke="#000" fill="#fff"/>
</g>
</svg>
The following will be returned:
{
points: [
[ 100, 100 ],
[ 400, 100 ],
[ 400, 400 ],
[ 100, 400 ],
[ 100, 100 ]
],
spatial: {
height: 300,
unit: "pixel",
width: 300,
x: 100,
y: 100
},
style: {
fill: "#fff",
stroke: "#000"
},
svg: "<svg width=\"800\" height=\"600\" xmlns=\"http://www.w3.org/2000/svg\"><g><rect x=\"100\" y=\"100\" width=\"300\" height=\"300\"></rect></g></svg>",
svgShape: "rect",
type: "SvgSelector"
}
A lot happened during the parsing of this selector:
- The style was extracted from the SVG - this makes rendering the SVGs directly more predictable.
- A bounding box was detected around the SVG - a fallback, if it is impractical to support full SVGs
- A list of points were extracted - if you are rendering using an HTML Canvas, these can easily be used in draw calls.
- The SVG shape was detected - This may be useful if you want to support a subset of SVG selectors.
This helper has been tested with commonly used Annotation tools that output SVGs.
TemporalSelector
Temporal selectors are common for Audio and Video content in IIIF. They usually use media frags #t=10,20
Temporal selectors do not support time formatted in HH:mm:ss like t=00:00,02:34
. All time fragments are expected
to be in seconds.
{
type: 'TemporalSelector',
temporal: {
startTime: 10,
endTime: 20
}
}
TemporalBoxSelector
This is a combination of the BoxSelector and TemporalSelector, where media frags contain both a spatial and temporal
element. e.g. #t=10,20&xwyh=100,200,300,400
{
type: 'TemporalSelector',
spatial: {
unit: 'pixel',
x: 100,
y: 200,
width: 300,
height: 400,
},
temporal: {
startTime: 10,
endTime: 20
}
}