fix(highlight): improve URL replacement spread and sentence alignment fallback
Distribute replacement characters across original URL spans to preserve positional mapping in highlights. In sentence alignment, add fallback to avoid misaligned word highlights when the sentence cannot be located, ensuring robustness in TTS word highlighting.
This commit is contained in:
parent
ff5a6b834d
commit
a2cdc575ef
2 changed files with 22 additions and 6 deletions
|
|
@ -44,8 +44,14 @@ const replaceMappedUrls = <TPos>(tokens: MappedChar<TPos>[]): MappedChar<TPos>[]
|
||||||
const anchor = tokens[start] ?? tokens[Math.max(0, end - 1)];
|
const anchor = tokens[start] ?? tokens[Math.max(0, end - 1)];
|
||||||
if (anchor) {
|
if (anchor) {
|
||||||
const replacement = `- (link to ${match[1]}) -`;
|
const replacement = `- (link to ${match[1]}) -`;
|
||||||
for (const char of replacement) {
|
// Spread the replacement characters across the original URL span so the
|
||||||
replaced.push(cloneMappedChar(char, anchor));
|
// mapped positions keep their positional spread instead of collapsing the
|
||||||
|
// whole URL onto a single DOM anchor.
|
||||||
|
const originalLength = Math.max(1, end - start);
|
||||||
|
for (let i = 0; i < replacement.length; i += 1) {
|
||||||
|
const sourceIndex = Math.min(end - 1, start + Math.floor((i * originalLength) / replacement.length));
|
||||||
|
const source = tokens[sourceIndex] ?? anchor;
|
||||||
|
replaced.push(cloneMappedChar(replacement[i], source));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cursor = end;
|
cursor = end;
|
||||||
|
|
|
||||||
|
|
@ -326,17 +326,27 @@ export function highlightHtmlWord(
|
||||||
// wider than the spoken sentence; rebasing keeps the alignment char offsets
|
// wider than the spoken sentence; rebasing keeps the alignment char offsets
|
||||||
// accurate regardless.
|
// accurate regardless.
|
||||||
if (sentenceState.alignment !== alignment || sentenceState.base === null) {
|
if (sentenceState.alignment !== alignment || sentenceState.base === null) {
|
||||||
sentenceState.alignment = alignment;
|
|
||||||
const found = sentenceState.text.indexOf(alignment.sentence);
|
const found = sentenceState.text.indexOf(alignment.sentence);
|
||||||
sentenceState.base = found >= 0 ? found : 0;
|
if (found < 0) {
|
||||||
|
// Sentence not located in the wrap — fail closed (leave only the sentence
|
||||||
|
// highlight) rather than snapping the word highlight to offset 0. Don't
|
||||||
|
// commit the alignment so the next tick retries the lookup.
|
||||||
|
sentenceState.base = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sentenceState.alignment = alignment;
|
||||||
|
sentenceState.base = found;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const base = sentenceState.base;
|
||||||
|
if (base === null) return false;
|
||||||
|
|
||||||
const word = words[wordIndex];
|
const word = words[wordIndex];
|
||||||
const { charStart, charEnd } = word;
|
const { charStart, charEnd } = word;
|
||||||
if (!Number.isInteger(charStart) || !Number.isInteger(charEnd) || charEnd <= charStart) return false;
|
if (!Number.isInteger(charStart) || !Number.isInteger(charEnd) || charEnd <= charStart) return false;
|
||||||
|
|
||||||
const start = Math.max(0, sentenceState.base + charStart);
|
const start = Math.max(0, base + charStart);
|
||||||
const end = Math.min(sentenceState.chars.length, sentenceState.base + charEnd);
|
const end = Math.min(sentenceState.chars.length, base + charEnd);
|
||||||
if (end <= start) return false;
|
if (end <= start) return false;
|
||||||
|
|
||||||
wordWraps = wrapCharRange(sentenceState.chars, start, end, HTML_WORD_CLASS);
|
wordWraps = wrapCharRange(sentenceState.chars, start, end, HTML_WORD_CLASS);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue