Skip to content
This repository has been archived by the owner on Feb 25, 2023. It is now read-only.

Commit

Permalink
TextSource* API updates (#2236)
Browse files Browse the repository at this point in the history
* Move TextSourceRange static functions to DocumentUtil

getWritingMode is also simplified

* Update Google Docs range to be empty to match other range sources

* Rename imposterContainer to imposterElement

* Add static creation functions

* Add static creation function

* Remove unused collapse function

* Don't select imposter elements

* Refactor setEndOffset

* Adjust argument order for setEndOffset

* Update TextSourceRange constructor

* Remove unused isConnected

* Cache rects

* Fix test

* Remove unused getRect

* Revert "Fix test"

* Remove cachedRect

* Use the source element rect to handle scroll differences

* Writing mode update

* Remove _cachedRects update

This shouldn't be necessary as the imposter is usually detached
almost immediately after scanning, giving no time for the window
to be resized or scrolled.
  • Loading branch information
toasted-nutbread authored Sep 25, 2022
1 parent 8240482 commit 75d3059
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 90 deletions.
4 changes: 2 additions & 2 deletions ext/js/accessibility/google-docs-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class GoogleDocsUtil {
const range = this._getRangeWithPoint(content, x, y, normalizeCssZoom);
this._setImportantStyle(textStyle, 'pointer-events', 'none');
this._setImportantStyle(textStyle, 'opacity', '0');
return new TextSourceRange(range, '', svgText, element);
return TextSourceRange.createFromImposter(range, svgText, element);
}

static _getRangeWithPoint(textNode, x, y, normalizeCssZoom) {
Expand All @@ -110,7 +110,7 @@ class GoogleDocsUtil {
}
}
range.setStart(textNode, start);
range.setEnd(textNode, end);
range.setEnd(textNode, start);
return range;
}

