Files
focus/src/core/orp.ts
gkxk 1f7a6a2054 fix(core): address white-box review feedback
- Fix unicode surrogate pairs and emoji slicing via Array.from in ORP calculator
- Add slot smoothing constraint to pivot element to prevent horizontal jitter
- Implement adaptive timestamp compensation and visibilitychange auto-pause
- Slim storage layer by updating only STORE_META on progress changes with memory fallback
- Align EPUB chapter indices to token array indices instead of raw character offsets
- Support standalone emoji tokens via Extended_Pictographic regex
- Code-split JSZip via dynamic import to optimize initial bundle size

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 08:37:32 +08:00

81 lines
2.1 KiB
TypeScript

/**
* Optimal Recognition Point (ORP) Calculation for Spritz RSVP Reading.
* Fixates the eye at ~30%-35% into the word where human ocular fixation naturally rests.
* Uses unicode-safe code point segmentation (Array.from) to eliminate surrogate pair
* and emoji splitting defects.
*/
const CJK_REGEX = /[一-龥぀-ヿ가-힯]/;
export function isCJKChar(char: string): boolean {
return CJK_REGEX.test(char);
}
export function isCJKWord(word: string): boolean {
return CJK_REGEX.test(word);
}
/**
* Calculates the 0-based index of the ORP character in cleanWord.
* Based on unicode code-point length (Array.from(cleanWord).length).
*/
export function calculateOrpIndex(cleanWord: string): number {
const chars = Array.from(cleanWord);
const len = chars.length;
if (len <= 1) return 0;
if (isCJKWord(cleanWord)) {
// CJK characters have high semantic density per glyph
if (len === 2) return 0;
if (len <= 4) return 1;
if (len <= 6) return 2;
return Math.min(len - 1, Math.floor((len - 1) * 0.35));
}
// Standard Spritz ORP Table for alphabetic words
if (len <= 5) return 1;
if (len <= 9) return 2;
if (len <= 13) return 3;
return 4;
}
export interface SplitWord {
prefix: string;
pivot: string;
suffix: string;
orpIndex: number;
}
/**
* Splits a token into prefix, pivot (ORP character), and suffix.
* Handles leading punctuation (e.g. "“", "《", "(") and trailing punctuation.
* Operates purely on unicode code points to prevent corrupting surrogate pairs.
*/
export function splitOrp(
coreWord: string,
leadingPunct: string = '',
trailingPunct: string = ''
): SplitWord {
if (!coreWord) {
return {
prefix: leadingPunct,
pivot: '',
suffix: trailingPunct,
orpIndex: 0,
};
}
const chars = Array.from(coreWord);
const orpIndex = calculateOrpIndex(coreWord);
const prefix = leadingPunct + chars.slice(0, orpIndex).join('');
const pivot = chars[orpIndex] || '';
const suffix = chars.slice(orpIndex + 1).join('') + trailingPunct;
return {
prefix,
pivot,
suffix,
orpIndex,
};
}