Blob Blame History Raw
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef mozilla_a11y_TextRange_h__
#define mozilla_a11y_TextRange_h__

#include "mozilla/Move.h"
#include "nsCaseTreatment.h"
#include "nsRect.h"
#include "nsTArray.h"

class nsIVariant;

namespace mozilla {
namespace a11y {

class Accessible;
class HyperTextAccessible;

/**
 * A text point (hyper text + offset), represents a boundary of text range.
 */
struct TextPoint final {
  TextPoint(HyperTextAccessible* aContainer, int32_t aOffset)
      : mContainer(aContainer), mOffset(aOffset) {}
  TextPoint(const TextPoint& aPoint)
      : mContainer(aPoint.mContainer), mOffset(aPoint.mOffset) {}

  HyperTextAccessible* mContainer;
  int32_t mOffset;

  bool operator==(const TextPoint& aPoint) const {
    return mContainer == aPoint.mContainer && mOffset == aPoint.mOffset;
  }
  bool operator<(const TextPoint& aPoint) const;
};

/**
 * Represents a text range within the text control or document.
 */
class TextRange final {
 public:
  TextRange(HyperTextAccessible* aRoot, HyperTextAccessible* aStartContainer,
            int32_t aStartOffset, HyperTextAccessible* aEndContainer,
            int32_t aEndOffset);
  TextRange() : mStartOffset{0}, mEndOffset{0} {}
  TextRange(TextRange&& aRange)
      : mRoot(mozilla::Move(aRange.mRoot)),
        mStartContainer(mozilla::Move(aRange.mStartContainer)),
        mEndContainer(mozilla::Move(aRange.mEndContainer)),
        mStartOffset(aRange.mStartOffset),
        mEndOffset(aRange.mEndOffset) {}

  TextRange& operator=(TextRange&& aRange) {
    mRoot = mozilla::Move(aRange.mRoot);
    mStartContainer = mozilla::Move(aRange.mStartContainer);
    mEndContainer = mozilla::Move(aRange.mEndContainer);
    mStartOffset = aRange.mStartOffset;
    mEndOffset = aRange.mEndOffset;
    return *this;
  }

  HyperTextAccessible* StartContainer() const { return mStartContainer; }
  int32_t StartOffset() const { return mStartOffset; }
  HyperTextAccessible* EndContainer() const { return mEndContainer; }
  int32_t EndOffset() const { return mEndOffset; }

  bool operator==(const TextRange& aRange) const {
    return mStartContainer == aRange.mStartContainer &&
           mStartOffset == aRange.mStartOffset &&
           mEndContainer == aRange.mEndContainer &&
           mEndOffset == aRange.mEndOffset;
  }

  TextPoint StartPoint() const {
    return TextPoint(mStartContainer, mStartOffset);
  }
  TextPoint EndPoint() const { return TextPoint(mEndContainer, mEndOffset); }

  /**
   * Return a container containing both start and end points.
   */
  Accessible* Container() const;

  /**
   * Return a list of embedded objects enclosed by the text range (includes
   * partially overlapped objects).
   */
  void EmbeddedChildren(nsTArray<Accessible*>* aChildren) const;

  /**
   * Return text enclosed by the range.
   */
  void Text(nsAString& aText) const;

  /**
   * Return list of bounding rects of the text range by lines.
   */
  void Bounds(nsTArray<nsIntRect> aRects) const;

  enum ETextUnit { eFormat, eWord, eLine, eParagraph, ePage, eDocument };

  /**
   * Move the range or its points on specified amount of given units.
   */
  void Move(ETextUnit aUnit, int32_t aCount) {
    MoveEnd(aUnit, aCount);
    MoveStart(aUnit, aCount);
  }
  void MoveStart(ETextUnit aUnit, int32_t aCount) {
    MoveInternal(aUnit, aCount, *mStartContainer, mStartOffset, mEndContainer,
                 mEndOffset);
  }
  void MoveEnd(ETextUnit aUnit, int32_t aCount) {
    MoveInternal(aUnit, aCount, *mEndContainer, mEndOffset);
  }

  /**
   * Move the range points to the closest unit boundaries.
   */
  void Normalize(ETextUnit aUnit);

  /**
   * Crops the range if it overlaps the given accessible element boundaries,
   * returns true if the range was cropped successfully.
   */
  bool Crop(Accessible* aContainer);

