@creativeorange/azure-text-to-speech 3.0.2 → 3.0.4

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.
@@ -75,6 +75,12 @@ export declare class TextToSpeech {
75
75
  private startHighlightingInterval;
76
76
  private updateHighlightForCurrentTime;
77
77
  private highlightTextRange;
78
+ private getSpokenTextDomOffset;
79
+ private isRangeVisuallyVisible;
80
+ private getClippingRect;
81
+ private isClippingElement;
82
+ private isClippingOverflowValue;
83
+ private rectsOverlap;
78
84
  private setHighlightTargetFromNode;
79
85
  private shouldHighlight;
80
86
  private resetHighlightState;
@@ -9188,7 +9188,8 @@ class TextToSpeech {
9188
9188
  segment.highlightDiv,
9189
9189
  segment.originalHighlightDivInnerHTML,
9190
9190
  relativeTextOffset,
9191
- wordBoundary.wordLength
9191
+ wordBoundary.wordLength,
9192
+ segment.text
9192
9193
  );
9193
9194
  this.previousWordBoundary = wordBoundary;
9194
9195
  }
@@ -9437,17 +9438,20 @@ class TextToSpeech {
9437
9438
  this.highlightDiv,
9438
9439
  this.originalHighlightDivInnerHTML,
9439
9440
  normalizedTextOffset,
9440
- wordBoundary.wordLength
9441
+ wordBoundary.wordLength,
9442
+ this.textToRead
9441
9443
  );
9442
9444
  this.previousWordBoundary = wordBoundary;
9443
9445
  this.currentWord = wordBoundary.text;
9444
9446
  this.wordBoundaryOffset = wordBoundary.textOffset;
9445
9447
  }
9446
- highlightTextRange(target, originalHtml, textOffset, wordLength) {
9447
- if (!target || wordLength <= 0) {
9448
+ highlightTextRange(target, originalHtml, textOffset, wordLength, spokenText) {
9449
+ if (!target || wordLength <= 0 || textOffset < 0) {
9448
9450
  return;
9449
9451
  }
9450
9452
  target.innerHTML = originalHtml;
9453
+ const alignmentOffset = this.getSpokenTextDomOffset(target, spokenText);
9454
+ const mappedOffset = textOffset + alignmentOffset;
9451
9455
  const walker = document.createTreeWalker(
9452
9456
  target,
9453
9457
  NodeFilter.SHOW_TEXT
@@ -9457,15 +9461,15 @@ class TextToSpeech {
9457
9461
  let endNode;
9458
9462
  let startOffset = 0;
9459
9463
  let endOffset = 0;
9460
- const requestedEnd = textOffset + wordLength;
9464
+ const requestedEnd = mappedOffset + wordLength;
9461
9465
  while (walker.nextNode()) {
9462
9466
  const node = walker.currentNode;
9463
9467
  const nodeLength = node.data.length;
9464
9468
  const nodeStart = currentOffset;
9465
9469
  const nodeEnd = currentOffset + nodeLength;
9466
- if (!startNode && textOffset >= nodeStart && textOffset < nodeEnd) {
9470
+ if (!startNode && mappedOffset >= nodeStart && mappedOffset < nodeEnd) {
9467
9471
  startNode = node;
9468
- startOffset = textOffset - nodeStart;
9472
+ startOffset = mappedOffset - nodeStart;
9469
9473
  }
9470
9474
  if (requestedEnd > nodeStart && requestedEnd <= nodeEnd) {
9471
9475
  endNode = node;
@@ -9481,6 +9485,9 @@ class TextToSpeech {
9481
9485
  try {
9482
9486
  range.setStart(startNode, startOffset);
9483
9487
  range.setEnd(endNode, endOffset);
9488
+ if (!this.isRangeVisuallyVisible(range, target)) {
9489
+ return;
9490
+ }
9484
9491
  const mark = document.createElement("mark");
9485
9492
  mark.className = "co-tts-highlight";
9486
9493
  mark.appendChild(range.extractContents());
@@ -9489,6 +9496,67 @@ class TextToSpeech {
9489
9496
  target.innerHTML = originalHtml;
9490
9497
  }
9491
9498
  }
9499
+ getSpokenTextDomOffset(target, spokenText) {
9500
+ var _a;
9501
+ if (!spokenText) {
9502
+ return 0;
9503
+ }
9504
+ const targetText = (_a = target.textContent) != null ? _a : "";
9505
+ if (targetText === "" || targetText === spokenText) {
9506
+ return 0;
9507
+ }
9508
+ const index = targetText.indexOf(spokenText);
9509
+ return index >= 0 ? index : 0;
9510
+ }
9511
+ isRangeVisuallyVisible(range, target) {
9512
+ if (typeof range.getClientRects !== "function") {
9513
+ return true;
9514
+ }
9515
+ let rects;
9516
+ try {
9517
+ rects = Array.from(range.getClientRects()).filter(
9518
+ (rect) => rect.width > 0 && rect.height > 0
9519
+ );
9520
+ } catch {
9521
+ return true;
9522
+ }
9523
+ if (rects.length === 0) {
9524
+ return true;
9525
+ }
9526
+ const clipRect = this.getClippingRect(target);
9527
+ return rects.some((rect) => this.rectsOverlap(rect, clipRect));
9528
+ }
9529
+ getClippingRect(target) {
9530
+ let clipElement = target;
9531
+ while (clipElement) {
9532
+ if (this.isClippingElement(clipElement)) {
9533
+ return clipElement.getBoundingClientRect();
9534
+ }
9535
+ clipElement = clipElement.parentElement;
9536
+ }
9537
+ return target.getBoundingClientRect();
9538
+ }
9539
+ isClippingElement(element) {
9540
+ var _a;
9541
+ const style = (_a = globalThis.getComputedStyle) == null ? void 0 : _a.call(globalThis, element);
9542
+ if (!style) {
9543
+ return false;
9544
+ }
9545
+ const overflowX = style.overflowX || style.overflow;
9546
+ const overflowY = style.overflowY || style.overflow;
9547
+ const clipsOverflow = this.isClippingOverflowValue(overflowX) || this.isClippingOverflowValue(overflowY);
9548
+ if (clipsOverflow) {
9549
+ return true;
9550
+ }
9551
+ const lineClamp = style.getPropertyValue("-webkit-line-clamp").trim();
9552
+ return lineClamp !== "" && lineClamp !== "none";
9553
+ }
9554
+ isClippingOverflowValue(value) {
9555
+ return value === "hidden" || value === "auto" || value === "scroll" || value === "clip";
9556
+ }
9557
+ rectsOverlap(a, b) {
9558
+ return a.right > b.left && a.left < b.right && a.bottom > b.top && a.top < b.bottom;
9559
+ }
9492
9560
  setHighlightTargetFromNode(node) {
9493
9561
  var _a;
9494
9562
  const highlightDiv = this.getHighlightDivForNode(node);