@readium/shared 1.2.1 → 2.0.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 (56) hide show
  1. package/dist/index.js +1256 -1152
  2. package/dist/index.umd.cjs +3 -3
  3. package/package.json +11 -10
  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 +9 -1
  9. package/src/publication/Manifest.ts +5 -5
  10. package/src/publication/Metadata.ts +77 -8
  11. package/src/publication/Profiles.ts +15 -0
  12. package/src/publication/Properties.ts +10 -0
  13. package/src/publication/Publication.ts +2 -2
  14. package/src/publication/ReadingProgression.ts +2 -15
  15. package/src/publication/TDM.ts +56 -0
  16. package/src/publication/epub/MediaOverlay.ts +43 -0
  17. package/src/publication/epub/Metadata.ts +16 -0
  18. package/src/publication/epub/Properties.ts +0 -10
  19. package/src/publication/epub/index.ts +2 -2
  20. package/src/publication/index.ts +4 -1
  21. package/src/util/JSONParse.ts +3 -3
  22. package/types/src/publication/AltIdentifier.d.ts +23 -0
  23. package/types/src/publication/Contributor.d.ts +4 -0
  24. package/types/src/publication/GuidedNavigation.d.ts +62 -9
  25. package/types/src/publication/Layout.d.ts +8 -0
  26. package/types/src/publication/Link.d.ts +3 -0
  27. package/types/src/publication/Manifest.d.ts +2 -2
  28. package/types/src/publication/Metadata.d.ts +25 -2
  29. package/types/src/publication/Profiles.d.ts +10 -0
  30. package/types/src/publication/Properties.d.ts +6 -0
  31. package/types/src/publication/Publication.d.ts +1 -1
  32. package/types/src/publication/ReadingProgression.d.ts +2 -5
  33. package/types/src/publication/TDM.d.ts +30 -0
  34. package/types/src/publication/accessibility/Accessibility.d.ts +196 -0
  35. package/types/src/publication/accessibility/AccessibilityMetadataDisplayGuide.d.ts +211 -0
  36. package/types/src/publication/accessibility/Localization.d.ts +37 -0
  37. package/types/src/publication/accessibility/index.d.ts +3 -0
  38. package/types/src/publication/epub/MediaOverlay.d.ts +19 -0
  39. package/types/src/publication/epub/Metadata.d.ts +6 -0
  40. package/types/src/publication/epub/Properties.d.ts +1 -5
  41. package/types/src/publication/epub/index.d.ts +2 -2
  42. package/types/src/publication/index.d.ts +4 -1
  43. package/types/src/util/JSONParse.d.ts +2 -2
  44. package/LICENSE +0 -28
  45. package/src/publication/epub/EPUBLayout.ts +0 -10
  46. package/src/publication/epub/Presentation.ts +0 -16
  47. package/src/publication/presentation/Metadata.ts +0 -19
  48. package/src/publication/presentation/Presentation.ts +0 -155
  49. package/src/publication/presentation/Properties.ts +0 -70
  50. package/src/publication/presentation/index.ts +0 -3
  51. package/types/src/publication/epub/EPUBLayout.d.ts +0 -5
  52. package/types/src/publication/epub/Presentation.d.ts +0 -7
  53. package/types/src/publication/presentation/Metadata.d.ts +0 -6
  54. package/types/src/publication/presentation/Presentation.d.ts +0 -95
  55. package/types/src/publication/presentation/Properties.d.ts +0 -32
  56. package/types/src/publication/presentation/index.d.ts +0 -3