  enum EDirection { eBackward, eForward };

  /**
   * Return range enclosing the found text.
   */
  void FindText(const nsAString& aText, EDirection aDirection,
                nsCaseTreatment aCaseSensitive, TextRange* aFoundRange) const;

  enum EAttr {
    eAnimationStyleAttr,
    eAnnotationObjectsAttr,
    eAnnotationTypesAttr,
    eBackgroundColorAttr,
    eBulletStyleAttr,
    eCapStyleAttr,
    eCaretBidiModeAttr,
    eCaretPositionAttr,
    eCultureAttr,
    eFontNameAttr,
    eFontSizeAttr,
    eFontWeightAttr,
    eForegroundColorAttr,
    eHorizontalTextAlignmentAttr,
    eIndentationFirstLineAttr,
    eIndentationLeadingAttr,
    eIndentationTrailingAttr,
    eIsActiveAttr,
    eIsHiddenAttr,
    eIsItalicAttr,
    eIsReadOnlyAttr,
    eIsSubscriptAttr,
    eIsSuperscriptAttr,
    eLinkAttr,
    eMarginBottomAttr,
    eMarginLeadingAttr,
    eMarginTopAttr,
    eMarginTrailingAttr,
    eOutlineStylesAttr,
    eOverlineColorAttr,
    eOverlineStyleAttr,
    eSelectionActiveEndAttr,
    eStrikethroughColorAttr,
    eStrikethroughStyleAttr,
    eStyleIdAttr,
    eStyleNameAttr,
    eTabsAttr,
    eTextFlowDirectionsAttr,
    eUnderlineColorAttr,
    eUnderlineStyleAttr
  };

  /**
   * Return range enclosing text having requested attribute.
   */
  void FindAttr(EAttr aAttr, nsIVariant* aValue, EDirection aDirection,
                TextRange* aFoundRange) const;

  /**
   * Add/remove the text range from selection.
   */
  void AddToSelection() const;
  void RemoveFromSelection() const;
  void Select() const;

  /**
   * Scroll the text range into view.
   */
  enum EHowToAlign { eAlignToTop, eAlignToBottom };
  void ScrollIntoView(EHowToAlign aHow) const;

  /**
   * Return true if this TextRange object represents an actual range of text.
   */
  bool IsValid() const { return mRoot; }

  void SetStartPoint(HyperTextAccessible* aContainer, int32_t aOffset) {
    mStartContainer = aContainer;
    mStartOffset = aOffset;
  }
  void SetEndPoint(HyperTextAccessible* aContainer, int32_t aOffset) {
    mStartContainer = aContainer;
    mStartOffset = aOffset;
  }

 private:
  TextRange(const TextRange& aRange) = delete;
  TextRange& operator=(const TextRange& aRange) = delete;

  friend class HyperTextAccessible;
  friend class xpcAccessibleTextRange;

  void Set(HyperTextAccessible* aRoot, HyperTextAccessible* aStartContainer,
           int32_t aStartOffset, HyperTextAccessible* aEndContainer,
           int32_t aEndOffset);

  /**
   * Text() method helper.
   * @param  aText            [in,out] calculated text
   * @param  aCurrent         [in] currently traversed node
   * @param  aStartIntlOffset [in] start offset if current node is a text node
   * @return                   true if calculation is not finished yet
   */
  bool TextInternal(nsAString& aText, Accessible* aCurrent,
                    uint32_t aStartIntlOffset) const;

  void MoveInternal(ETextUnit aUnit, int32_t aCount,
                    HyperTextAccessible& aContainer, int32_t aOffset,
                    HyperTextAccessible* aStopContainer = nullptr,
                    int32_t aStopOffset = 0);

  /**
   * A helper method returning a common parent for two given accessible
   * elements.
   */
  Accessible* CommonParent(Accessible* aAcc1, Accessible* aAcc2,
                           nsTArray<Accessible*>* aParents1, uint32_t* aPos1,
                           nsTArray<Accessible*>* aParents2,
                           uint32_t* aPos2) const;

  RefPtr<HyperTextAccessible> mRoot;
  RefPtr<HyperTextAccessible> mStartContainer;
  RefPtr<HyperTextAccessible> mEndContainer;
  int32_t mStartOffset;
  int32_t mEndOffset;
};

}  // namespace a11y
}  // namespace mozilla

#endif