Recently Written · git

scan-subread

PDF reader that follows an audiobook with the SubRead suite

git clone https://github.com/equwal/scan-subread

Log | Files | Refs


commit 1383fc56bf05995733f620ee76a3dec35beb0b41
truex <truex@equwal.com>
2026-09-26 17:48:03 -0700

Merge the reading view fixes: top bar, status strip, start card, fit width, page turns, follow hold, crossing cues, drawer

# Conflicts:
#	src/subtitles.ts
#	test/subtitles.test.ts

 README.md               | 136 +++++++++----
 index.html              |  95 +++++++--
 src/cue-pages.ts        | 131 ++++++++++++
 src/drawer.ts           |  58 ++++++
 src/follower.ts         |  95 ++++-----
 src/hit-test.ts         |  12 ++
 src/line-boxes.ts       |  75 +++++--
 src/main.ts             | 517 +++++++++++++++++++++++++++++-------------------
 src/messages.ts         |  86 ++++++++
 src/nav.ts              | 210 ++++++++++++++++++++
 src/paging.ts           |  86 ++++++++
 src/scroll.ts           |  28 +++
 src/status.ts           |  69 +++++++
 src/style.css           | 261 +++++++++++++++++++-----
 src/subtitles.ts        |  13 +-
 src/view.ts             | 187 ++++++++++++++++++
 test/cue-pages.test.ts  | 219 ++++++++++++++++++++
 test/follower.test.ts   | 220 ++++++++++-----------
 test/hit-test.test.ts   |  31 ++-
 test/line-boxes.test.ts | 131 +++++++++++-
 test/messages.test.ts   | 100 ++++++++++
 test/paging.test.ts     | 149 ++++++++++++++
 test/scroll.test.ts     |  80 ++++++++
 test/subtitles.test.ts  |  34 +++-
 24 files changed, 2541 insertions(+), 482 deletions(-)
diff --cc src/subtitles.ts
index bf1a658,fc677ab..4af23d2
--- a/src/subtitles.ts
+++ b/src/subtitles.ts
@@@ -49,29 -49,11 +49,32 @@@ export function parseSubtitles(input: s
    return cues;
  }
  
 +/**
 + * Decodes the bytes of a subtitle file to text, and removes the BOM.
 + *
 + * - A UTF-8 BOM, or bytes that are valid UTF-8: UTF-8.
 + * - A UTF-16 BOM: UTF-16, little endian or big endian as the BOM tells.
 + * - Other bytes: Shift_JIS, the usual encoding of old Japanese files.
 + *   Japanese text in Shift_JIS is almost never valid UTF-8.
 + */
 +export function decodeSubtitles(bytes: ArrayBuffer): string {
 +  const b = new Uint8Array(bytes);
 +  // TextDecoder removes the BOM of its own encoding.
 +  if (b[0] === 0xef && b[1] === 0xbb && b[2] === 0xbf) return new TextDecoder('utf-8').decode(b);
 +  if (b[0] === 0xff && b[1] === 0xfe) return new TextDecoder('utf-16le').decode(b);
 +  if (b[0] === 0xfe && b[1] === 0xff) return new TextDecoder('utf-16be').decode(b);
 +  try {
 +    return new TextDecoder('utf-8', { fatal: true }).decode(b);
 +  } catch {
 +    return new TextDecoder('shift_jis').decode(b);
 +  }
 +}
 +
- /** Index of the cue active at time t, or -1. Cues must be sorted by start. */
- export function cueIndexAt(cues: readonly Cue[], t: number): number {
+ /**
+  * Index of the last cue that starts at or before time t, or -1. In the
+  * silence after a cue, this is still that cue. Cues must be sorted by start.
+  */
+ export function lastCueAt(cues: readonly Pick<Cue, 'start'>[], t: number): number {
    let lo = 0;
    let hi = cues.length - 1;
    let found = -1;
diff --cc test/subtitles.test.ts
index bd12de2,e25e4ca..31d7259
--- a/test/subtitles.test.ts
+++ b/test/subtitles.test.ts
@@@ -1,6 -1,6 +1,6 @@@
  import fc from 'fast-check';
  import { describe, expect, it } from 'vitest';
- import { cueIndexAt, decodeSubtitles, parseSubtitles } from '../src/subtitles';
 -import { cueIndexAt, lastCueAt, parseSubtitles } from '../src/subtitles';
++import { cueIndexAt, decodeSubtitles, lastCueAt, parseSubtitles } from '../src/subtitles';
  
  const SRT = `1
  00:00:01,000 --> 00:00:03,500