@readium/shared 1.3.0 → 2.1.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.
Files changed (59) hide show
  1. package/dist/index.js +3898 -2382
  2. package/dist/index.umd.cjs +3 -3
  3. package/package.json +3 -1
  4. package/src/publication/AltIdentifier.ts +58 -0
  5. package/src/publication/Contributor.ts +15 -0
  6. package/src/publication/GuidedNavigation.ts +126 -15
  7. package/src/publication/Layout.ts +13 -0
  8. package/src/publication/Link.ts +7 -0
  9. package/src/publication/Metadata.ts +86 -9
  10. package/src/publication/Profiles.ts +15 -0
  11. package/src/publication/Properties.ts +10 -0
  12. package/src/publication/ReadingProgression.ts +2 -15
  13. package/src/publication/TDM.ts +56 -0
  14. package/src/publication/accessibility/Accessibility.ts +860 -0
  15. package/src/publication/accessibility/AccessibilityMetadataDisplayGuide.ts +1055 -0
  16. package/src/publication/accessibility/Localization.md +203 -0
  17. package/src/publication/accessibility/Localization.ts +115 -0
  18. package/src/publication/accessibility/index.ts +3 -0
  19. package/src/publication/accessibility/locales/en.json +231 -0
  20. package/src/publication/accessibility/locales/fr.json +231 -0
  21. package/src/publication/epub/MediaOverlay.ts +43 -0
  22. package/src/publication/epub/Metadata.ts +16 -0
  23. package/src/publication/epub/Properties.ts +0 -10
  24. package/src/publication/epub/index.ts +2 -2
  25. package/src/publication/index.ts +5 -1
  26. package/src/util/JSONParse.ts +3 -3
  27. package/types/src/publication/AltIdentifier.d.ts +23 -0
  28. package/types/src/publication/Contributor.d.ts +4 -0
  29. package/types/src/publication/GuidedNavigation.d.ts +62 -9
  30. package/types/src/publication/Layout.d.ts +8 -0
  31. package/types/src/publication/Link.d.ts +3 -0
  32. package/types/src/publication/Metadata.d.ts +28 -2
  33. package/types/src/publication/Profiles.d.ts +10 -0
  34. package/types/src/publication/Properties.d.ts +6 -0
  35. package/types/src/publication/ReadingProgression.d.ts +2 -5
  36. package/types/src/publication/TDM.d.ts +30 -0
  37. package/types/src/publication/accessibility/Accessibility.d.ts +337 -16
  38. package/types/src/publication/accessibility/AccessibilityMetadataDisplayGuide.d.ts +22 -25
  39. package/types/src/publication/accessibility/Localization.d.ts +45 -0
  40. package/types/src/publication/accessibility/index.d.ts +1 -0
  41. package/types/src/publication/epub/MediaOverlay.d.ts +19 -0
  42. package/types/src/publication/epub/Metadata.d.ts +6 -0
  43. package/types/src/publication/epub/Properties.d.ts +1 -5
  44. package/types/src/publication/epub/index.d.ts +2 -2
  45. package/types/src/publication/index.d.ts +5 -1
  46. package/types/src/util/JSONParse.d.ts +2 -2
  47. package/src/publication/epub/EPUBLayout.ts +0 -10
  48. package/src/publication/epub/Presentation.ts +0 -16
  49. package/src/publication/presentation/Metadata.ts +0 -19
  50. package/src/publication/presentation/Presentation.ts +0 -155
  51. package/src/publication/presentation/Properties.ts +0 -70
  52. package/src/publication/presentation/index.ts +0 -3
  53. package/types/src/publication/accessibility/AccessibilityDisplayString.d.ts +0 -56
  54. package/types/src/publication/epub/EPUBLayout.d.ts +0 -5
  55. package/types/src/publication/epub/Presentation.d.ts +0 -7
  56. package/types/src/publication/presentation/Metadata.d.ts +0 -6
  57. package/types/src/publication/presentation/Presentation.d.ts +0 -95
  58. package/types/src/publication/presentation/Properties.d.ts +0 -32
  59. package/types/src/publication/presentation/index.d.ts +0 -3
@@ -10,6 +10,7 @@ import {
10
10
  setToArray,
11
11
  } from '../util/JSONParse';