Expand Down
2 changes: 1 addition & 1 deletion ext/js/app/frontend.js
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,7 @@ class Frontend {
async _scanSelectedText(allowEmptyRange) {
const range = this._getFirstSelectionRange(allowEmptyRange);
if (range === null) { return false; }
const source = new TextSourceRange(range, range.toString(), null, null);
const source = TextSourceRange.create(range);
await this._textScanner.search(source, {focus: true, restoreSelection: true});
return true;
}
Expand Down
59 changes: 56 additions & 3 deletions ext/js/dom/document-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class DocumentUtil {
case 'IMG':
case 'BUTTON':
case 'SELECT':
return new TextSourceElement(element);
return TextSourceElement.create(element);
case 'INPUT':
if (element.type === 'text') {
imposterSourceElement = element;
Expand All @@ -85,8 +85,9 @@ class DocumentUtil {
if (imposter !== null) {
this._setImposterStyle(imposterContainer.style, 'z-index', '-2147483646');
this._setImposterStyle(imposter.style, 'pointer-events', 'none');
return TextSourceRange.createFromImposter(range, imposterContainer, imposterSourceElement);
}
return new TextSourceRange(range, '', imposterContainer, imposterSourceElement);
return TextSourceRange.create(range);
} else {
if (imposterContainer !== null) {
imposterContainer.parentNode.removeChild(imposterContainer);
Expand Down Expand Up @@ -131,7 +132,7 @@ class DocumentUtil {
// Scan text
source = source.clone();
const startLength = source.setStartOffset(extent, layoutAwareScan);
const endLength = source.setEndOffset(extent * 2 - startLength, layoutAwareScan, true);
const endLength = source.setEndOffset(extent * 2 - startLength, true, layoutAwareScan);
const text = source.text();
const textLength = text.length;
const textEndAnchor = textLength - endLength;
Expand Down Expand Up @@ -418,6 +419,58 @@ class DocumentUtil {
}
}

/**
* Offsets an array of DOMRects by a given amount.
* @param {DOMRect[]} rects The DOMRects to offset.
* @param {number} x The horizontal offset amount.
* @param {number} y The vertical offset amount.
* @returns {DOMRect} The DOMRects with the offset applied.
*/
static offsetDOMRects(rects, x, y) {
const results = [];
for (const rect of rects) {
results.push(new DOMRect(rect.left + x, rect.top + y, rect.width, rect.height));
}
return results;
}

/**
* Gets the parent writing mode of an element.
* See: https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode.
* @param {Element} element The HTML element to check.
* @returns {string} The writing mode.
*/
static getElementWritingMode(element) {
if (element !== null) {
const {writingMode} = getComputedStyle(element);
if (typeof writingMode === 'string') {
return this.normalizeWritingMode(writingMode);
}
}
return 'horizontal-tb';
}

/**
* Normalizes a CSS writing mode value by converting non-standard and deprecated values
* into their corresponding standard vaules.
* @param {string} writingMode The writing mode to normalize.
* @returns {string} The normalized writing mode.
*/
static normalizeWritingMode(writingMode) {
switch (writingMode) {
case 'lr':
case 'lr-tb':
case 'rl':
return 'horizontal-tb';
case 'tb':
return 'vertical-lr';
case 'tb-rl':
return 'vertical-rl';
default:
return writingMode;
}
}

// Private

static _getActiveButtons(event, array) {
Expand Down
29 changes: 8 additions & 21 deletions ext/js/dom/text-source-element.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
*/

class TextSourceElement {
constructor(element, fullContent=null, startOffset=0, endOffset=0) {
constructor(element, fullContent, startOffset, endOffset) {
this._element = element;
this._fullContent = (typeof fullContent === 'string' ? fullContent : TextSourceElement.getElementContent(element));
this._fullContent = fullContent;
this._startOffset = startOffset;
this._endOffset = endOffset;
this._content = this._fullContent.substring(this._startOffset, this._endOffset);
Expand All @@ -49,10 +49,6 @@ class TextSourceElement {
return this._endOffset;
}

get isConnected() {
return this._element.isConnected;
}

clone() {
return new TextSourceElement(this._element, this._fullContent, this._startOffset, this._endOffset);
}
Expand All @@ -65,7 +61,7 @@ class TextSourceElement {
return this._content;
}

setEndOffset(length, _layoutAwareScan, fromEnd) {
setEndOffset(length, fromEnd) {
const offset = fromEnd ? this._endOffset : this._startOffset;
length = Math.min(this._fullContent.length - offset, length);
if (length > 0) {
Expand All @@ -86,19 +82,6 @@ class TextSourceElement {
return length;
}

collapse(toStart) {
if (toStart) {
this._endOffset = this._startOffset;
} else {
this._startOffset = this._endOffset;
}
this._content = '';
}

getRect() {
return DocumentUtil.convertRectZoomCoordinates(this._element.getBoundingClientRect(), this._element);
}

getRects() {
return DocumentUtil.convertMultipleRectZoomCoordinates(this._element.getClientRects(), this._element);
}
Expand Down Expand Up @@ -130,7 +113,11 @@ class TextSourceElement {
return [this._element];
}

static getElementContent(element) {
static create(element) {
return new TextSourceElement(element, this._getElementContent(element), 0, 0);
}

static _getElementContent(element) {
let content;
switch (element.nodeName.toUpperCase()) {
case 'BUTTON':
Expand Down
109 changes: 51 additions & 58 deletions ext/js/dom/text-source-range.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@
*/

class TextSourceRange {
constructor(range, content, imposterContainer, imposterSourceElement) {
constructor(range, rangeStartOffset, content, imposterElement, imposterSourceElement, cachedRects, cachedSourceRect) {
this._range = range;
this._rangeStartOffset = range.startOffset;
this._rangeStartOffset = rangeStartOffset;
this._content = content;
this._imposterContainer = imposterContainer;
this._imposterElement = imposterElement;
this._imposterSourceElement = imposterSourceElement;
this._cachedRects = cachedRects;
this._cachedSourceRect = cachedSourceRect;
}

get type() {
Expand All @@ -45,33 +47,39 @@ class TextSourceRange {
return this._imposterSourceElement;
}

get isConnected() {
return (
this._range.startContainer.isConnected &&
this._range.endContainer.isConnected
);
}

clone() {
return new TextSourceRange(this._range.cloneRange(), this._content, this._imposterContainer, this._imposterSourceElement);
return new TextSourceRange(
this._range.cloneRange(),
this._rangeStartOffset,
this._content,
this._imposterElement,
this._imposterSourceElement,
this._cachedRects,
this._cachedSourceRect
);
}

cleanup() {
if (this._imposterContainer !== null && this._imposterContainer.parentNode !== null) {
this._imposterContainer.parentNode.removeChild(this._imposterContainer);
if (this._imposterElement !== null && this._imposterElement.parentNode !== null) {
this._imposterElement.parentNode.removeChild(this._imposterElement);
}
}

text() {
return this._content;
}

setEndOffset(length, layoutAwareScan, fromEnd) {
const state = (
fromEnd ?
new DOMTextScanner(this._range.endContainer, this._range.endOffset, !layoutAwareScan, layoutAwareScan).seek(length) :
new DOMTextScanner(this._range.startContainer, this._range.startOffset, !layoutAwareScan, layoutAwareScan).seek(length)
);
setEndOffset(length, fromEnd, layoutAwareScan) {
let node;
let offset;
if (fromEnd) {
node = this._range.endContainer;
offset = this._range.endOffset;
} else {
node = this._range.startContainer;
offset = this._range.startOffset;
}
const state = new DOMTextScanner(node, offset, !layoutAwareScan, layoutAwareScan).seek(length);
this._range.setEnd(state.node, state.offset);
this._content = (fromEnd ? this._content + state.content : state.content);
return length - state.remainder;
Expand All @@ -85,30 +93,25 @@ class TextSourceRange {
return length - state.remainder;
}

collapse(toStart) {
this._range.collapse(toStart);
this._content = '';
}

getRect() {
return DocumentUtil.convertRectZoomCoordinates(this._range.getBoundingClientRect(), this._range.startContainer);
}

getRects() {
if (this._isImposterDisconnected()) { return this._getCachedRects(); }
return DocumentUtil.convertMultipleRectZoomCoordinates(this._range.getClientRects(), this._range.startContainer);
}

getWritingMode() {
return TextSourceRange.getElementWritingMode(TextSourceRange.getParentElement(this._range.startContainer));
const node = this._isImposterDisconnected() ? this._imposterSourceElement : this._range.startContainer;
return DocumentUtil.getElementWritingMode(node !== null ? node.parentElement : null);
}

select() {
if (this._imposterElement !== null) { return; }
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(this._range);
}

deselect() {
if (this._imposterElement !== null) { return; }
const selection = window.getSelection();
selection.removeAllRanges();
}
Expand Down Expand Up @@ -143,36 +146,26 @@ class TextSourceRange {
return DocumentUtil.getNodesInRange(this._range);
}

static getParentElement(node) {
while (node !== null && node.nodeType !== Node.ELEMENT_NODE) {
node = node.parentNode;
}
return node;
static create(range) {
return new TextSourceRange(range, range.startOffset, range.toString(), null, null, null, null);
}

static getElementWritingMode(element) {
if (element !== null) {
const style = window.getComputedStyle(element);
const writingMode = style.writingMode;
if (typeof writingMode === 'string') {
return TextSourceRange.normalizeWritingMode(writingMode);
}
}
return 'horizontal-tb';
}

static normalizeWritingMode(writingMode) {
switch (writingMode) {
case 'lr':
case 'lr-tb':
case 'rl':
return 'horizontal-tb';
case 'tb':
return 'vertical-lr';
case 'tb-rl':
return 'vertical-rl';
default:
return writingMode;
}
static createFromImposter(range, imposterElement, imposterSourceElement) {
const cachedRects = DocumentUtil.convertMultipleRectZoomCoordinates(range.getClientRects(), range.startContainer);
const cachedSourceRect = DocumentUtil.convertRectZoomCoordinates(imposterSourceElement.getBoundingClientRect(), imposterSourceElement);
return new TextSourceRange(range, range.startOffset, range.toString(), imposterElement, imposterSourceElement, cachedRects, cachedSourceRect);
}

_isImposterDisconnected() {
return this._imposterElement !== null && !this._imposterElement.isConnected;
}

_getCachedRects() {
const sourceRect = DocumentUtil.convertRectZoomCoordinates(this._imposterSourceElement.getBoundingClientRect(), this._imposterSourceElement);
return DocumentUtil.offsetDOMRects(
this._cachedRects,
sourceRect.left - this._cachedSourceRect.left,
sourceRect.top - this._cachedSourceRect.top
);
}
}
8 changes: 4 additions & 4 deletions ext/js/language/text-scanner.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ class TextScanner extends EventDispatcher {
getTextSourceContent(textSource, length, layoutAwareScan) {
const clonedTextSource = textSource.clone();

clonedTextSource.setEndOffset(length, layoutAwareScan, false);
clonedTextSource.setEndOffset(length, false, layoutAwareScan);

const includeSelector = this._includeSelector;
const excludeSelector = this._excludeSelector;
Expand Down Expand Up @@ -875,7 +875,7 @@ class TextScanner extends EventDispatcher {
const {dictionaryEntries, originalTextLength} = await yomichan.api.termsFind(searchText, details, optionsContext);
if (dictionaryEntries.length === 0) { return null; }

textSource.setEndOffset(originalTextLength, layoutAwareScan, false);
textSource.setEndOffset(originalTextLength, false, layoutAwareScan);
const sentence = DocumentUtil.extractSentence(
textSource,
layoutAwareScan,
Expand All @@ -902,7 +902,7 @@ class TextScanner extends EventDispatcher {
const dictionaryEntries = await yomichan.api.kanjiFind(searchText, optionsContext);
if (dictionaryEntries.length === 0) { return null; }

textSource.setEndOffset(1, layoutAwareScan, false);
textSource.setEndOffset(1, false, layoutAwareScan);
const sentence = DocumentUtil.extractSentence(
textSource,
layoutAwareScan,
Expand Down Expand Up @@ -1127,7 +1127,7 @@ class TextScanner extends EventDispatcher {
(excludeSelector !== null && DocumentUtil.anyNodeMatchesSelector(nodes, excludeSelector))
) {
--length;
textSource.setEndOffset(length, layoutAwareScan, false);
textSource.setEndOffset(length, false, layoutAwareScan);
} else {
break;
}
Expand Down
2 changes: 1 addition & 1 deletion ext/js/pages/settings/popup-preview-frame.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ class PopupPreviewFrame {

const range = document.createRange();
range.selectNodeContents(textNode);
const source = new TextSourceRange(range, range.toString(), null, null);
const source = TextSourceRange.create(range);

try {
await this._frontend.setTextSource(source);
Expand Down

0 comments on commit 75d3059

Please sign in to comment.