# ReaderV2 bridge contract

`reader_v2/index.html` publishes one global object: `window.ReaderV2`.
All mutating/navigation methods return a `Promise`; `getStateJson()` returns a JSON string synchronously.

## Loading

```js
ReaderV2.loadBook({
  id: "book-id",                    // optional native identifier
  url: "https://appassets.androidplatform.net/book/current", // required
  format: "epub",                   // "epub" or "fb2"; optional when URL/signature is sufficient
  title: "Title",                   // optional fallback metadata
  author: "Author",                 // optional fallback metadata
  locator: { type: "epub", cfi: "epubcfi(...)" }, // optional
  progress: 0.42,                    // optional fallback when locator is absent
  cachedLocations: "[...]",         // optional epub.js locations string
  highlights: [                     // optional initial restored annotations
    { locator: { type: "epub", cfi: "epubcfi(...)" }, color: "#ffd84a" }
  ],
  preferences: { /* schema below */ }
});
```

The engine obtains the book only with `fetch(payload.url).arrayBuffer()`. A whole book is never transported as Base64. FB2 `<binary>` elements are decoded only for images embedded by the FB2 document itself.

For EPUB, `bookReady` is emitted only after the first rendition has displayed and epub.js locations have either loaded from a valid `cachedLocations` string or finished generating. An invalid cache is ignored and regenerated. A newly generated cache is sent as `paginationCache`.

## Preferences

`ReaderV2.setPreferences(partialPreferences)` merges and applies:

```js
{
  fontFamily: "Droid Serif, serif",
  fontSize: 20,              // clamped to 12..42 CSS px
  lineHeight: 1.58,          // clamped to 1.1..2.4
  margin: 28,                // clamped to 8..72 CSS px
  textAlign: "justify",      // justify | left | right | center
  theme: "light",           // light | sepia | dark
  verticalScroll: false,
  pageTurnMode: "tapSwipe", // tapSwipe | swipe | tap
  invertZones: false,        // reverses only left/right tap zones
  brightnessGesture: false   // enables vertical left-edge brightness gesture
}
```

The left-edge brightness gesture emits `brightnessDelta` with `payload.delta` in percentage points, clamped to `-100..100`; upward is positive. Native code owns the actual screen-brightness value.

The selectable families `Droid Serif`, `EB Garamond`, `Droid Sans`, `Roboto`, `PT Sans`, `PT Serif`, `Merriweather`, and `Open Sans` are bundled in `fonts/`. The shell and every EPUB iframe attach the same font stylesheet, and preference changes wait for the chosen face before repagination. Exact font sources, licenses, hashes, axes, and the Droid Sans italic limitation are recorded in `fonts/PROVENANCE.md`.

Reader canvas colors are fixed to `#FFFFFF` (light), `#F6F3E0` (sepia), and `#000000` (dark).

## Navigation and state

```js
ReaderV2.next();
ReaderV2.previous();
ReaderV2.goToProgress(0.0_to_1.0);
ReaderV2.goToLocator(locator);
ReaderV2.goToChapter(href);
ReaderV2.getStateJson();
```

Locators:

```js
{ type: "epub", cfi: "epubcfi(...)" }
{ type: "fb2", sectionId: "fb2-section-12", offset: 240, endSectionId: "...", endOffset: 278 }
```

`endSectionId` and `endOffset` are present for an FB2 selection/highlight. Navigation uses its start `sectionId` and `offset`.

Every page action emits `navigation` with `{direction, handled, boundary, locator}`. This event has no control-panel side effect. Only a center tap emits `toggleControls`.

## Search and selection

```js
ReaderV2.search("query");
ReaderV2.nextSearch();
ReaderV2.previousSearch();
ReaderV2.clearSearch();
ReaderV2.clearSelection();
```

Search emits `{query,total,currentIndex,searching,truncated,result}`. At most 500 results are retained; `truncated` reports that cap. A result contains `locator`, `excerpt`, and chapter/href where available.

## Persistent highlights

```js
ReaderV2.addHighlight(locator, "#ffd84a");
ReaderV2.removeHighlight(locator);
ReaderV2.setHighlights([
  { locator: locatorA, color: "#ffd84a" },
  { locator: locatorB, color: "#8bd89b" }
]);
```

`setHighlights(list)` is the exact bulk-restore method. It replaces all current user highlights with the supplied list and returns a Promise resolving to the restored count. Search markers are transient and are not part of this list.

## Native messages

Every event is sent through:

```js
AndroidBridge.postMessage(JSON.stringify({ version: 2, type, payload }));
```

Event types:

- `shellReady` — global API is installed.
- `bookReady` — the requested book is genuinely displayed and ready.
- `paginationCache` — `{locations: String}` generated by epub.js.
- `toc` — `{format, items:[{label,href,depth}]}`.
- `progress` — format, `progress`, locator, page/chapter fields and boundaries. Both formats expose `chapterCurrentPage`, `chapterTotalPages`, and `remainingInChapter`; the legacy aliases `chapterPage` and `chapterTotal` are retained.
- `search` — current search state/result.
- `selection` — selected text and stable locator.
- `toggleControls` — center reading-zone tap.
- `navigation` — navigation result/boundary; never opens controls itself.
- `brightnessDelta` — normalized left-edge gesture delta.
- `externalLink` — validated `http`, `https`, or `mailto` URL for native handling.
- `error` — `{stage,code,message,recoverable}`.
