moveCaret(isUp, select) {
    const selection = document.getSelection();
    if (selection.rangeCount === 0) {
      return;
    }
    const { focusNode } = selection;
    const focusElement =
      focusNode.nodeType !== Node.ELEMENT_NODE
        ? focusNode.parentElement
        : focusNode;
    const root = focusElement.closest(".textLayer");
    if (!root) {
      return;
    }
    const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
    walker.currentNode = focusNode;

    // Move to the next element which is not on the same line as the focus
    // element.
    const focusRect = focusElement.getBoundingClientRect();
    let newLineElement = null;
    const nodeIterator = (
      isUp ? walker.previousSibling : walker.nextSibling
    ).bind(walker);
    while (nodeIterator()) {
      const element = walker.currentNode.parentElement;
      if (!this.#isOnSameLine(focusRect, element.getBoundingClientRect())) {
        newLineElement = element;
        break;
      }
    }

    if (!newLineElement) {
      // Need to find the next line on the next page.
      const node = this.#getNodeOnNextPage(root, isUp);
      if (!node) {
        return;
      }
      if (select) {
        const lastNode =
          (isUp ? walker.firstChild() : walker.lastChild()) || focusNode;
        selection.extend(lastNode, isUp ? 0 : lastNode.length);
        const range = document.createRange();
        range.setStart(node, isUp ? node.length : 0);
        range.setEnd(node, isUp ? node.length : 0);
        selection.addRange(range);
        return;
      }
      const [caretX] = this.#getCaretPosition(selection, isUp);
      const { parentElement } = node;
      this.#setCaretPosition(
        select,
        selection,
        parentElement,
        parentElement.getBoundingClientRect(),
        caretX
      );
      return;
    }

    // We've a candidate for the next line now we want to find the first element
    // which is under/over the caret.
    const [caretX, caretY] = this.#getCaretPosition(selection, isUp);
    const newLineElementRect = newLineElement.getBoundingClientRect();

    // Maybe the element on the new line is a valid candidate.
    if (this.#isUnderOver(newLineElementRect, caretX, caretY, isUp)) {
      this.#setCaretPosition(
        select,
        selection,
        newLineElement,
        newLineElementRect,
        caretX
      );
      return;
    }

    while (nodeIterator()) {
      // Search an element on the same line as newLineElement
      // which could be under/over the caret.
      const element = walker.currentNode.parentElement;
      const elementRect = element.getBoundingClientRect();
      if (!this.#isOnSameLine(newLineElementRect, elementRect)) {
        break;
      }
      if (this.#isUnderOver(elementRect, caretX, caretY, isUp)) {
        // We found the element.
        this.#setCaretPosition(select, selection, element, elementRect, caretX);
        return;
      }
    }

    // No element has been found so just put the caret on the element on the new
    // line.
    this.#setCaretPosition(
      select,
      selection,
      newLineElement,
      newLineElementRect,
      caretX
    );
  }
Move the caret in the given direction.
@param {boolean} isUp
@param {boolean} select