@@ -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;
@@ -94,7 +99,8 @@ export class Link {
94
99
  * Parses a [Link] from its RWPM JSON representation.
95
100
  */
96
101
  public static deserialize(json: any): Link | undefined {
97
- if (!(json && json.href && typeof json.href === 'string')) return;
102
+ if (!json || typeof json.href !== 'string') return;
103
+
98
104
  return new Link({
99
105
  href: json.href,
100
106
  templated: json.templated,
@@ -108,6 +114,7 @@ export class Link {
108
114
  properties: Properties.deserialize(json.properties),
109
115
  height: positiveNumberfromJSON(json.height),
110
116
  width: positiveNumberfromJSON(json.width),
117
+ size: positiveNumberfromJSON(json.size),
111
118
  duration: positiveNumberfromJSON(json.duration),
112
119
  bitrate: positiveNumberfromJSON(json.bitrate),
113
120
  languages: arrayfromJSONorString(json.language),
@@ -128,6 +135,7 @@ export class Link {
128
135
  if (this.properties) json.properties = this.properties.serialize();
129
136
  if (this.height !== undefined) json.height = this.height;
130
137
  if (this.width !== undefined) json.width = this.width;
138
+ if (this.size !== undefined) json.size = this.size;
131
139
  if (this.duration !== undefined) json.duration = this.duration;
132
140
  if (this.bitrate !== undefined) json.bitrate = this.bitrate;
133
141
  if (this.languages) json.language = this.languages;
@@ -28,7 +28,7 @@ export class Manifest {
28
28
  public readonly resources?: Links;
29
29
 
30
30
  /** Identifies the collection that contains a table of contents. */
31
- public readonly tableOfContents?: Links;
31
+ public readonly toc?: Links;
32
32
 
33
33
  public readonly subcollections?: Map<string, Array<PublicationCollection>>;
34
34
 
@@ -38,7 +38,7 @@ export class Manifest {
38
38
  links: Links;
39
39
  readingOrder: Links;
40
40
  resources?: Links;
41
- tableOfContents?: Links;
41
+ toc?: Links;
42
42
  subcollections?: Map<string, Array<PublicationCollection>>;
43
43
  }) {
44
44
  this.context = values.context;
@@ -46,7 +46,7 @@ export class Manifest {
46
46
  this.links = values.links;
47
47
  this.readingOrder = values.readingOrder;
48
48
  this.resources = values.resources;
49
- this.tableOfContents = values.tableOfContents;
49
+ this.toc = values.toc;
50
50
  this.subcollections = values.subcollections;
51
51
  }
52
52
 
@@ -79,7 +79,7 @@ export class Manifest {
79
79
  links,
80
80
  readingOrder,
81
81
  resources: Links.deserialize(json.resources),
82
- tableOfContents: Links.deserialize(json.toc),
82
+ toc: Links.deserialize(json.toc),
83
83
  subcollections: PublicationCollection.deserializeCollections({
84
84
  sub: json.sub,
85
85
  }),
@@ -96,7 +96,7 @@ export class Manifest {
96
96
  json.links = this.links.serialize();
97
97
  json.readingOrder = this.readingOrder.serialize();
98
98
  if (this.resources) json.resources = this.resources.serialize();
99
- if (this.tableOfContents) json.toc = this.tableOfContents.serialize();
99
+ if (this.toc) json.toc = this.toc.serialize();
100
100
  PublicationCollection.serializeCollection(json, this.subcollections);
101
101
  return json;
102
102
  }
@@ -8,11 +8,15 @@ 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 { TDM } from './TDM';
16
20
 
17
21
  /**
18
22
  * https://readium.org/webpub-manifest/schema/metadata.schema.json
@@ -25,7 +29,9 @@ import { Subjects } from './Subject';
25
29
  export class Metadata {
26
30
  public title: LocalizedString;
27
31
  public typeUri?: string;
32
+ public conformsTo?: Array<Profile>;
28
33
  public identifier?: string;
34
+ public altIdentifier?: AltIdentifier;
29
35
  public subtitle?: LocalizedString;
30
36
  public sortAs?: LocalizedString;
31
37
  public artists?: Contributors;
@@ -49,16 +55,20 @@ export class Metadata {
49
55
  public belongsTo?: BelongsTo;
50
56
  public belongsToCollections?: Contributors;
51
57
  public belongsToSeries?: Contributors;
58
+ public layout?: Layout;
52
59
  public readingProgression?: ReadingProgression;
53
60
  public duration?: number;
54
61
  public numberOfPages?: number;
62
+ public tdm?: TDM;
55
63
  public otherMetadata?: { [key: string]: any };
56
64
 
57
65
  /**All metadata not in otherMetadata */
58
66
  private static readonly mappedProperties = [
59
67
  'title',
60
68
  '@type',
69
+ 'conformsTo',
61
70
  'identifier',
71
+ 'altIdentifier',
62
72
  'subtitle',
63
73
  'sortAs',
64
74
  'artist',
@@ -80,16 +90,20 @@ export class Metadata {
80
90
  'modified',
81
91
  'subject',
82
92
  'belongsTo',
93
+ 'layout',
83
94
  'readingProgression',
84
95
  'duration',
85
96
  'numberOfPages',
97
+ 'tdm'
86
98
  ];
87
99
 
88
100
  /** Creates [Metadata] object */
89
101
  constructor(values: {
90
102
  title: LocalizedString;
91
103
  typeUri?: string;
104
+ conformsTo?: Array<Profile>;
92
105
  identifier?: string;
106
+ altIdentifier?: AltIdentifier;
93
107
  subtitle?: LocalizedString;
94
108
  sortAs?: LocalizedString;
95
109
  artists?: Contributors;
@@ -113,15 +127,19 @@ export class Metadata {
113
127
  belongsTo?: BelongsTo;
114
128
  belongsToCollections?: Contributors;
115
129
  belongsToSeries?: Contributors;
130
+ layout?: Layout;
116
131
  readingProgression?: ReadingProgression;
117
132
  duration?: number;
118
133
  numberOfPages?: number;
134
+ tdm?: TDM;
119
135
  otherMetadata?: { [key: string]: any };
120
136
  }) {
121
137
  //title always required
122
138
  this.title = values.title as LocalizedString;
123
139
  this.typeUri = values.typeUri;
140
+ this.conformsTo = values.conformsTo;
124
141
  this.identifier = values.identifier;
142
+ this.altIdentifier = values.altIdentifier;
125
143
  this.subtitle = values.subtitle;
126
144
  this.sortAs = values.sortAs;
127
145
  this.artists = values.artists;
@@ -145,7 +163,6 @@ export class Metadata {
145
163
  this.belongsTo = values.belongsTo;
146
164
  this.belongsToCollections = values.belongsToCollections;
147
165
  this.belongsToSeries = values.belongsToSeries;
148
-
149
166
  if (
150
167
  this.belongsToCollections &&
151
168
  this.belongsToCollections.items.length > 0
@@ -163,9 +180,11 @@ export class Metadata {
163
180
  this.belongsTo.items.set('series', this.belongsToSeries);
164
181
  }
165
182
 
183
+ this.layout = values.layout;
166
184
  this.readingProgression = values.readingProgression;
167
185
  this.duration = values.duration;
168
186
  this.numberOfPages = values.numberOfPages;
187
+ this.tdm = values.tdm;
169
188
  this.otherMetadata = values.otherMetadata;
170
189
  }
171
190
 
@@ -179,7 +198,9 @@ export class Metadata {
179
198
 
180
199
  const title = LocalizedString.deserialize(json.title) as LocalizedString;
181
200
  const typeUri = json['@type'];
201
+ const conformsTo = arrayfromJSONorString(json.conformsTo);
182
202
  const identifier = json.identifier;
203
+ const altIdentifier = AltIdentifier.deserialize(json.altIdentifier);
183
204
  const subtitle = LocalizedString.deserialize(json.subtitle);
184
205
  const sortAs = LocalizedString.deserialize(json.sortAs);
185
206
  const artists = Contributors.deserialize(json.artist);
@@ -201,9 +222,11 @@ export class Metadata {
201
222
  const modified = datefromJSON(json.modified);
202
223
  const subjects = Subjects.deserialize(json.subject);
203
224
  const belongsTo = BelongsTo.deserialize(json.belongsTo);
225
+ const layout = json.layout;
204
226
  const readingProgression = json.readingProgression;
205
227
  const duration = positiveNumberfromJSON(json.duration);
206
228
  const numberOfPages = positiveNumberfromJSON(json.numberOfPages);
229
+ const tdm = TDM.deserialize(json.tdm);
207
230
 
208
231
  let otherMetadata = Object.assign({}, json);
209
232
  Metadata.mappedProperties.forEach(x => delete otherMetadata[x]);
@@ -214,7 +237,9 @@ export class Metadata {
214
237
  return new Metadata({
215
238
  title,
216
239
  typeUri,
240
+ conformsTo,
217
241
  identifier,
242
+ altIdentifier,
218
243
  subtitle,
219
244
  sortAs,
220
245
  artists,
@@ -236,10 +261,12 @@ export class Metadata {
236
261
  modified,
237
262
  subjects,
238
263
  belongsTo,
264
+ layout,
239
265
  readingProgression,
240
266
  duration,
241
267
  numberOfPages,
242
- otherMetadata,
268
+ tdm,
269
+ otherMetadata
243
270
  });
244
271
  }
245
272
 
@@ -249,7 +276,9 @@ export class Metadata {
249
276
  public serialize(): any {
250
277
  const json: any = { title: this.title.serialize() };
251
278
  if (this.typeUri !== undefined) json['@type'] = this.typeUri;
279
+ if (this.conformsTo) json.conformsTo = this.conformsTo;
252
280
  if (this.identifier !== undefined) json.identifier = this.identifier;
281
+ if (this.altIdentifier) json.altIdentifier = this.altIdentifier.serialize();
253
282
  if (this.subtitle) json.subtitle = this.subtitle.serialize();
254
283
  if (this.sortAs) json.sortAs = this.sortAs.serialize();
255
284
  if (this.editors) json.editor = this.editors.serialize();
@@ -273,11 +302,13 @@ export class Metadata {
273
302
  json.modified = this.modified.toISOString();
274
303
  if (this.subjects) json.subject = this.subjects.serialize();
275
304
  if (this.belongsTo) json.belongsTo = this.belongsTo.serialize();
305
+ if (this.layout) json.layout = this.layout;
276
306
  if (this.readingProgression)
277
307
  json.readingProgression = this.readingProgression;
278
308
  if (this.duration !== undefined) json.duration = this.duration;
279
309
  if (this.numberOfPages !== undefined)
280
310
  json.numberOfPages = this.numberOfPages;
311
+ if (this.tdm) json.tdm = this.tdm.serialize();
281
312
 
282
313
  if (this.otherMetadata) {
283
314
  const metadata = this.otherMetadata;
@@ -288,16 +319,54 @@ export class Metadata {
288
319
  }
289
320
 
290
321
  /**
291
- * Computes a [ReadingProgression] when the value of [readingProgression] is set to
292
- * auto, using the publication language.
322
+ * Computes a [Layout] using the [conformsTo] profile and layout property.
323
+ *
324
+ * Special cases:
325
+ * - EPUB profile defaults to reflowable if layout is not present
326
+ * - Divina profile defaults to fixed if layout is not present
327
+ * - Layout is ignored for audiobook and PDF profiles
328
+ * - Layout is ignored if set to reflowable on a Divina profile
329
+ *
330
+ * Note: Stops at the first matching profile.
331
+ */
332
+ public get effectiveLayout(): Layout | null {
333
+ if (!this.conformsTo) {
334
+ return null; // Default Web Publication behavior
335
+ }
336
+
337
+ // Check each profile in order, stopping at the first match
338
+ for (const profile of this.conformsTo) {
339
+ switch (profile) {
340
+ case Profile.EPUB:
341
+ // EPUB defaults to reflowable if layout is not set
342
+ return this.layout || Layout.reflowable;
343
+
344
+ case Profile.DIVINA:
345
+ // Divina defaults to fixed if layout is not set
346
+ // Layout is ignored if set to reflowable
347
+ if (this.layout === Layout.reflowable) {
348
+ return Layout.fixed;
349
+ }
350
+ return this.layout || Layout.fixed;
351
+
352
+ case Profile.AUDIOBOOK:
353
+ case Profile.PDF:
354
+ // Layout is ignored for audiobook and PDF profiles
355
+ return null;
356
+ }
357
+ }
358
+
359
+ // If we get here, we have a conformsTo but no matching profile
360
+ return null;
361
+ }
362
+
363
+ /**
364
+ * Computes a [ReadingProgression] when the value of [readingProgression] is undefined, using the publication language.
293
365
  *
294
366
  * See this issue for more details: https://github.com/readium/architecture/issues/113
295
367
  */
296
368
  public get effectiveReadingProgression(): ReadingProgression {
297
- if (
298
- this.readingProgression &&
299
- this.readingProgression !== ReadingProgression.auto
300
- ) {
369
+ if (this.readingProgression) {
301
370
  return this.readingProgression;
302
371
  }
303
372
 
@@ -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
  */
@@ -30,7 +30,7 @@ export class Publication {
30
30
  /** Identifies resources that are necessary for rendering the publication. */
31
31
  public readonly resources?: Links;
32
32
  /** Identifies the collection that contains a table of contents. */
33
- public readonly tableOfContents?: Links;
33
+ public readonly toc?: Links;
34
34
  /** Identifies the collection that contains sub collections. */
35
35
  public readonly subcollections?: Map<string, Array<PublicationCollection>>;
36
36
 
@@ -42,7 +42,7 @@ export class Publication {
42
42
  this.links = values.manifest.links;
43
43
  this.readingOrder = values.manifest.readingOrder;
44
44
  this.resources = values.manifest.resources;
45
- this.tableOfContents = values.manifest.tableOfContents;
45
+ this.toc = values.manifest.toc;
46
46
  this.subcollections = values.manifest.subcollections;
47
47
  }
48
48
 
@@ -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
+ }