12
12
  import { LocalizedString } from './LocalizedString';
13
+ import { AltIdentifier } from './AltIdentifier';
13
14
 
14
15
  /**
15
16
  * Contributor Object for the Readium Web Publication Manifest.
@@ -25,6 +26,9 @@ export class Contributor {
25
26
  /** An unambiguous reference to this contributor. */
26
27
  public readonly identifier?: string;
27
28
 
29
+ /** Alternate identifiers for this contributor. */
30
+ public readonly altIdentifiers?: Set<AltIdentifier>;
31
+
28
32
  /** The role of the contributor in the publication making. */
29
33
  public readonly roles?: Set<string>;
30
34
 
@@ -41,6 +45,7 @@ export class Contributor {
41
45
  name: LocalizedString;
42
46
  sortAs?: string;
43
47
  identifier?: string;
48
+ altIdentifiers?: Set<AltIdentifier>;
44
49
  roles?: Set<string>;
45
50
  links?: Links;
46
51
  position?: number;
@@ -48,6 +53,7 @@ export class Contributor {
48
53
  this.name = values.name;
49
54
  this.sortAs = values.sortAs;
50
55
  this.identifier = values.identifier;
56
+ this.altIdentifiers = values.altIdentifiers;
51
57
  this.roles = values.roles;
52
58
  this.links = values.links;
53
59
  this.position = values.position;
@@ -70,6 +76,14 @@ export class Contributor {
70
76
  name: LocalizedString.deserialize(json.name) as LocalizedString,
71
77
  sortAs: json.sortAs,
72
78
  identifier: json.identifier,
79
+ altIdentifiers: json.altIdentifier
80
+ ? (json.altIdentifier instanceof Array
81
+ ? new Set<AltIdentifier>(json.altIdentifier
82
+ .map((x: string | { value: string; scheme?: string }) => AltIdentifier.deserialize(x))
83
+ .filter((x: AltIdentifier | undefined): x is AltIdentifier => x !== undefined))
84
+ : new Set<AltIdentifier>([AltIdentifier.deserialize(json.altIdentifier as string | { value: string; scheme?: string })]
85
+ .filter((x: AltIdentifier | undefined): x is AltIdentifier => x !== undefined)))
86
+ : undefined,
73
87
  roles: json.role
74
88
  ? new Set<string>(arrayfromJSONorString(json.role))
75
89
  : undefined,
@@ -86,6 +100,7 @@ export class Contributor {
86
100
  const json: any = { name: this.name.serialize() };
87
101
  if (this.sortAs !== undefined) json.sortAs = this.sortAs;
88
102
  if (this.identifier !== undefined) json.identifier = this.identifier;
103
+ if (this.altIdentifiers) json.altIdentifier = setToArray(this.altIdentifiers).map(altId => altId.serialize());
89
104
  if (this.roles) json.role = setToArray(this.roles);
90
105
  if (this.links) json.links = this.links.serialize();
91
106
  if (this.position !== undefined) json.position = this.position;
@@ -43,6 +43,66 @@ export class GuidedNavigationDocument {
43
43
  }
44
44
  }
45
45
 
46
+ /**
47
+ * Represents a text value containing plain text, SSML, and language information.
48
+ */
49
+ export class GuidedNavigationText {
50
+ /** Plain text content */
51
+ public readonly plain?: string;
52
+
53
+ /** SSML (Speech Synthesis Markup Language) content */
54
+ public readonly ssml?: string;
55
+
56
+ /**
57
+ * BCP 47 language tag
58
+ * @pattern ^((?<grandfathered>(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang))|((?<language>([A-Za-z]{2,3}(-(?<extlang>[A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-(?<script>[A-Za-z]{4}))?(-(?<region>[A-Za-z]{2}|[0-9]{3}))?(-(?<variant>[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-(?<extension>[0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(?<privateUse>x(-[A-Za-z0-9]{1,8})+))?)|(?<privateUse2>x(-[A-Za-z0-9]{1,8})+))$
59
+ */
60
+ public readonly language?: string;
61
+
62
+ constructor(values: {
63
+ plain?: string;
64
+ ssml?: string;
65
+ language?: string;
66
+ }) {
67
+ this.plain = values.plain;
68
+ this.ssml = values.ssml;
69
+ this.language = values.language;
70
+ }
71
+
72
+ /**
73
+ * Deserializes a GuidedNavigationText from JSON
74
+ */
75
+ public static deserialize(json: any): GuidedNavigationText | undefined {
76
+ if (json === undefined || json === null) return undefined;
77
+
78
+ if (typeof json === 'string') {
79
+ return new GuidedNavigationText({ plain: json });
80
+ }
81
+
82
+ // Only create if there are actual values
83
+ if (json.plain || json.ssml || json.language) {
84
+ return new GuidedNavigationText({
85
+ plain: json.plain,
86
+ ssml: json.ssml,
87
+ language: json.language
88
+ });
89
+ }
90
+
91
+ return undefined;
92
+ }
93
+
94
+ /**
95
+ * Serializes the text to a plain object
96
+ */
97
+ public serialize(): any {
98
+ const result: any = {};
99
+ if (this.plain !== undefined) result.plain = this.plain;
100
+ if (this.ssml !== undefined) result.ssml = this.ssml;
101
+ if (this.language !== undefined) result.language = this.language;
102
+ return Object.keys(result).length > 0 ? result : undefined;
103
+ }
104
+ }
105
+
46
106
  /**
47
107
  * Guided Navigation Object
48
108
  * https://github.com/readium/guided-navigation/blob/main/schema/object.schema.json
@@ -61,12 +121,27 @@ export class GuidedNavigationObject {
61
121
  /** Convey the structural semantics of a publication. */
62
122
  public readonly role?: Set<string>;
63
123
 
64
- /** Textual equivalent of the resources or fragment of the resources referenced by the current Guided Navigation Object. */
65
- public readonly text?: string;
124
+ /**
125
+ * Indicates the heading level (1-6) for the navigation object.
126
+ * @minimum 1
127
+ * @maximum 6
128
+ */
129
+ public readonly level?: number;
130
+
131
+ /**
132
+ * Textual equivalent of the resources or fragment of the resources referenced by the current Guided Navigation Object.
133
+ */
134
+ public readonly text?: GuidedNavigationText;
66
135
 
67
136
  /** References a textual resource or a fragment of it. */
68
137
  public readonly textref?: string;
69
138
 
139
+ /**
140
+ * Describes the image referenced by the current Guided Navigation Object.
141
+ * This is a GuidedNavigationObject that should not contain 'level' or 'children' properties.
142
+ */
143
+ public readonly description?: Omit<GuidedNavigationObject, 'level' | 'children'>;
144
+
70
145
  /**
71
146
  * Creates a [GuidedNavigation] object.
72
147
  */
@@ -75,41 +150,68 @@ export class GuidedNavigationObject {
75
150
  children?: GuidedNavigationObject[];
76
151
  imgref?: string;
77
152
  role?: Set<string>;
78
- text?: string;
153
+ level?: number;
154
+ text?: GuidedNavigationText;
79
155
  textref?: string;
156
+ description?: Omit<GuidedNavigationObject, 'level' | 'children'>;
80
157
  }) {
81
158
  this.audioref = values.audioref;
82
159
  this.children = values.children;
83
160
  this.imgref = values.imgref;
84
161
  this.role = values.role;
162
+ this.level = values.level !== undefined ? Math.min(6, Math.max(1, values.level)) : undefined;
85
163
  this.text = values.text;
86
164
  this.textref = values.textref;
165
+ this.description = values.description;
87
166
  }
88
167
 
89
168
  /**
90
- * Parses a [GuidedNavigationObject] from its RWPM JSON representation.
91
- *
92
- * A GuidedNavigationObject can be parsed from a single string, or a full-fledged object.
93
- */
169
+ * Gets the plain text content.
170
+ * Returns undefined if no text is available.
171
+ */
172
+ public get plainText(): string | undefined {
173
+ return this.text?.plain;
174
+ }
175
+
176
+ /**
177
+ * Gets the SSML content if available.
178
+ */
179
+ public get ssmlText(): string | undefined {
180
+ return this.text?.ssml;
181
+ }
182
+
183
+ /**
184
+ * Gets the language of the text if available.
185
+ */
186
+ public get textLanguage(): string | undefined {
187
+ return this.text?.language;
188
+ }
189
+
190
+ /**
191
+ * Deserializes a GuidedNavigationObject from JSON
192
+ */
94
193
  public static deserialize(json: any): GuidedNavigationObject | undefined {
95
- if (!json) return;
194
+ if (!json) return undefined;
195
+
96
196
  return new GuidedNavigationObject({
97
197
  audioref: json.audioref,
98
198
  children: GuidedNavigationObject.deserializeArray(json.children),
99
199
  imgref: json.imgref,
100
200
  role: json.role
101
- ? new Set<string>(arrayfromJSONorString(json.role))
102
- : undefined,
103
- text: json.text,
201
+ ? new Set<string>(arrayfromJSONorString(json.role))
202
+ : undefined,
203
+ level: typeof json.level === 'number' ? json.level : undefined,
204
+ text: GuidedNavigationText.deserialize(json.text),
104
205
  textref: json.textref,
206
+ description: GuidedNavigationObject.deserialize(json.description)
105
207
  });
106
208
  }
107
209
 
108
210
  /**
109
211
  * Parses a [GuidedNavigationObject] array from its RWPM JSON representation.
110
- */
212
+ */
111
213
  public static deserializeArray(json: any): GuidedNavigationObject[] | undefined {
112
- if (!(json instanceof Array)) return;
214
+ if (!(json instanceof Array)) return undefined;
113
215
  return json
114
216
  .map<GuidedNavigationObject>((item) => GuidedNavigationObject.deserialize(item) as GuidedNavigationObject)
115
217
  .filter((x) => x !== undefined);
@@ -117,15 +219,24 @@ export class GuidedNavigationObject {
117
219
 
118
220
  /**
119
221
  * Serializes a [GuidedNavigationObject] to its RWPM JSON representation.
120
- */
222
+ */
121
223
  public serialize(): any {
122
224
  const json: any = {};
123
225
  if (this.audioref !== undefined) json.audioref = this.audioref;
124
226
  if (this.children !== undefined) json.children = this.children.map(x => x.serialize());
125
227
  if (this.imgref !== undefined) json.imgref = this.imgref;
126
228
  if (this.role !== undefined) json.role = setToArray(this.role);
127
- if (this.text !== undefined) json.text = this.text;
229
+ if (this.level !== undefined) json.level = this.level;
230
+ if (this.text !== undefined) {
231
+ const serializedText = this.text.serialize();
232
+ if (serializedText !== undefined) {
233
+ json.text = serializedText;
234
+ }
235
+ }
128
236
  if (this.textref !== undefined) json.textref = this.textref;
237
+ if (this.description) {
238
+ json.description = this.description.serialize();
239
+ }
129
240
  return json;
130
241
  }
131
242
 
@@ -0,0 +1,13 @@
1
+ /* Copyright 2025 Readium Foundation. All rights reserved.
2
+ * Use of this source code is governed by a BSD-style license,
3
+ * available in the LICENSE file present in the Github repository of the project.
4
+ */
5
+
6
+ /** Hint about the nature of the layout for the publication.
7
+ * https://readium.org/webpub-manifest/contexts/default/#layout-and-reading-progression
8
+ */
9
+ export enum Layout {
10
+ reflowable = 'reflowable',
11
+ fixed = 'fixed',
12
+ scrolled = 'scrolled'
13
+ }
@@ -42,6 +42,9 @@ export class Link {
42
42
  /** Width of the linked resource in pixels. */
43
43
  public readonly width?: number;
44
44
 
45
+ /** Size of the linked resource in bytes. */
46
+ public readonly size?: number;
47
+
45
48
  /** Length of the linked resource in seconds. */
46
49
  public readonly duration?: number;
47
50
 
@@ -69,6 +72,7 @@ export class Link {
69
72
  properties?: Properties;
70
73
  height?: number;
71
74
  width?: number;
75
+ size?: number;
72
76
  duration?: number;
73
77
  bitrate?: number;
74
78
  languages?: Array<string>;
@@ -83,6 +87,7 @@ export class Link {
83
87
  this.properties = values.properties;
84
88
  this.height = values.height;
85
89
  this.width = values.width;
90
+ this.size = values.size;
86
91
  this.duration = values.duration;
87
92
  this.bitrate = values.bitrate;
88
93
  this.languages = values.languages;
@@ -109,6 +114,7 @@ export class Link {
109
114
  properties: Properties.deserialize(json.properties),
110
115
  height: positiveNumberfromJSON(json.height),
111
116
  width: positiveNumberfromJSON(json.width),
117
+ size: positiveNumberfromJSON(json.size),
112
118
  duration: positiveNumberfromJSON(json.duration),
113
119
  bitrate: positiveNumberfromJSON(json.bitrate),
114
120
  languages: arrayfromJSONorString(json.language),
@@ -129,6 +135,7 @@ export class Link {
129
135
  if (this.properties) json.properties = this.properties.serialize();
130
136
  if (this.height !== undefined) json.height = this.height;
131
137
  if (this.width !== undefined) json.width = this.width;
138
+ if (this.size !== undefined) json.size = this.size;
132
139
  if (this.duration !== undefined) json.duration = this.duration;
133
140
  if (this.bitrate !== undefined) json.bitrate = this.bitrate;
134
141
  if (this.languages) json.language = this.languages;
@@ -1,4 +1,4 @@
1
- /* Copyright 2021 Readium Foundation. All rights reserved.
1
+ /* Copyright 2025 Readium Foundation. All rights reserved.
2
2
  * Use of this source code is governed by a BSD-style license,
3
3
  * available in the LICENSE file present in the Github repository of the project.
4
4
  */
@@ -8,11 +8,16 @@ import {
8
8
  datefromJSON,
9
9
  positiveNumberfromJSON,
10
10
  } from '../util/JSONParse';
11
+ import { AltIdentifier } from './AltIdentifier';
11
12
  import { BelongsTo } from './BelongsTo';
12
13
  import { Contributors } from './Contributor';
14
+ import { Layout } from './Layout';
13
15
  import { LocalizedString } from './LocalizedString';
16
+ import { Profile } from './Profiles';
14
17
  import { ReadingProgression } from './ReadingProgression';
15
18
  import { Subjects } from './Subject';
19
+ import { Accessibility } from './accessibility/Accessibility';
20
+ import { TDM } from './TDM';
16
21
 
17
22
  /**
18
23
  * https://readium.org/webpub-manifest/schema/metadata.schema.json
@@ -25,7 +30,9 @@ import { Subjects } from './Subject';
25
30
  export class Metadata {
26
31
  public title: LocalizedString;
27
32
  public typeUri?: string;
33
+ public conformsTo?: Array<Profile>;
28
34
  public identifier?: string;
35
+ public altIdentifier?: AltIdentifier;
29
36
  public subtitle?: LocalizedString;
30
37
  public sortAs?: LocalizedString;
31
38
  public artists?: Contributors;
@@ -49,16 +56,21 @@ export class Metadata {
49
56
  public belongsTo?: BelongsTo;
50
57
  public belongsToCollections?: Contributors;
51
58
  public belongsToSeries?: Contributors;
59
+ public layout?: Layout;
52
60
  public readingProgression?: ReadingProgression;
53
61
  public duration?: number;
54
62
  public numberOfPages?: number;
63
+ public accessibility?: Accessibility;
64
+ public tdm?: TDM;
55
65
  public otherMetadata?: { [key: string]: any };
56
66
 
57
67
  /**All metadata not in otherMetadata */
58
68
  private static readonly mappedProperties = [
59
69
  'title',
60
70
  '@type',
71
+ 'conformsTo',
61
72
  'identifier',
73
+ 'altIdentifier',
62
74
  'subtitle',
63
75
  'sortAs',
64
76
  'artist',
@@ -80,16 +92,21 @@ export class Metadata {
80
92
  'modified',
81
93
  'subject',
82
94
  'belongsTo',
95
+ 'layout',
83
96
  'readingProgression',
84
97
  'duration',
85
98
  'numberOfPages',
99
+ 'accessibility',
100
+ 'tdm'
86
101
  ];
87
102
 
88
103
  /** Creates [Metadata] object */
89
104
  constructor(values: {
90
105
  title: LocalizedString;
91
106
  typeUri?: string;
107
+ conformsTo?: Array<Profile>;
92
108
  identifier?: string;
109
+ altIdentifier?: AltIdentifier;
93
110
  subtitle?: LocalizedString;
94
111
  sortAs?: LocalizedString;
95
112
  artists?: Contributors;
@@ -113,15 +130,20 @@ export class Metadata {
113
130
  belongsTo?: BelongsTo;
114
131
  belongsToCollections?: Contributors;
115
132
  belongsToSeries?: Contributors;
133
+ layout?: Layout;
116
134
  readingProgression?: ReadingProgression;
117
135
  duration?: number;
118
136
  numberOfPages?: number;
137
+ accessibility?: Accessibility;
138
+ tdm?: TDM;
119
139
  otherMetadata?: { [key: string]: any };
120
140
  }) {
121
141
  //title always required
122
142
  this.title = values.title as LocalizedString;
123
143
  this.typeUri = values.typeUri;
144
+ this.conformsTo = values.conformsTo;
124
145
  this.identifier = values.identifier;
146
+ this.altIdentifier = values.altIdentifier;
125
147
  this.subtitle = values.subtitle;
126
148
  this.sortAs = values.sortAs;
127
149
  this.artists = values.artists;
@@ -145,7 +167,6 @@ export class Metadata {
145
167
  this.belongsTo = values.belongsTo;
146
168
  this.belongsToCollections = values.belongsToCollections;
147
169
  this.belongsToSeries = values.belongsToSeries;
148
-
149
170
  if (
150
171
  this.belongsToCollections &&
151
172
  this.belongsToCollections.items.length > 0
@@ -163,9 +184,12 @@ export class Metadata {
163
184
  this.belongsTo.items.set('series', this.belongsToSeries);
164
185
  }
165
186
 
187
+ this.layout = values.layout;
166
188
  this.readingProgression = values.readingProgression;
167
189
  this.duration = values.duration;
168
190
  this.numberOfPages = values.numberOfPages;
191
+ this.accessibility = values.accessibility;
192
+ this.tdm = values.tdm;
169
193
  this.otherMetadata = values.otherMetadata;
170
194
  }
171
195
 
@@ -179,7 +203,9 @@ export class Metadata {
179
203
 
180
204
  const title = LocalizedString.deserialize(json.title) as LocalizedString;
181
205
  const typeUri = json['@type'];
206
+ const conformsTo = arrayfromJSONorString(json.conformsTo);
182
207
  const identifier = json.identifier;
208
+ const altIdentifier = AltIdentifier.deserialize(json.altIdentifier);
183
209
  const subtitle = LocalizedString.deserialize(json.subtitle);
184
210
  const sortAs = LocalizedString.deserialize(json.sortAs);
185
211
  const artists = Contributors.deserialize(json.artist);
@@ -201,9 +227,12 @@ export class Metadata {
201
227
  const modified = datefromJSON(json.modified);
202
228
  const subjects = Subjects.deserialize(json.subject);
203
229
  const belongsTo = BelongsTo.deserialize(json.belongsTo);
230
+ const accessibility = Accessibility.deserialize(json.accessibility);
231
+ const layout = json.layout;
204
232
  const readingProgression = json.readingProgression;
205
233
  const duration = positiveNumberfromJSON(json.duration);
206
234
  const numberOfPages = positiveNumberfromJSON(json.numberOfPages);
235
+ const tdm = TDM.deserialize(json.tdm);
207
236
 
208
237
  let otherMetadata = Object.assign({}, json);
209
238
  Metadata.mappedProperties.forEach(x => delete otherMetadata[x]);
@@ -214,7 +243,9 @@ export class Metadata {
214
243
  return new Metadata({
215
244
  title,
216
245
  typeUri,
246
+ conformsTo,
217
247
  identifier,
248
+ altIdentifier,
218
249
  subtitle,
219
250
  sortAs,
220
251
  artists,
@@ -236,10 +267,13 @@ export class Metadata {
236
267
  modified,
237
268
  subjects,
238
269
  belongsTo,
270
+ layout,
239
271
  readingProgression,
240
272
  duration,
241
273
  numberOfPages,
242
- otherMetadata,
274
+ accessibility,
275
+ tdm,
276
+ otherMetadata
243
277
  });
244
278
  }
245
279
 
@@ -249,7 +283,9 @@ export class Metadata {
249
283
  public serialize(): any {
250
284
  const json: any = { title: this.title.serialize() };
251
285
  if (this.typeUri !== undefined) json['@type'] = this.typeUri;
286
+ if (this.conformsTo) json.conformsTo = this.conformsTo;
252
287
  if (this.identifier !== undefined) json.identifier = this.identifier;
288
+ if (this.altIdentifier) json.altIdentifier = this.altIdentifier.serialize();
253
289
  if (this.subtitle) json.subtitle = this.subtitle.serialize();
254
290
  if (this.sortAs) json.sortAs = this.sortAs.serialize();
255
291
  if (this.editors) json.editor = this.editors.serialize();
@@ -273,11 +309,14 @@ export class Metadata {
273
309
  json.modified = this.modified.toISOString();
274
310
  if (this.subjects) json.subject = this.subjects.serialize();
275
311
  if (this.belongsTo) json.belongsTo = this.belongsTo.serialize();
312
+ if (this.layout) json.layout = this.layout;
276
313
  if (this.readingProgression)
277
314
  json.readingProgression = this.readingProgression;
278
315
  if (this.duration !== undefined) json.duration = this.duration;
279
316
  if (this.numberOfPages !== undefined)
280
317
  json.numberOfPages = this.numberOfPages;
318
+ if (this.accessibility) json.accessibility = this.accessibility.serialize();
319
+ if (this.tdm) json.tdm = this.tdm.serialize();
281
320
 
282
321
  if (this.otherMetadata) {
283
322
  const metadata = this.otherMetadata;
@@ -288,16 +327,54 @@ export class Metadata {
288
327
  }
289
328
 
290
329
  /**
291
- * Computes a [ReadingProgression] when the value of [readingProgression] is set to
292
- * auto, using the publication language.
330
+ * Computes a [Layout] using the [conformsTo] profile and layout property.
331
+ *
332
+ * Special cases:
333
+ * - EPUB profile defaults to reflowable if layout is not present
334
+ * - Divina profile defaults to fixed if layout is not present
335
+ * - Layout is ignored for audiobook and PDF profiles
336
+ * - Layout is ignored if set to reflowable on a Divina profile
337
+ *
338
+ * Note: Stops at the first matching profile.
339
+ */
340
+ public get effectiveLayout(): Layout | null {
341
+ if (!this.conformsTo) {
342
+ return null; // Default Web Publication behavior
343
+ }
344
+
345
+ // Check each profile in order, stopping at the first match
346
+ for (const profile of this.conformsTo) {
347
+ switch (profile) {
348
+ case Profile.EPUB:
349
+ // EPUB defaults to reflowable if layout is not set
350
+ return this.layout || Layout.reflowable;
351
+
352
+ case Profile.DIVINA:
353
+ // Divina defaults to fixed if layout is not set
354
+ // Layout is ignored if set to reflowable
355
+ if (this.layout === Layout.reflowable) {
356
+ return Layout.fixed;
357
+ }
358
+ return this.layout || Layout.fixed;
359
+
360
+ case Profile.AUDIOBOOK:
361
+ case Profile.PDF:
362
+ // Layout is ignored for audiobook and PDF profiles
363
+ return null;
364
+ }
365
+ }
366
+
367
+ // If we get here, we have a conformsTo but no matching profile
368
+ return null;
369
+ }
370
+
371
+ /**
372
+ * Computes a [ReadingProgression] when the value of [readingProgression] is undefined, using the publication language.
293
373
  *
294
374
  * See this issue for more details: https://github.com/readium/architecture/issues/113
295
375
  */
296
376
  public get effectiveReadingProgression(): ReadingProgression {
297
- if (
298
- this.readingProgression &&
299
- this.readingProgression !== ReadingProgression.auto
300
- ) {
377
+ if (this.readingProgression) {
301
378
  return this.readingProgression;
302
379
  }
303
380
 
@@ -0,0 +1,15 @@
1
+ /* Copyright 2025 Readium Foundation. All rights reserved.
2
+ * Use of this source code is governed by a BSD-style license,
3
+ * available in the LICENSE file present in the Github repository of the project.
4
+ */
5
+
6
+ /**
7
+ * Registered profiles.
8
+ * https://readium.org/webpub-manifest/profiles/
9
+ */
10
+ export enum Profile {
11
+ EPUB = 'https://readium.org/webpub-manifest/profiles/epub',
12
+ AUDIOBOOK = 'https://readium.org/webpub-manifest/profiles/audiobook',
13
+ DIVINA = 'https://readium.org/webpub-manifest/profiles/divina',
14
+ PDF = 'https://readium.org/webpub-manifest/profiles/pdf'
15
+ }
@@ -3,6 +3,12 @@
3
3
  * available in the LICENSE file present in the Github repository of the project.
4
4
  */
5
5
 
6
+ export enum Page {
7
+ left = 'left',
8
+ right = 'right',
9
+ center = 'center'
10
+ }
11
+
6
12
  /**
7
13
  * Properties associated to the linked resource.
8
14
  *
@@ -16,6 +22,10 @@ export class Properties {
16
22
  this.otherProperties = values;
17
23
  }
18
24
 
25
+ get page(): Page | undefined {
26
+ return this.otherProperties['page'] as Page | undefined;
27
+ }
28
+
19
29
  /**
20
30
  * Creates a [Properties] from its RWPM JSON representation.
21
31
  */
@@ -3,26 +3,13 @@
3
3
  * available in the LICENSE file present in the Github repository of the project.
4
4
  */
5
5
 
6
- import { Page } from './presentation/Presentation';
6
+ import { Page } from "./Properties";
7
7
 
8
8
  export enum ReadingProgression {
9
- auto = 'auto',
10
- btt = 'btt',
11
9
  ltr = 'ltr',
12
10
  rtl = 'rtl',
13
- ttb = 'ttb',
14
11
  }
15
12
 
16
13
  export function leadingPage(readingProgression: ReadingProgression): Page {
17
- switch (readingProgression) {
18
- case ReadingProgression.auto:
19
- case ReadingProgression.ttb:
20
- case ReadingProgression.ltr:
21
- return Page.left;
22
- case ReadingProgression.rtl:
23
- case ReadingProgression.btt:
24
- return Page.right;
25
- default:
26
- return Page.left;
27
- }
14
+ return readingProgression === ReadingProgression.rtl ? Page.right : Page.left;
28
15
  }
@@ -0,0 +1,56 @@
1
+ /* Copyright 2025 Readium Foundation. All rights reserved.
2
+ * Use of this source code is governed by a BSD-style license,
3
+ * available in the LICENSE file present in the Github repository of the project.
4
+ */
5
+
6
+ /**
7
+ * https://readium.org/webpub-manifest/contexts/default/#text-and-data-mining
8
+ */
9
+
10
+ export enum TDMReservation {
11
+ all = 'all',
12
+ none = 'none'
13
+ }
14
+
15
+ export class TDM {
16
+ /**
17
+ * Indicates whether the publication allows text and data mining.
18
+ */
19
+ public readonly reservation?: TDMReservation;
20
+
21
+ /**
22
+ * Additional policy information about text and data mining usage.
23
+ */
24
+ public readonly policy?: string;
25
+
26
+ /** Creates a [TDM] object */
27
+ constructor(values: {
28
+ reservation?: TDMReservation;
29
+ policy?: string;
30
+ }) {
31
+ this.reservation = values.reservation;
32
+ this.policy = values.policy;
33
+ }
34
+
35
+ /**
36
+ * Parses a [TDM] from its RWPM JSON representation.
37
+ */
38
+ public static deserialize(json: any): TDM | undefined {
39
+ if (!json) return;
40
+
41
+ return new TDM({
42
+ reservation: json.reservation as TDMReservation,
43
+ policy: json.policy,
44
+ });
45
+ }
46
+
47
+ /**
48
+ * Serializes a [TDM] to its RWPM JSON representation.
49
+ */
50
+ public serialize(): any {
51
+ const json: any = {};
52
+ if (this.reservation !== undefined) json.reservation = this.reservation;
53
+ if (this.policy !== undefined) json.policy = this.policy;
54
+ return json;
55
+ }
56
+ }