@readium/speech 0.2.0 → 0.3.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.
package/README.md CHANGED
@@ -36,20 +36,28 @@ We are now focused on the fourth phase: extracting [Guided Navigation objects](h
36
36
 
37
37
  ## Demos
38
38
 
39
- Two live demos are available:
40
-
41
- 1. [Voice selection with playback demo](https://readium.org/speech/demo)
42
- 2. [In-context demo](https://readium.org/speech/demo/article)
43
-
44
- The first demo showcases the following features:
39
+ ### [Voice selection with playback demo](https://readium.org/speech/demo)
45
40
 
46
41
  - fetching a list of all available languages, translating them to the user's locale and sorting them based on these translations
47
42
  - returning a list of voices for a given language, grouped by region and sorted based on quality
48
43
  - filtering languages and voices based on gender and offline availability
49
44
  - using embedded test utterances to demo voices
50
45
  - using the current Navigator for playback control
46
+ - highlighting: as playback progresses, the current word/sentence is highlighted
47
+
48
+ ### [In-context demo](https://readium.org/speech/demo/article)
49
+
50
+ In-context reading with seamless voice selection (grouped by region and sorted based on quality), and playback control, providing an optional read-along experience that integrates naturally with the content. Also showcases highlighting, as above.
51
+
52
+ ### [Extraction playground](https://readium.org/speech/demo/playground)
51
53
 
52
- The second demo focuses on in-context reading with seamless voice selection (grouped by region and sorted based on quality), and playback control, providing an optional read-along experience that integrates naturally with the content. Both demos also showcase highlighting: as playback progresses, the current word/sentence is highlighted.
54
+ Pick a sample piece of markup and watch it move through the whole pipeline:
55
+
56
+ - the [Guided Navigation](docs/GuidedNavigation.md) document it produces
57
+ - the [utterances](docs/UtteranceExtraction.md) extracted from that, with every extraction option (format, language handling, skipped roles, sentence interruption, contextualization) adjustable live
58
+ - playback of the resulting utterances through the WebSpeech navigator, with word-boundary highlighting
59
+
60
+ The sample markup is drawn from this project's own conformance test suite ([`fixtures/`](fixtures/README.md)), so each pane also shows a pass/fail badge against that suite's expected output — a side effect of reusing real test content, not the point of the demo.
53
61
 
54
62
  ## Installation
55
63
 
@@ -127,6 +135,8 @@ Documentation provides guides for:
127
135
  - [Voice Management](docs/VoiceManagement.md)
128
136
  - [Playback API](docs/Playback.md)
129
137
  - [Highlighting](docs/Highlighting.md)
138
+ - [Guided Navigation](docs/GuidedNavigation.md) — extracting [Guided Navigation objects](https://readium.org/guided-navigation) from HTML/XHTML content
139
+ - [Utterance Extraction](docs/UtteranceExtraction.md) — extracting utterances from Guided Navigation objects
130
140
 
131
141
  ## Development
132
142
 
@@ -148,7 +158,7 @@ This will compile the TypeScript code and generate the following outputs in the
148
158
 
149
159
  ### Running Demos Locally
150
160
 
151
- The project includes two demo applications that can be served locally:
161
+ The project includes demo applications that can be served locally:
152
162
 
153
163
  1. Start the local development server:
154
164
  ```bash
@@ -158,6 +168,7 @@ The project includes two demo applications that can be served locally:
158
168
  2. Open your browser to:
159
169
  - [Voice selection demo](http://localhost:8080/demo)
160
170
  - [In-context reading demo](http://localhost:8080/demo/article)
171
+ - [Extraction playground](http://localhost:8080/demo/playground)
161
172
 
162
173
  ### ChromeOS Debugging
163
174
 
@@ -167,12 +178,19 @@ For ChromeOS development, the project includes a debug mode that mocks the Web S
167
178
 
168
179
  2. The debug page loads mock voices from a json file which contains a snapshot of ChromeOS voices.
169
180
 
170
- ### Running Tests
181
+ ### Testing
182
+
183
+ `npm test` builds the library and runs the full test suite (`ava`) across `test/**/*.test.ts`. Narrower scripts are available for working on one area at a time:
171
184
 
172
- To run the test suite for `WebSpeechVoiceManager`:
173
185
  ```bash
174
- npm test
186
+ npm test # build + full suite
187
+ npm run test:voices # WebSpeechVoiceManager only
188
+ npm run test:gnd # HTML/XHTML -> Guided Navigation conversion
189
+ npm run test:utterances # Guided Navigation -> utterance extraction
175
190
  ```
191
+
192
+ `test:gnd` and `test:utterances` are both driven by [`fixtures/`](fixtures/README.md), a language-agnostic conformance suite of paired input/expected-output files (`input.html`/`input.xhtml`, `gnd.json`, `utterances.json`) covering the [Guided Navigation](docs/GuidedNavigation.md) and [utterance extraction](docs/UtteranceExtraction.md) stages one role/encoding/option at a time. Each fixture is a plain-file test case any platform implementation can consume, not just this TypeScript one — see [fixtures/README.md](fixtures/README.md) for the format, how to add a fixture, and how a fixture "passes".
193
+
176
194
  ## Acknowledgments
177
195
 
178
196
  This project is based on the work done initially by [Hadrien Gardeur](https://github.com/hadriengardeur) in the [web-speech-recommended-voices](https://github.com/HadrienGardeur/web-speech-recommended-voices) repository.
@@ -38,7 +38,7 @@ export declare class WebSpeechEngine implements ReadiumSpeechPlaybackEngine {
38
38
  private validateText;
39
39
  private getCurrentVoiceForUtterance;
40
40
  getCurrentVoice(): ReadiumSpeechVoice | null;
41
- private escapeSSML;
41
+ private toPlainText;
42
42
  loadUtterances(contents: ReadiumSpeechUtterance[]): void;
43
43
  setVoice(voice: ReadiumSpeechVoice | string): Promise<void>;
44
44
  getAvailableVoices(): Promise<ReadiumSpeechVoice[]>;
@@ -0,0 +1,18 @@
1
+ import { GndText } from './types.js';
2
+ /** Normalized (whitespace-coalesced and trimmed) text content of a node's subtree. */
3
+ export declare function normalizedNodeText(el: Node): string;
4
+ /**
5
+ * Computes the text that becomes a node's `GndObject.description`, and
6
+ * whether the node is visible in the first place. Follows the AccName
7
+ * precedence order (https://www.w3.org/TR/accname/#terminology, 2.A-2.D)
8
+ * for its accessible-name sources, with a non-AccName `aria-describedby`
9
+ * fallback spliced in between 2.C and 2.D — see that branch below.
10
+ */
11
+ export declare function extractNodeAria(el: Element): [GndText | null, boolean];
12
+ /**
13
+ * Maps an HTML element to the SSML tag its text should be wrapped in.
14
+ * https://www.w3.org/TR/speech-synthesis11/#S3.2.2
15
+ */
16
+ export declare function convertElementToSSMLTag(tagName: string): [string, Record<string, string>?];
17
+ /** Elements whose entire subtree carries no user-facing content. */
18
+ export declare const skippedElements: Set<string>;
@@ -0,0 +1,52 @@
1
+ import { GndObject } from './types.js';
2
+ import { GndMediaType } from './dom.js';
3
+ /** Walks a DOM subtree, building the Guided Navigation object tree. */
4
+ export declare class Converter {
5
+ xmlParsed: boolean;
6
+ ids: Map<string, Element>;
7
+ suppressed: Set<Element>;
8
+ idAlloc: {
9
+ claimed: Set<string>;
10
+ counters: Map<string, number>;
11
+ };
12
+ noterefDepth: number;
13
+ allowNode: Element | null;
14
+ private root;
15
+ private current;
16
+ private segments;
17
+ private textAcc;
18
+ private currentCtx;
19
+ private flowEndsWithSpace;
20
+ private pendingChildren;
21
+ constructor(xmlParsed: boolean);
22
+ private allocateId;
23
+ private claimId;
24
+ prescan(root: Element): void;
25
+ convert(root: Element): void;
26
+ convertChildren(root: Element): void;
27
+ result(): GndObject[];
28
+ private descend;
29
+ private appendChild;
30
+ private walk;
31
+ private head;
32
+ private tail;
33
+ private text;
34
+ private textContext;
35
+ private updateFlowSpace;
36
+ private closeSegment;
37
+ private resetFlow;
38
+ private placeholder;
39
+ private pagebreak;
40
+ private noteref;
41
+ private link;
42
+ private flushText;
43
+ }
44
+ /**
45
+ * Converts an HTML or XHTML fragment or document into Guided Navigation
46
+ * objects, reflecting exactly the input it's given: a real, author-written
47
+ * <body> becomes its own role: ["body"] node like any other element; a
48
+ * <body> synthesized only by text/html parsing around a bodyless fragment
49
+ * is not content and is skipped through; a bodyless XHTML fragment's root
50
+ * element is itself the content.
51
+ */
52
+ export declare function parseMarkup(input: string, mediaType?: GndMediaType): GndObject[];
@@ -0,0 +1,5 @@
1
+ export declare function nodeLanguage(el: Element | null): string;
2
+ export declare function hasElementChild(el: Element): boolean;
3
+ export declare function isAncestorOf(anc: Element, n: Element): boolean;
4
+ export type GndMediaType = "text/html" | "application/xhtml+xml";
5
+ export declare function sniffMediaType(input: string): GndMediaType;
@@ -0,0 +1,4 @@
1
+ export type { GndRole, GndText, GndObject, GndDocument } from './types.js';
2
+ export { makeGnd } from './makeGnd.js';
3
+ export { parseMarkup } from './converter.js';
4
+ export type { GndMediaType } from './dom.js';
@@ -0,0 +1,8 @@
1
+ import { GndDocument } from './types.js';
2
+ import { GndMediaType } from './dom.js';
3
+ export type { GndMediaType };
4
+ /**
5
+ * Builds a Guided Navigation document from an HTML or XHTML fragment or
6
+ * document, following https://github.com/readium/guided-navigation.
7
+ */
8
+ export declare function makeGnd(input: string, mediaType?: GndMediaType): GndDocument;
@@ -0,0 +1,24 @@
1
+ import { GndObject, GndRole } from './types.js';
2
+ import { TextBuilder } from './text.js';
3
+ export interface ObjBuilder {
4
+ id?: string;
5
+ audioref?: string;
6
+ imgref?: string;
7
+ textref?: string;
8
+ videoref?: string;
9
+ text?: TextBuilder;
10
+ role?: GndRole[];
11
+ children?: ObjBuilder[];
12
+ description?: string;
13
+ }
14
+ export declare function isEmptyObj(o: ObjBuilder): boolean;
15
+ /** A node being built up during the tree walk, before its final shape is known. */
16
+ export declare class NavObject {
17
+ el?: Element;
18
+ object: ObjBuilder;
19
+ children: NavObject[];
20
+ noText: boolean;
21
+ finalize(): ObjBuilder;
22
+ }
23
+ export declare function finalizeToGndObject(o: ObjBuilder): GndObject;
24
+ export declare function gndObjectToObjBuilder(n: GndObject): ObjBuilder;
@@ -0,0 +1,8 @@
1
+ import { GndRole } from './types.js';
2
+ /**
3
+ * Determines the Guided Navigation roles of an element, combining the roles
4
+ * derived from the element type itself with the ones from its ARIA `role` and
5
+ * `epub:type` attributes, e.g. `<section epub:type="chapter">` -> `[section, chapter]`.
6
+ * An ARIA role of "presentation"/"none" strips the element of its native semantics.
7
+ */
8
+ export declare function extractNodeRoles(el: Element): GndRole[];
@@ -0,0 +1,18 @@
1
+ import { GndText } from './types.js';
2
+ export interface TextBuilder {
3
+ plain: string;
4
+ ssml: string;
5
+ language: string;
6
+ }
7
+ export declare function textIsEmpty(t: TextBuilder): boolean;
8
+ export declare function finalizeText(t?: TextBuilder): string | GndText | undefined;
9
+ export interface SSMLContext {
10
+ lang: string;
11
+ tag: string;
12
+ attrs?: Record<string, string>;
13
+ }
14
+ export declare function ctxEqual(a: SSMLContext, b: SSMLContext): boolean;
15
+ export declare const ssmlTextEscape: (s: string) => string;
16
+ export declare const ssmlAttrEscape: (s: string) => string;
17
+ export declare function isNoBreakSpace(ch: string): boolean;
18
+ export declare function normalizeWhitespace(text: string, stripLeading: boolean): string;
@@ -0,0 +1,21 @@
1
+ export type GndRole = string;
2
+ export interface GndText {
3
+ language: string;
4
+ plain?: string;
5
+ ssml?: string;
6
+ }
7
+ export interface GndObject {
8
+ role?: GndRole[];
9
+ text?: string | GndText;
10
+ description?: string;
11
+ imgref?: string;
12
+ audioref?: string;
13
+ videoref?: string;
14
+ textref?: string;
15
+ id?: string;
16
+ children?: GndObject[];
17
+ }
18
+ export interface GndDocument {
19
+ links?: unknown[];
20
+ guided: GndObject[];
21
+ }